Support reflection calls to multifile class members

#KT-11447 Fixed
This commit is contained in:
Alexander Udalov
2016-03-22 15:21:09 +03:00
parent 9fa101b3fe
commit 6924d883eb
6 changed files with 171 additions and 4 deletions
@@ -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
@@ -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
@@ -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<*>>