Support reflection calls to multifile class members
#KT-11447 Fixed
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// FILE: Test1.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun getX() = 1
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
assertEquals("getX", ::getX.name)
|
||||||
|
assertEquals("getY", ::getY.name)
|
||||||
|
assertEquals("getZ", ::getZ.name)
|
||||||
|
|
||||||
|
assertEquals(1, ::getX.call())
|
||||||
|
assertEquals(239, ::getY.call())
|
||||||
|
assertEquals(42, ::getZ.callBy(emptyMap()))
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: Test2.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
fun getY() = 239
|
||||||
|
|
||||||
|
fun getZ(value: Int = 42) = value
|
||||||
Vendored
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// KT-11447 Multifile declaration causes IAE: Method can not access a member of class
|
||||||
|
// WITH_REFLECT
|
||||||
|
// FILE: Test1.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
var x = 1
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
assertEquals("x", ::x.name)
|
||||||
|
assertEquals("y", ::y.name)
|
||||||
|
assertEquals("MAGIC_NUMBER", ::MAGIC_NUMBER.name)
|
||||||
|
|
||||||
|
assertEquals(1, ::x.call())
|
||||||
|
assertEquals(1, ::x.getter.call())
|
||||||
|
|
||||||
|
assertEquals(239, ::y.call())
|
||||||
|
assertEquals(239, ::y.getter.call())
|
||||||
|
|
||||||
|
assertEquals(42, ::MAGIC_NUMBER.call())
|
||||||
|
assertEquals(42, ::MAGIC_NUMBER.getter.call())
|
||||||
|
|
||||||
|
assertEquals(Unit, ::x.setter.call(2))
|
||||||
|
assertEquals(2, ::x.call())
|
||||||
|
assertEquals(2, ::x.getter.call())
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: Test2.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
val y = 239
|
||||||
|
|
||||||
|
const val MAGIC_NUMBER = 42
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// FULL_JDK
|
||||||
|
// FILE: 1.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlin.reflect.jvm.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun testX() {
|
||||||
|
val field = ::x.javaField ?: throw AssertionError("No java field for ${::x.name}")
|
||||||
|
|
||||||
|
try {
|
||||||
|
field.get(null)
|
||||||
|
throw AssertionError("Fail: field.get should fail because the field is private")
|
||||||
|
}
|
||||||
|
catch (e: IllegalAccessException) {
|
||||||
|
// OK
|
||||||
|
}
|
||||||
|
|
||||||
|
field.setAccessible(true)
|
||||||
|
assertEquals("I am x", field.get(null))
|
||||||
|
field.set(null, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testY() {
|
||||||
|
val field = ::y.javaField ?: throw AssertionError("No java field for ${::y.name}")
|
||||||
|
|
||||||
|
assertEquals("I am const y", field.get(null))
|
||||||
|
|
||||||
|
// Accessible = false should have no effect because the field is public
|
||||||
|
field.setAccessible(false)
|
||||||
|
|
||||||
|
assertEquals("I am const y", field.get(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
testX()
|
||||||
|
testY()
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: 2.kt
|
||||||
|
|
||||||
|
@file:kotlin.jvm.JvmName("Test")
|
||||||
|
@file:kotlin.jvm.JvmMultifileClass
|
||||||
|
package test
|
||||||
|
|
||||||
|
var x = "I am x"
|
||||||
|
const val y = "I am const y"
|
||||||
@@ -11485,6 +11485,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/box/reflection/multifileClasses")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class MultifileClasses extends AbstractBlackBoxCodegenTest {
|
||||||
|
public void testAllFilesPresentInMultifileClasses() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("callFunctionsInMultifileClass.kt")
|
||||||
|
public void testCallFunctionsInMultifileClass() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/multifileClasses/callFunctionsInMultifileClass.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("callPropertiesInMultifileClass.kt")
|
||||||
|
public void testCallPropertiesInMultifileClass() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/multifileClasses/callPropertiesInMultifileClass.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("javaFieldForVarAndConstVal.kt")
|
||||||
|
public void testJavaFieldForVarAndConstVal() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/multifileClasses/javaFieldForVarAndConstVal.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/box/reflection/noReflectAtRuntime")
|
@TestMetadata("compiler/testData/codegen/box/reflection/noReflectAtRuntime")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
val moduleData: RuntimeModuleData
|
val moduleData: RuntimeModuleData
|
||||||
get() = moduleData_()
|
get() = moduleData_()
|
||||||
|
|
||||||
|
protected open val methodOwner: Class<*>
|
||||||
|
get() = jClass
|
||||||
|
|
||||||
abstract val constructorDescriptors: Collection<ConstructorDescriptor>
|
abstract val constructorDescriptors: Collection<ConstructorDescriptor>
|
||||||
|
|
||||||
abstract fun getProperties(name: Name): Collection<PropertyDescriptor>
|
abstract fun getProperties(name: Name): Collection<PropertyDescriptor>
|
||||||
@@ -175,9 +178,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? {
|
fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? {
|
||||||
if (name == "<init>") return null
|
if (name == "<init>") return null
|
||||||
|
|
||||||
// Method for a top level function should be the one from the package facade.
|
return methodOwner.tryGetMethod(name, loadParameterTypes(desc), declared)
|
||||||
// This is likely to change after the package part reform.
|
|
||||||
return jClass.tryGetMethod(name, loadParameterTypes(desc), declared)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
|
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
|
||||||
@@ -189,7 +190,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
}
|
}
|
||||||
addParametersAndMasks(parameterTypes, desc, false)
|
addParametersAndMasks(parameterTypes, desc, false)
|
||||||
|
|
||||||
return jClass.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, declared)
|
return methodOwner.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, declared)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? {
|
fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? {
|
||||||
|
|||||||
@@ -38,6 +38,19 @@ internal class KPackageImpl(override val jClass: Class<*>, val moduleName: Strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val methodOwner: Class<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
|
val facadeName = ReflectKotlinClass.create(jClass)?.classHeader?.multifileClassName
|
||||||
|
// We need to check isNotEmpty because this is the value read from the annotation which cannot be null.
|
||||||
|
// The default value for 'xs' is empty string, as declared in kotlin.Metadata
|
||||||
|
// TODO: do not read ReflectKotlinClass multiple times, obtain facade name from descriptor
|
||||||
|
if (facadeName != null && facadeName.isNotEmpty()) {
|
||||||
|
jClass.classLoader.loadClass(facadeName.replace('/', '.'))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal val scope: MemberScope get() = descriptor().memberScope
|
internal val scope: MemberScope get() = descriptor().memberScope
|
||||||
|
|
||||||
override val members: Collection<KCallable<*>>
|
override val members: Collection<KCallable<*>>
|
||||||
|
|||||||
Reference in New Issue
Block a user