Fix instance parameter type of inherited declaration in reflection
Both for callables obtained via reflection API (KClass.members etc) and for callables obtained via ::-references, the instance parameter is now the class which was used to construct the type at the left-hand side of the reference, NOT the class where the callable is originally declared as is known at compile-time. The reason is to reduce the difference in behavior of KCallable.call vs FunctionN.invoke: the latter always required the subclass instance for a fake override, and it's reasonable that the former would require it as well. Note that in Java reflection, behavior could differ in a similar case. For a simple fake override, Class.getMethod would return the method declared in the base class and that method will accept instances of the base class in invoke. However, it's difficult to rely on this behavior because if there's a bridge for a fake override in the derived class (e.g. when overridden members have different signatures), the returned Method object is accepting the derived class as the receiver. This just confirms the fact that Java reflection operates on a different level of abstraction, namely JVM methods in .class files, which is not applicable to our use cases directly. Another reason not to replicate Java reflection's behavior is the uncertainty as to which member is returned in case there are several in the hierarchy for a given fake override: see the "otherwise one of the methods is chosen arbitrarily" note in javadoc on Class.getMethod. #KT-24170 Fixed
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
// IGNORE_BACKEND: JS_IR, JS, NATIVE
|
||||
// WITH_REFLECT
|
||||
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
open class A<T> {
|
||||
fun foo(t: T) {}
|
||||
}
|
||||
|
||||
open class B<U> : A<U>()
|
||||
|
||||
class C : B<String>()
|
||||
|
||||
fun box(): String {
|
||||
val afoo = A::class.members.single { it.name == "foo" }
|
||||
val bfoo = B::class.members.single { it.name == "foo" }
|
||||
val cfoo = C::class.members.single { it.name == "foo" }
|
||||
|
||||
assertNotEquals(afoo, bfoo)
|
||||
assertNotEquals(afoo.hashCode(), bfoo.hashCode())
|
||||
assertNotEquals(bfoo, cfoo)
|
||||
assertNotEquals(bfoo.hashCode(), cfoo.hashCode())
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// IGNORE_BACKEND: JVM_IR, JS_IR, JS, NATIVE
|
||||
// WITH_REFLECT
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
open class A<T> {
|
||||
fun foo(t: T) {}
|
||||
}
|
||||
|
||||
open class B<U> : A<U>()
|
||||
|
||||
class C : B<String>()
|
||||
|
||||
fun box(): String {
|
||||
assertEquals("fun A<T>.foo(T): kotlin.Unit", A<Double>::foo.toString())
|
||||
assertEquals("fun B<U>.foo(U): kotlin.Unit", B<Float>::foo.toString())
|
||||
assertEquals("fun C.foo(kotlin.String): kotlin.Unit", C::foo.toString())
|
||||
|
||||
val afoo = A::class.members.single { it.name == "foo" }
|
||||
assertEquals("fun A<T>.foo(T): kotlin.Unit", afoo.toString())
|
||||
val bfoo = B::class.members.single { it.name == "foo" }
|
||||
assertEquals("fun B<U>.foo(U): kotlin.Unit", bfoo.toString())
|
||||
val cfoo = C::class.members.single { it.name == "foo" }
|
||||
assertEquals("fun C.foo(kotlin.String): kotlin.Unit", cfoo.toString())
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// IGNORE_BACKEND: JVM_IR, JS_IR, JS, NATIVE
|
||||
// WITH_REFLECT
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
interface I1 {
|
||||
fun f()
|
||||
val x: Int
|
||||
}
|
||||
|
||||
interface I2 {
|
||||
fun f()
|
||||
val x: Int
|
||||
}
|
||||
|
||||
interface I3 {
|
||||
fun f()
|
||||
val x: Int
|
||||
}
|
||||
|
||||
interface I : I2, I1, I3
|
||||
|
||||
fun box(): String {
|
||||
assertEquals("fun I.f(): kotlin.Unit", I::f.toString())
|
||||
assertEquals("val I.x: kotlin.Int", I::x.toString())
|
||||
|
||||
val f = I::class.members.single { it.name == "f" }
|
||||
assertEquals("fun I.f(): kotlin.Unit", f.toString())
|
||||
val x = I::class.members.single { it.name == "x" }
|
||||
assertEquals("val I.x: kotlin.Int", x.toString())
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// IGNORE_BACKEND: JS, NATIVE, JS_IR, JVM_IR
|
||||
// WITH_REFLECT
|
||||
|
||||
import kotlin.reflect.full.instanceParameter
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
import kotlin.reflect.jvm.javaType
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
open class A {
|
||||
val property = "OK"
|
||||
|
||||
fun function() {}
|
||||
}
|
||||
|
||||
class B : A()
|
||||
|
||||
fun box(): String {
|
||||
assertEquals(B::class, B::property.instanceParameter!!.type.jvmErasure)
|
||||
assertEquals(B::class.java, B::property.instanceParameter!!.type.javaType)
|
||||
assertEquals(B::class, B::function.instanceParameter!!.type.jvmErasure)
|
||||
assertEquals(B::class.java, B::function.instanceParameter!!.type.javaType)
|
||||
|
||||
val property = B::class.members.single { it.name == "property" }
|
||||
val function = B::class.members.single { it.name == "function" }
|
||||
assertEquals(B::class, property.instanceParameter!!.type.jvmErasure)
|
||||
assertEquals(B::class.java, property.instanceParameter!!.type.javaType)
|
||||
assertEquals(B::class, function.instanceParameter!!.type.jvmErasure)
|
||||
assertEquals(B::class.java, function.instanceParameter!!.type.javaType)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+20
@@ -19097,6 +19097,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/extensionPropertyReceiverToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideEqualsHashCode.kt")
|
||||
public void testFakeOverrideEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideEqualsHashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString.kt")
|
||||
public void testFakeOverrideToString() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString2.kt")
|
||||
public void testFakeOverrideToString2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionEqualsHashCode.kt")
|
||||
public void testFunctionEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/functionEqualsHashCode.kt");
|
||||
@@ -19377,6 +19392,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceExtensionReceiverAndValueParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("instanceParameterOfFakeOverride.kt")
|
||||
public void testInstanceParameterOfFakeOverride() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceParameterOfFakeOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("isMarkedNullable.kt")
|
||||
public void testIsMarkedNullable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/isMarkedNullable.kt");
|
||||
|
||||
+20
@@ -19097,6 +19097,21 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/extensionPropertyReceiverToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideEqualsHashCode.kt")
|
||||
public void testFakeOverrideEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideEqualsHashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString.kt")
|
||||
public void testFakeOverrideToString() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString2.kt")
|
||||
public void testFakeOverrideToString2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionEqualsHashCode.kt")
|
||||
public void testFunctionEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/functionEqualsHashCode.kt");
|
||||
@@ -19377,6 +19392,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceExtensionReceiverAndValueParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("instanceParameterOfFakeOverride.kt")
|
||||
public void testInstanceParameterOfFakeOverride() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceParameterOfFakeOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("isMarkedNullable.kt")
|
||||
public void testIsMarkedNullable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/isMarkedNullable.kt");
|
||||
|
||||
+20
@@ -19102,6 +19102,21 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/extensionPropertyReceiverToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideEqualsHashCode.kt")
|
||||
public void testFakeOverrideEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideEqualsHashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString.kt")
|
||||
public void testFakeOverrideToString() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString2.kt")
|
||||
public void testFakeOverrideToString2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionEqualsHashCode.kt")
|
||||
public void testFunctionEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/functionEqualsHashCode.kt");
|
||||
@@ -19382,6 +19397,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceExtensionReceiverAndValueParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("instanceParameterOfFakeOverride.kt")
|
||||
public void testInstanceParameterOfFakeOverride() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceParameterOfFakeOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("isMarkedNullable.kt")
|
||||
public void testIsMarkedNullable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/isMarkedNullable.kt");
|
||||
|
||||
@@ -40,12 +40,16 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
|
||||
val result = ArrayList<KParameter>()
|
||||
var index = 0
|
||||
|
||||
if (descriptor.dispatchReceiverParameter != null && !isBound) {
|
||||
result.add(KParameterImpl(this, index++, KParameter.Kind.INSTANCE) { descriptor.dispatchReceiverParameter!! })
|
||||
}
|
||||
if (!isBound) {
|
||||
val instanceReceiver = descriptor.instanceReceiverParameter
|
||||
if (instanceReceiver != null) {
|
||||
result.add(KParameterImpl(this, index++, KParameter.Kind.INSTANCE) { instanceReceiver })
|
||||
}
|
||||
|
||||
if (descriptor.extensionReceiverParameter != null && !isBound) {
|
||||
result.add(KParameterImpl(this, index++, KParameter.Kind.EXTENSION_RECEIVER) { descriptor.extensionReceiverParameter!! })
|
||||
val extensionReceiver = descriptor.extensionReceiverParameter
|
||||
if (extensionReceiver != null) {
|
||||
result.add(KParameterImpl(this, index++, KParameter.Kind.EXTENSION_RECEIVER) { extensionReceiver })
|
||||
}
|
||||
}
|
||||
|
||||
for (i in descriptor.valueParameters.indices) {
|
||||
|
||||
@@ -182,6 +182,9 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
|
||||
|
||||
private val classId: ClassId get() = RuntimeTypeMapper.mapJvmClassToKotlinClassId(jClass)
|
||||
|
||||
// Note that we load members from the container's default type, which might be confusing. For example, a function declared in a
|
||||
// generic class "A<T>" would have "A<T>" as the receiver parameter even if a concrete type like "A<String>" was specified
|
||||
// in the function reference. Another, maybe slightly less confusing, approach would be to use the star-projected type ("A<*>").
|
||||
internal val memberScope: MemberScope get() = descriptor.defaultType.memberScope
|
||||
|
||||
internal val staticScope: MemberScope get() = descriptor.staticScope
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package kotlin.reflect.jvm.internal
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KType
|
||||
@@ -41,7 +40,22 @@ internal class KParameterImpl(
|
||||
}
|
||||
|
||||
override val type: KType
|
||||
get() = KTypeImpl(descriptor.type) { callable.caller.parameterTypes[index] }
|
||||
get() = KTypeImpl(descriptor.type) {
|
||||
val descriptor = descriptor
|
||||
|
||||
if (descriptor is ReceiverParameterDescriptor &&
|
||||
callable.descriptor.instanceReceiverParameter == descriptor &&
|
||||
callable.descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|
||||
) {
|
||||
// In case of fake overrides, dispatch receiver type should be computed manually because Caller.parameterTypes returns
|
||||
// types from Java reflection where receiver is always the declaring class of the original declaration
|
||||
// (not the class where the fake override is generated, which is returned by KParameter.type)
|
||||
(callable.descriptor.containingDeclaration as ClassDescriptor).toJavaClass()
|
||||
?: throw KotlinReflectionInternalError("Cannot determine receiver Java type of inherited declaration: $descriptor")
|
||||
} else {
|
||||
callable.caller.parameterTypes[index]
|
||||
}
|
||||
}
|
||||
|
||||
override val isOptional: Boolean
|
||||
get() = (descriptor as? ValueParameterDescriptor)?.declaresOrInheritsDefaultValue() ?: false
|
||||
|
||||
@@ -33,7 +33,7 @@ internal object ReflectionObjectRenderer {
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendReceivers(callable: CallableDescriptor) {
|
||||
val dispatchReceiver = callable.dispatchReceiverParameter
|
||||
val dispatchReceiver = callable.instanceReceiverParameter
|
||||
val extensionReceiver = callable.extensionReceiverParameter
|
||||
|
||||
appendReceiverType(dispatchReceiver)
|
||||
|
||||
@@ -182,6 +182,11 @@ internal val CallableMemberDescriptor.isPublicInBytecode: Boolean
|
||||
return (visibility == Visibilities.PUBLIC || visibility == Visibilities.INTERNAL) && !isEffectivelyInlineOnly()
|
||||
}
|
||||
|
||||
internal val CallableDescriptor.instanceReceiverParameter: ReceiverParameterDescriptor?
|
||||
get() =
|
||||
if (dispatchReceiverParameter != null) (containingDeclaration as ClassDescriptor).thisAsReceiverParameter
|
||||
else null
|
||||
|
||||
internal fun <M : MessageLite, D : CallableDescriptor> deserializeToDescriptor(
|
||||
moduleAnchor: Class<*>,
|
||||
proto: M,
|
||||
|
||||
+20
@@ -17187,6 +17187,21 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/extensionPropertyReceiverToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideEqualsHashCode.kt")
|
||||
public void testFakeOverrideEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideEqualsHashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString.kt")
|
||||
public void testFakeOverrideToString() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString2.kt")
|
||||
public void testFakeOverrideToString2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionEqualsHashCode.kt")
|
||||
public void testFunctionEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/functionEqualsHashCode.kt");
|
||||
@@ -17467,6 +17482,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceExtensionReceiverAndValueParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("instanceParameterOfFakeOverride.kt")
|
||||
public void testInstanceParameterOfFakeOverride() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceParameterOfFakeOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("isMarkedNullable.kt")
|
||||
public void testIsMarkedNullable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/isMarkedNullable.kt");
|
||||
|
||||
+20
@@ -18247,6 +18247,21 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/extensionPropertyReceiverToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideEqualsHashCode.kt")
|
||||
public void testFakeOverrideEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideEqualsHashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString.kt")
|
||||
public void testFakeOverrideToString() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideToString2.kt")
|
||||
public void testFakeOverrideToString2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/fakeOverrideToString2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionEqualsHashCode.kt")
|
||||
public void testFunctionEqualsHashCode() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/methodsFromAny/functionEqualsHashCode.kt");
|
||||
@@ -18527,6 +18542,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceExtensionReceiverAndValueParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("instanceParameterOfFakeOverride.kt")
|
||||
public void testInstanceParameterOfFakeOverride() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/instanceParameterOfFakeOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("isMarkedNullable.kt")
|
||||
public void testIsMarkedNullable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/reflection/parameters/isMarkedNullable.kt");
|
||||
|
||||
Reference in New Issue
Block a user