Reflection: improve and optimize kotlinFunction/kotlinProperty
- Make the implementations very similar, to fix KT-54833 where the companion object case was forgotten for kotlinProperty. - Optimize both functions to look up the function/property by name first, to cover the most probable case when the JVM name of a declaration is equal to its Kotlin name. This fixes KT-55937. #KT-54833 Fixed #KT-55937 Fixed
This commit is contained in:
+18
@@ -43806,6 +43806,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("companionObjectProperty.kt")
|
||||||
|
public void testCompanionObjectProperty() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/companionObjectProperty.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("constructor.kt")
|
@TestMetadata("constructor.kt")
|
||||||
public void testConstructor() throws Exception {
|
public void testConstructor() throws Exception {
|
||||||
@@ -43896,6 +43902,18 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
|||||||
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialFunctionNames.kt")
|
||||||
|
public void testNonTrivialFunctionNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialFunctionNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialPropertyNames.kt")
|
||||||
|
public void testNonTrivialPropertyNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialPropertyNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("openSuspendFun.kt")
|
@TestMetadata("openSuspendFun.kt")
|
||||||
public void testOpenSuspendFun() throws Exception {
|
public void testOpenSuspendFun() throws Exception {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
|
||||||
|
class C {
|
||||||
|
companion object {
|
||||||
|
val x = "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val f = C::class.java.getDeclaredField("x").kotlinProperty as KProperty1<C.Companion, String>
|
||||||
|
return f.get(C.Companion)
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
import kotlin.reflect.jvm.javaMethod
|
||||||
|
import kotlin.reflect.jvm.kotlinFunction
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class A {
|
||||||
|
@JvmName("jvmFoo")
|
||||||
|
fun foo(s: String): Int = s.length
|
||||||
|
|
||||||
|
fun mangled(z: Z): Number = z.value
|
||||||
|
|
||||||
|
internal fun internal(s: String): Int = s.length
|
||||||
|
|
||||||
|
|
||||||
|
// Some different members with similar JVM signatures, to check that kotlinFunction doesn't incorrectly resolve into one of these.
|
||||||
|
private fun jvmFoo(s: String): String = s
|
||||||
|
private fun `mangled-IQRRRT4`(z: Number) {}
|
||||||
|
private fun `internal$main`(s: String): String = s
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Z(val value: Number)
|
||||||
|
|
||||||
|
fun test(f: KFunction<*>) {
|
||||||
|
val javaMethod = f.javaMethod
|
||||||
|
?: error("javaMethod == null for $f")
|
||||||
|
assertEquals(f, javaMethod.kotlinFunction, "Incorrect kotlinFunction for $javaMethod")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
test(A::foo)
|
||||||
|
test(A::mangled)
|
||||||
|
test(A::internal)
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val x = "outer" // NB: backing field of this property has the name `x$1`, to avoid conflict with public field moved from companion.
|
||||||
|
val y = "outer" // Same here, `y$1`.
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val x = "companion"
|
||||||
|
|
||||||
|
const val y = "companion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(f: KProperty<*>) {
|
||||||
|
val javaField = f.javaField
|
||||||
|
?: error("javaField == null for $f")
|
||||||
|
|
||||||
|
assertEquals(f, javaField.kotlinProperty, "Incorrect kotlinProperty for $javaField")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
test(A::x)
|
||||||
|
test(A::y)
|
||||||
|
|
||||||
|
// We have to use reflection API to get companion object properties if we want to test the invariant `p.javaField.kotlinProperty == p`.
|
||||||
|
// If we used the callable reference syntax instead `A.Companion::x`, we'd get a bound property reference, which is an instance of
|
||||||
|
// `KProperty0`. Whereas `kotlinProperty` always return unbound reference, so a `KProperty1`.
|
||||||
|
test(A.Companion::class.declaredMemberProperties.single { it.name == "x" })
|
||||||
|
test(A.Companion::class.declaredMemberProperties.single { it.name == "y" })
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+18
@@ -42012,6 +42012,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("companionObjectProperty.kt")
|
||||||
|
public void testCompanionObjectProperty() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/companionObjectProperty.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("constructor.kt")
|
@TestMetadata("constructor.kt")
|
||||||
public void testConstructor() throws Exception {
|
public void testConstructor() throws Exception {
|
||||||
@@ -42102,6 +42108,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialFunctionNames.kt")
|
||||||
|
public void testNonTrivialFunctionNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialFunctionNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialPropertyNames.kt")
|
||||||
|
public void testNonTrivialPropertyNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialPropertyNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("openSuspendFun.kt")
|
@TestMetadata("openSuspendFun.kt")
|
||||||
public void testOpenSuspendFun() throws Exception {
|
public void testOpenSuspendFun() throws Exception {
|
||||||
|
|||||||
+18
@@ -43806,6 +43806,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("companionObjectProperty.kt")
|
||||||
|
public void testCompanionObjectProperty() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/companionObjectProperty.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("constructor.kt")
|
@TestMetadata("constructor.kt")
|
||||||
public void testConstructor() throws Exception {
|
public void testConstructor() throws Exception {
|
||||||
@@ -43896,6 +43902,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialFunctionNames.kt")
|
||||||
|
public void testNonTrivialFunctionNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialFunctionNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonTrivialPropertyNames.kt")
|
||||||
|
public void testNonTrivialPropertyNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialPropertyNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("openSuspendFun.kt")
|
@TestMetadata("openSuspendFun.kt")
|
||||||
public void testOpenSuspendFun() throws Exception {
|
public void testOpenSuspendFun() throws Exception {
|
||||||
|
|||||||
+15
@@ -33661,6 +33661,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("companionObjectProperty.kt")
|
||||||
|
public void testCompanionObjectProperty() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/companionObjectProperty.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("constructor.kt")
|
@TestMetadata("constructor.kt")
|
||||||
public void testConstructor() throws Exception {
|
public void testConstructor() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/reflection/mapping/constructor.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/constructor.kt");
|
||||||
@@ -33736,6 +33741,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nonTrivialFunctionNames.kt")
|
||||||
|
public void testNonTrivialFunctionNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialFunctionNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nonTrivialPropertyNames.kt")
|
||||||
|
public void testNonTrivialPropertyNames() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/reflection/mapping/nonTrivialPropertyNames.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("openSuspendFun.kt")
|
@TestMetadata("openSuspendFun.kt")
|
||||||
public void testOpenSuspendFun() throws Exception {
|
public void testOpenSuspendFun() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt");
|
runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt");
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import kotlin.reflect.*
|
|||||||
import kotlin.reflect.full.companionObject
|
import kotlin.reflect.full.companionObject
|
||||||
import kotlin.reflect.full.functions
|
import kotlin.reflect.full.functions
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.jvm.internal.*
|
||||||
import kotlin.reflect.jvm.internal.KPackageImpl
|
import kotlin.reflect.jvm.internal.KPackageImpl
|
||||||
import kotlin.reflect.jvm.internal.KTypeImpl
|
import kotlin.reflect.jvm.internal.KTypeImpl
|
||||||
import kotlin.reflect.jvm.internal.asKCallableImpl
|
import kotlin.reflect.jvm.internal.asKCallableImpl
|
||||||
@@ -92,14 +93,22 @@ val Field.kotlinProperty: KProperty<*>?
|
|||||||
get() {
|
get() {
|
||||||
if (isSynthetic) return null
|
if (isSynthetic) return null
|
||||||
|
|
||||||
// TODO: optimize (search by name)
|
if (Modifier.isStatic(modifiers)) {
|
||||||
|
val kotlinPackage = getKPackage()
|
||||||
|
if (kotlinPackage != null) {
|
||||||
|
return kotlinPackage.members.findKProperty(this)
|
||||||
|
}
|
||||||
|
|
||||||
val kotlinPackage = getKPackage()
|
val companionKClass = declaringClass.kotlin.companionObject
|
||||||
if (kotlinPackage != null) {
|
if (companionKClass != null) {
|
||||||
return kotlinPackage.members.filterIsInstance<KProperty<*>>().firstOrNull { it.javaField == this }
|
val companionField = declaringClass.getDeclaredFieldOrNull(name)
|
||||||
|
if (companionField != null) {
|
||||||
|
companionKClass.memberProperties.findKProperty(companionField)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return declaringClass.kotlin.memberProperties.firstOrNull { it.javaField == this }
|
return declaringClass.kotlin.memberProperties.findKProperty(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -119,23 +128,45 @@ val Method.kotlinFunction: KFunction<*>?
|
|||||||
if (Modifier.isStatic(modifiers)) {
|
if (Modifier.isStatic(modifiers)) {
|
||||||
val kotlinPackage = getKPackage()
|
val kotlinPackage = getKPackage()
|
||||||
if (kotlinPackage != null) {
|
if (kotlinPackage != null) {
|
||||||
return kotlinPackage.members.filterIsInstance<KFunction<*>>().firstOrNull { it.javaMethod == this }
|
return kotlinPackage.members.findKFunction(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For static bridge method generated for a @JvmStatic function in the companion object, also try to find the latter
|
// For static bridge method generated for a @JvmStatic function in the companion object, also try to find the latter
|
||||||
val companion = declaringClass.kotlin.companionObject
|
val companionKClass = declaringClass.kotlin.companionObject
|
||||||
if (companion != null) {
|
if (companionKClass != null) {
|
||||||
companion.functions.firstOrNull {
|
val companionMethod = companionKClass.java.getDeclaredMethodOrNull(name, *parameterTypes)
|
||||||
val m = it.javaMethod
|
if (companionMethod != null) {
|
||||||
m != null && m.name == this.name &&
|
companionKClass.functions.findKFunction(companionMethod)?.let { return it }
|
||||||
m.parameterTypes.contentEquals(this.parameterTypes) && m.returnType == this.returnType
|
}
|
||||||
}?.let { return it }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return declaringClass.kotlin.functions.firstOrNull { it.javaMethod == this }
|
return declaringClass.kotlin.functions.findKFunction(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Collection<KCallable<*>>.findKFunction(method: Method): KFunction<*>? {
|
||||||
|
// As an optimization, try to search among functions with the same name first, and then among the rest of functions.
|
||||||
|
// This is needed because a function's JVM name might be different from its Kotlin name (because of `@JvmName`, inline class mangling,
|
||||||
|
// internal visibility, etc).
|
||||||
|
for (callable in this) {
|
||||||
|
if (callable is KFunction<*> && callable.name == method.name && callable.javaMethod == method) return callable
|
||||||
|
}
|
||||||
|
for (callable in this) {
|
||||||
|
if (callable is KFunction<*> && callable.name != method.name && callable.javaMethod == method) return callable
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Collection<KCallable<*>>.findKProperty(field: Field): KProperty<*>? {
|
||||||
|
for (callable in this) {
|
||||||
|
if (callable is KProperty<*> && callable.name == field.name && callable.javaField == field) return callable
|
||||||
|
}
|
||||||
|
for (callable in this) {
|
||||||
|
if (callable is KProperty<*> && callable.name != field.name && callable.javaField == field) return callable
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [KFunction] instance corresponding to the given Java [Constructor] instance,
|
* Returns a [KFunction] instance corresponding to the given Java [Constructor] instance,
|
||||||
* or `null` if this constructor cannot be represented by a Kotlin function
|
* or `null` if this constructor cannot be represented by a Kotlin function
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
|||||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
|
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
|
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import kotlin.jvm.internal.FunctionReference
|
import kotlin.jvm.internal.FunctionReference
|
||||||
import kotlin.jvm.internal.PropertyReference
|
import kotlin.jvm.internal.PropertyReference
|
||||||
@@ -308,3 +310,17 @@ internal open class CreateKCallableVisitor(private val container: KDeclarationCo
|
|||||||
override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, data: Unit): KCallableImpl<*> =
|
override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, data: Unit): KCallableImpl<*> =
|
||||||
KFunctionImpl(container, descriptor)
|
KFunctionImpl(container, descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Class<*>.getDeclaredMethodOrNull(name: String, vararg parameterTypes: Class<*>): Method? =
|
||||||
|
try {
|
||||||
|
getDeclaredMethod(name, *parameterTypes)
|
||||||
|
} catch (e: NoSuchMethodException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Class<*>.getDeclaredFieldOrNull(name: String): Field? =
|
||||||
|
try {
|
||||||
|
getDeclaredField(name)
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user