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")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -39,6 +39,9 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
||||
val moduleData: RuntimeModuleData
|
||||
get() = moduleData_()
|
||||
|
||||
protected open val methodOwner: Class<*>
|
||||
get() = jClass
|
||||
|
||||
abstract val constructorDescriptors: Collection<ConstructorDescriptor>
|
||||
|
||||
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? {
|
||||
if (name == "<init>") return null
|
||||
|
||||
// Method for a top level function should be the one from the package facade.
|
||||
// This is likely to change after the package part reform.
|
||||
return jClass.tryGetMethod(name, loadParameterTypes(desc), declared)
|
||||
return methodOwner.tryGetMethod(name, loadParameterTypes(desc), declared)
|
||||
}
|
||||
|
||||
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
|
||||
@@ -189,7 +190,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
||||
}
|
||||
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<*>? {
|
||||
|
||||
@@ -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
|
||||
|
||||
override val members: Collection<KCallable<*>>
|
||||
|
||||
Reference in New Issue
Block a user