From 3a5de13dd4c894f1da4209c90e0eaced9793b327 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 28 Aug 2018 19:09:19 +0200 Subject: [PATCH] Support inline classes in function signatures in call/callBy #KT-25664 Fixed #KT-26748 Open #KT-26765 Open --- .../call/inlineClasses/fieldAccessors.kt | 37 +++++ .../inlineClasses/functionsAndConstructors.kt | 41 ++++++ .../inlineClasses/jvmStaticFieldInObject.kt | 33 +++++ .../call/inlineClasses/jvmStaticFunction.kt | 35 +++++ .../call/inlineClasses/properties.kt | 51 +++++++ .../inlineClassFunctionsAndConstructors.kt | 43 ++++++ .../mapping/types/inlineClassInSignature.kt | 27 ++++ .../codegen/BlackBoxCodegenTestGenerated.java | 48 ++++++ .../LightAnalysisModeTestGenerated.java | 48 ++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 48 ++++++ .../reflect/jvm/internal/KFunctionImpl.kt | 4 +- .../reflect/jvm/internal/KPropertyImpl.kt | 5 +- .../reflect/jvm/internal/calls/Caller.kt | 5 + .../reflect/jvm/internal/calls/CallerImpl.kt | 19 ++- .../internal/calls/InlineClassAwareCaller.kt | 138 ++++++++++++++++++ .../IrJsCodegenBoxTestGenerated.java | 48 ++++++ .../semantics/JsCodegenBoxTestGenerated.java | 48 ++++++ 17 files changed, 666 insertions(+), 12 deletions(-) create mode 100644 compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt create mode 100644 compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt create mode 100644 compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt create mode 100644 compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt create mode 100644 compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt create mode 100644 compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt create mode 100644 compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/InlineClassAwareCaller.kt diff --git a/compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt b/compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt new file mode 100644 index 00000000000..09cbe47c115 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt @@ -0,0 +1,37 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// WITH_REFLECT + +import kotlin.reflect.KCallable +import kotlin.reflect.jvm.isAccessible +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +class C { + private var member: S = S("") + + fun unboundRef() = C::member.apply { isAccessible = true } + fun boundRef() = this::member.apply { isAccessible = true } +} + +private var topLevel: S = S("") + +fun box(): String { + val c = C() + assertEquals(Unit, c.unboundRef().setter.call(c, S("ab"))) + assertEquals(S("ab"), c.unboundRef().call(c)) + assertEquals(S("ab"), c.unboundRef().getter.call(c)) + + assertEquals(Unit, c.boundRef().setter.call(S("cd"))) + assertEquals(S("cd"), c.boundRef().call()) + assertEquals(S("cd"), c.boundRef().getter.call()) + + val topLevel = ::topLevel.apply { isAccessible = true } + assertEquals(Unit, topLevel.setter.call(S("ef"))) + assertEquals(S("ef"), topLevel.call()) + assertEquals(S("ef"), topLevel.getter.call()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt b/compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt new file mode 100644 index 00000000000..cf9338b71a4 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt @@ -0,0 +1,41 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// WITH_REFLECT + +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +class C { + fun member(x: S, y: String): S = x + S(y) +} + +fun topLevel(x: String, y: S): S = S(x) + y + +/* TODO: support constructors with inline class types in the signature (KT-26765) +class D { + inner class Inner(x: S, y: S) { + val result = x + y + } +} +*/ + +fun S.extension(y: S): S = this + y + +fun S.extension2(): String = value + +fun box(): String { + assertEquals(S("ab"), C::member.call(C(), S("a"), "b")) + assertEquals(S("cd"), ::topLevel.call("c", S("d"))) + // assertEquals(S("ef"), D::Inner.call(D(), S("e"), S("f")).result) + assertEquals(S("gh"), S::extension.call(S("g"), S("h"))) + assertEquals("_", S::extension2.call(S("_"))) + + assertEquals(S("ij"), C()::member.call(S("i"), "j")) + // assertEquals(S("kl"), D()::Inner.call(S("k"), S("l")).result) + assertEquals(S("mn"), S("m")::extension.call(S("n"))) + assertEquals("_", S("_")::extension2.call()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt b/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt new file mode 100644 index 00000000000..b95a7acd2a3 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt @@ -0,0 +1,33 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// JVM_TARGET: 1.8 +// WITH_REFLECT + +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.jvm.isAccessible +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +object C { + @JvmStatic + private var p: S = S("") + + fun boundRef() = this::p.apply { isAccessible = true } +} + +fun box(): String { + val unboundRef = C::class.members.single { it.name == "p" } as KMutableProperty1 + unboundRef.isAccessible = true + assertEquals(Unit, unboundRef.setter.call(C, S("ab"))) + assertEquals(S("ab"), unboundRef.call(C)) + assertEquals(S("ab"), unboundRef.getter.call(C)) + + val boundRef = C.boundRef() + assertEquals(Unit, boundRef.setter.call(S("cd"))) + assertEquals(S("cd"), boundRef.call()) + assertEquals(S("cd"), boundRef.getter.call()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt b/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt new file mode 100644 index 00000000000..7f266ae3774 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt @@ -0,0 +1,35 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// JVM_TARGET: 1.8 +// WITH_REFLECT + +import kotlin.reflect.KFunction +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +object C { + @JvmStatic + fun foo(x: S, y: String): S = x + S(y) +} + +interface I { + companion object { + @JvmStatic + fun bar(x: String, y: S): S = S(x) + y + } +} + +fun box(): String { + assertEquals(S("ab"), C::foo.call(S("a"), "b")) + assertEquals(S("cd"), (I)::bar.call("c", S("d"))) + + val unboundFoo = C::class.members.single { it.name == "foo" } as KFunction<*> + assertEquals(S("ef"), unboundFoo.call(C, S("e"), "f")) + + val unboundBar = I.Companion::class.members.single { it.name == "bar" } as KFunction<*> + assertEquals(S("gh"), unboundBar.call(I, "g", S("h"))) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt b/compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt new file mode 100644 index 00000000000..c0fe29d549e --- /dev/null +++ b/compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt @@ -0,0 +1,51 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// WITH_REFLECT + +import kotlin.reflect.KMutableProperty2 +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +class C { + var member: S = S("") + + private var suffix = S("") + var S.memExt: S + get() = this + suffix + set(value) { suffix = this + value } +} + +var topLevel: S = S("") + +private var suffix = S("") +var S.ext: S + get() = this + suffix + set(value) { suffix = this + value } + +fun box(): String { + val c = C() + assertEquals(Unit, C::member.setter.call(c, S("ab"))) + assertEquals(S("ab"), C::member.call(c)) + assertEquals(S("ab"), C::member.getter.call(c)) + + assertEquals(Unit, c::member.setter.call(S("cd"))) + assertEquals(S("cd"), c::member.call()) + assertEquals(S("cd"), c::member.getter.call()) + + val memExt = C::class.members.single { it.name == "memExt" } as KMutableProperty2 + assertEquals(Unit, memExt.setter.call(c, S(""), S("f"))) + assertEquals(S("ef"), memExt.call(c, S("e"))) + assertEquals(S("ef"), memExt.getter.call(c, S("e"))) + + assertEquals(Unit, ::topLevel.setter.call(S("gh"))) + assertEquals(S("gh"), ::topLevel.call()) + assertEquals(S("gh"), ::topLevel.getter.call()) + + assertEquals(Unit, S::ext.setter.call(S(""), S("j"))) + assertEquals(S("ij"), S::ext.call(S("i"))) + assertEquals(S("ij"), S::ext.getter.call(S("i"))) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt b/compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt new file mode 100644 index 00000000000..3f42e49d63a --- /dev/null +++ b/compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt @@ -0,0 +1,43 @@ +// IGNORE_BACKEND: JS_IR, JS, NATIVE, JVM_IR +// WITH_REFLECT + +import kotlin.test.assertEquals + +inline class S(val value: String) { + operator fun plus(other: S): S = S(this.value + other.value) +} + +class C { + fun member(a: S, b: S = S("b")): S = a + b +} + +fun topLevel(c: S, d: S = S("d")): S = c + d + +/* TODO: support constructors with inline class types in the signature (KT-26765) +class D(e: S, f: S = S("f")) { + val result = e + f +} +*/ + +fun S.extension(h: S = S("h")): S = this + h + +fun box(): String { + assertEquals(S("ab"), C::member.callBy(C::member.parameters.filter { it.name != "b" }.associate { + it to (if (it.name == "a") S("a") else C()) + })) + + assertEquals(S("cd"), ::topLevel.callBy(::topLevel.parameters.filter { it.name != "d" }.associate { it to S("c") })) + + // assertEquals(S("ef"), ::D.callBy(::D.parameters.filter { it.name != "f" }.associate { it to S("e") }).result) + + assertEquals(S("gh"), S::extension.callBy(S::extension.parameters.filter { it.name != "h" }.associate { it to S("g") })) + + + val boundMember = C()::member + assertEquals(S("ab"), boundMember.callBy(boundMember.parameters.associate { it to S(it.name!!) })) + + val boundExtension = S("g")::extension + assertEquals(S("gh"), boundExtension.callBy(boundExtension.parameters.associate { it to S(it.name!!) })) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt b/compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt new file mode 100644 index 00000000000..4479a8e7542 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt @@ -0,0 +1,27 @@ +// IGNORE_BACKEND: JVM_IR, JS_IR, JS, NATIVE +// WITH_REFLECT + +import kotlin.reflect.jvm.* +import kotlin.test.assertEquals + +inline class S(val value: String) + +fun S.foo(x: Int, s: S): S = this + +/* TODO: Support calling members of inline classes in reflection (KT-26748) +inline class T(val s: S) { + fun bar(u: S): T = this +} +*/ + +fun box(): String { + assertEquals(listOf(String::class.java, Int::class.java, String::class.java), S::foo.parameters.map { it.type.javaType }) + assertEquals(S::class.java, S::foo.returnType.javaType) + +/* + assertEquals(listOf(), T::bar.parameters.map { it.type.javaType }) + assertEquals(String::class.java, T::bar.returnType.javaType) +*/ + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 869ba630316..cfdedbd1ca1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -17927,6 +17927,44 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/call/bound/objectPropertyAccessors.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/call/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/call/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("fieldAccessors.kt") + public void testFieldAccessors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt"); + } + + @TestMetadata("functionsAndConstructors.kt") + public void testFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt"); + } + + @TestMetadata("jvmStaticFieldInObject.kt") + public void testJvmStaticFieldInObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt"); + } + + @TestMetadata("jvmStaticFunction.kt") + public void testJvmStaticFunction() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/callBy") @@ -17971,6 +18009,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/callBy/extensionFunction.kt"); } + @TestMetadata("inlineClassFunctionsAndConstructors.kt") + public void testInlineClassFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt"); + } + @TestMetadata("jvmStaticInCompanionObject.kt") public void testJvmStaticInCompanionObject() throws Exception { runTest("compiler/testData/codegen/box/reflection/callBy/jvmStaticInCompanionObject.kt"); @@ -18852,6 +18895,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt"); } + @TestMetadata("inlineClassInSignature.kt") + public void testInlineClassInSignature() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt"); + } + @TestMetadata("innerGenericTypeArgument.kt") public void testInnerGenericTypeArgument() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/types/innerGenericTypeArgument.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 9a8d02abe52..6acf53f10d9 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -17927,6 +17927,44 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/call/bound/objectPropertyAccessors.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/call/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/call/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("fieldAccessors.kt") + public void testFieldAccessors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt"); + } + + @TestMetadata("functionsAndConstructors.kt") + public void testFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt"); + } + + @TestMetadata("jvmStaticFieldInObject.kt") + public void testJvmStaticFieldInObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt"); + } + + @TestMetadata("jvmStaticFunction.kt") + public void testJvmStaticFunction() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/callBy") @@ -17971,6 +18009,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/callBy/extensionFunction.kt"); } + @TestMetadata("inlineClassFunctionsAndConstructors.kt") + public void testInlineClassFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt"); + } + @TestMetadata("jvmStaticInCompanionObject.kt") public void testJvmStaticInCompanionObject() throws Exception { runTest("compiler/testData/codegen/box/reflection/callBy/jvmStaticInCompanionObject.kt"); @@ -18852,6 +18895,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt"); } + @TestMetadata("inlineClassInSignature.kt") + public void testInlineClassInSignature() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt"); + } + @TestMetadata("innerGenericTypeArgument.kt") public void testInnerGenericTypeArgument() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/types/innerGenericTypeArgument.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 958f744b261..04d247b5c87 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -17927,6 +17927,44 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/call/bound/objectPropertyAccessors.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/call/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/call/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); + } + + @TestMetadata("fieldAccessors.kt") + public void testFieldAccessors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt"); + } + + @TestMetadata("functionsAndConstructors.kt") + public void testFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt"); + } + + @TestMetadata("jvmStaticFieldInObject.kt") + public void testJvmStaticFieldInObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt"); + } + + @TestMetadata("jvmStaticFunction.kt") + public void testJvmStaticFunction() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/callBy") @@ -17971,6 +18009,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/callBy/extensionFunction.kt"); } + @TestMetadata("inlineClassFunctionsAndConstructors.kt") + public void testInlineClassFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt"); + } + @TestMetadata("jvmStaticInCompanionObject.kt") public void testJvmStaticInCompanionObject() throws Exception { runTest("compiler/testData/codegen/box/reflection/callBy/jvmStaticInCompanionObject.kt"); @@ -18852,6 +18895,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt"); } + @TestMetadata("inlineClassInSignature.kt") + public void testInlineClassInSignature() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt"); + } + @TestMetadata("innerGenericTypeArgument.kt") public void testInnerGenericTypeArgument() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/types/innerGenericTypeArgument.kt"); diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt index cdbeec1fa29..89d3c4d91c9 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt @@ -87,7 +87,7 @@ internal class KFunctionImpl private constructor( createStaticMethodCaller(member) } else -> throw KotlinReflectionInternalError("Could not compute caller for function: $descriptor (member = $member)") - } + }.createInlineClassAwareCallerIfNeeded(descriptor) } override val defaultCaller: Caller<*>? by ReflectProperties.lazySoft defaultCaller@{ @@ -129,7 +129,7 @@ internal class KFunctionImpl private constructor( createStaticMethodCaller(member) } else -> null - } + }?.createInlineClassAwareCallerIfNeeded(descriptor, isDefault = true) } private fun createStaticMethodCaller(member: Method) = diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt index fa8cdb4e317..774d26a20b8 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt @@ -24,6 +24,7 @@ import kotlin.reflect.jvm.internal.JvmPropertySignature.* import kotlin.reflect.jvm.internal.calls.Caller import kotlin.reflect.jvm.internal.calls.CallerImpl import kotlin.reflect.jvm.internal.calls.ThrowingCaller +import kotlin.reflect.jvm.internal.calls.createInlineClassAwareCallerIfNeeded internal abstract class KPropertyImpl private constructor( override val container: KDeclarationContainerImpl, @@ -185,7 +186,7 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool fun isNotNullProperty(): Boolean = !TypeUtils.isNullableType(property.descriptor.type) - fun computeFieldCaller(field: Field): Caller = when { + fun computeFieldCaller(field: Field): CallerImpl = when { property.descriptor.isJvmFieldPropertyInCompanionObject() || !Modifier.isStatic(field.modifiers) -> if (isGetter) if (isBound) CallerImpl.FieldGetter.BoundInstance(field, property.boundReceiver) @@ -263,7 +264,7 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool return if (isBound) CallerImpl.Method.BoundInstance(accessor, property.boundReceiver) else CallerImpl.Method.Instance(accessor) } - } + }.createInlineClassAwareCallerIfNeeded(descriptor) } private fun PropertyDescriptor.isJvmFieldPropertyInCompanionObject(): Boolean { diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/Caller.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/Caller.kt index 56eeeb3e756..3ff18d328e5 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/Caller.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/Caller.kt @@ -26,3 +26,8 @@ internal interface Caller { internal val Caller<*>.arity: Int get() = parameterTypes.size + +/** + * A marker interface that signifies that this caller has a "bound receiver" object which should be used as the dispatch receiver instance. + */ +interface BoundCaller diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/CallerImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/CallerImpl.kt index e7953f8cc8e..6865b62aaaa 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/CallerImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/CallerImpl.kt @@ -44,7 +44,7 @@ internal sealed class CallerImpl( // TODO fix 'callBy' for bound (and non-bound) inner class constructor references // See https://youtrack.jetbrains.com/issue/KT-14990 - class BoundConstructor(constructor: ReflectConstructor<*>, private val boundReceiver: Any?) : + class BoundConstructor(constructor: ReflectConstructor<*>, private val boundReceiver: Any?) : BoundCaller, CallerImpl>( constructor, constructor.declaringClass, null, constructor.genericParameterTypes @@ -96,7 +96,7 @@ internal sealed class CallerImpl( } } - class BoundStatic(method: ReflectMethod, private val boundReceiver: Any?) : Method( + class BoundStatic(method: ReflectMethod, private val boundReceiver: Any?) : BoundCaller, Method( method, requiresInstance = false, parameterTypes = method.genericParameterTypes.dropFirst() ) { override fun call(args: Array<*>): Any? { @@ -105,14 +105,15 @@ internal sealed class CallerImpl( } } - class BoundInstance(method: ReflectMethod, private val boundReceiver: Any?) : Method(method, requiresInstance = false) { + class BoundInstance(method: ReflectMethod, private val boundReceiver: Any?) : BoundCaller, + Method(method, requiresInstance = false) { override fun call(args: Array<*>): Any? { checkArguments(args) return callMethod(boundReceiver, args) } } - class BoundJvmStaticInObject(method: ReflectMethod) : Method(method, requiresInstance = false) { + class BoundJvmStaticInObject(method: ReflectMethod) : BoundCaller, Method(method, requiresInstance = false) { override fun call(args: Array<*>): Any? { checkArguments(args) return callMethod(null, args) @@ -145,14 +146,15 @@ internal sealed class CallerImpl( } } - class BoundInstance(field: ReflectField, private val boundReceiver: Any?) : FieldGetter(field, requiresInstance = false) { + class BoundInstance(field: ReflectField, private val boundReceiver: Any?) : BoundCaller, + FieldGetter(field, requiresInstance = false) { override fun call(args: Array<*>): Any? { checkArguments(args) return member.get(boundReceiver) } } - class BoundJvmStaticInObject(field: ReflectField) : FieldGetter(field, requiresInstance = false) + class BoundJvmStaticInObject(field: ReflectField) : BoundCaller, FieldGetter(field, requiresInstance = false) } sealed class FieldSetter( @@ -188,7 +190,7 @@ internal sealed class CallerImpl( } } - class BoundInstance(field: ReflectField, notNull: Boolean, private val boundReceiver: Any?) : + class BoundInstance(field: ReflectField, notNull: Boolean, private val boundReceiver: Any?) : BoundCaller, FieldSetter(field, notNull, requiresInstance = false) { override fun call(args: Array<*>): Any? { checkArguments(args) @@ -196,7 +198,8 @@ internal sealed class CallerImpl( } } - class BoundJvmStaticInObject(field: ReflectField, notNull: Boolean) : FieldSetter(field, notNull, requiresInstance = false) { + class BoundJvmStaticInObject(field: ReflectField, notNull: Boolean) : BoundCaller, + FieldSetter(field, notNull, requiresInstance = false) { override fun call(args: Array<*>): Any? { checkArguments(args) return member.set(null, args.last()) diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/InlineClassAwareCaller.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/InlineClassAwareCaller.kt new file mode 100644 index 00000000000..550689af156 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/calls/InlineClassAwareCaller.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package kotlin.reflect.jvm.internal.calls + +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.isInlineClassType +import org.jetbrains.kotlin.types.KotlinType +import java.lang.reflect.Member +import java.lang.reflect.Method +import java.lang.reflect.Type +import kotlin.reflect.jvm.internal.KotlinReflectionInternalError +import kotlin.reflect.jvm.internal.toJavaClass + +/** + * A caller that is used whenever the declaration has inline classes in its parameter types or return type. + * Each argument of an inline class type is unboxed, and the return value (if it's of an inline class type) is boxed. + */ +internal class InlineClassAwareCaller( + private val descriptor: CallableMemberDescriptor, + private val caller: CallerImpl, + private val isDefault: Boolean +) : Caller { + override val member: M + get() = caller.member + + override val returnType: Type + get() = caller.returnType + + override val parameterTypes: List + get() = caller.parameterTypes + + private class BoxUnboxData(val argumentRange: IntRange, val unbox: Array, val box: Method?) { + operator fun component1(): IntRange = argumentRange + operator fun component2(): Array = unbox + operator fun component3(): Method? = box + } + + private val data: BoxUnboxData by lazy(LazyThreadSafetyMode.PUBLICATION) { + val shift = when { + caller is CallerImpl.Method.BoundStatic -> { + // Bound reference to a static method is only possible for a top level extension function/property, + // and in that case the number of expected arguments is one less than usual, hence -1 + -1 + } + descriptor.dispatchReceiverParameter != null && caller !is BoundCaller -> 1 + else -> 0 + } + + val extraArgumentsTail = if (isDefault) 2 else 0 + + val kotlinParameterTypes = + listOfNotNull(descriptor.extensionReceiverParameter?.type) + + descriptor.valueParameters.map(ValueParameterDescriptor::getType) + val expectedArgsSize = kotlinParameterTypes.size + shift + extraArgumentsTail + if (arity != expectedArgsSize) { + throw KotlinReflectionInternalError( + "Inconsistent number of parameters in the descriptor and Java reflection object: $arity != $expectedArgsSize\n" + + "Calling: $descriptor\n" + + "Parameter types: ${this.parameterTypes})\n" + + "Default: $isDefault" + ) + } + + // maxOf is needed because in case of a bound top level extension, shift can be -1 (see above). But in that case, we need not unbox + // the extension receiver argument, since it has already been unboxed at compile time and generated into the reference + val argumentRange = maxOf(shift, 0) until (kotlinParameterTypes.size + shift) + + val unbox = Array(expectedArgsSize) { i -> + if (i in argumentRange) { + kotlinParameterTypes[i - shift].toInlineClass()?.getUnboxMethod() + } else null + } + + val box = descriptor.returnType!!.toInlineClass()?.getBoxMethod() + + BoxUnboxData(argumentRange, unbox, box) + } + + override fun call(args: Array<*>): Any? { + val (range, unbox, box) = data + + @Suppress("UNCHECKED_CAST") + val unboxed = args.copyOf() as Array + for (index in range) { + val method = unbox[index] + val arg = args[index] + // Note that arg may be null in case we're calling a $default method and it's an optional parameter of an inline class type + unboxed[index] = + if (method != null && arg != null) method.invoke(arg) + else arg + } + + val result = caller.call(unboxed) + + return box?.invoke(null, result) ?: result + } + + private fun Class<*>.getBoxMethod(): Method = try { + getDeclaredMethod("box" + JvmAbi.IMPL_SUFFIX_FOR_INLINE_CLASS_MEMBERS, getUnboxMethod().returnType) + } catch (e: NoSuchMethodException) { + throw KotlinReflectionInternalError("No box method found in inline class: $this (calling $descriptor)") + } + + private fun Class<*>.getUnboxMethod(): Method = try { + getDeclaredMethod("unbox" + JvmAbi.IMPL_SUFFIX_FOR_INLINE_CLASS_MEMBERS) + } catch (e: NoSuchMethodException) { + throw KotlinReflectionInternalError("No unbox method found in inline class: $this (calling $descriptor)") + } + + private fun KotlinType.toInlineClass(): Class<*>? { + val descriptor = constructor.declarationDescriptor + if (descriptor is ClassDescriptor && descriptor.isInline) { + return descriptor.toJavaClass() ?: throw KotlinReflectionInternalError( + "Class object for the class ${descriptor.name} cannot be found (classId=${descriptor.classId})" + ) + } + + return null + } +} + +internal fun CallerImpl.createInlineClassAwareCallerIfNeeded( + descriptor: CallableMemberDescriptor, + isDefault: Boolean = false +): Caller { + val needsInlineAwareCaller = + descriptor.valueParameters.any { it.type.isInlineClassType() } || + descriptor.returnType?.isInlineClassType() == true || + (this !is BoundCaller && descriptor.extensionReceiverParameter?.type?.isInlineClassType() == true) + return if (needsInlineAwareCaller) InlineClassAwareCaller(descriptor, this, isDefault) else this +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java index 37664095d4e..e6cfb73d411 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java @@ -16057,6 +16057,44 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/call/bound/objectPropertyAccessors.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/call/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractIrJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/call/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("fieldAccessors.kt") + public void testFieldAccessors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt"); + } + + @TestMetadata("functionsAndConstructors.kt") + public void testFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt"); + } + + @TestMetadata("jvmStaticFieldInObject.kt") + public void testJvmStaticFieldInObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt"); + } + + @TestMetadata("jvmStaticFunction.kt") + public void testJvmStaticFunction() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/callBy") @@ -16101,6 +16139,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/callBy/extensionFunction.kt"); } + @TestMetadata("inlineClassFunctionsAndConstructors.kt") + public void testInlineClassFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt"); + } + @TestMetadata("jvmStaticInCompanionObject.kt") public void testJvmStaticInCompanionObject() throws Exception { runTest("compiler/testData/codegen/box/reflection/callBy/jvmStaticInCompanionObject.kt"); @@ -16957,6 +17000,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt"); } + @TestMetadata("inlineClassInSignature.kt") + public void testInlineClassInSignature() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt"); + } + @TestMetadata("innerGenericTypeArgument.kt") public void testInnerGenericTypeArgument() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/types/innerGenericTypeArgument.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index df4025f08b8..70bea159e63 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -17122,6 +17122,44 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/call/bound/objectPropertyAccessors.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/call/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/call/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("fieldAccessors.kt") + public void testFieldAccessors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/fieldAccessors.kt"); + } + + @TestMetadata("functionsAndConstructors.kt") + public void testFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/functionsAndConstructors.kt"); + } + + @TestMetadata("jvmStaticFieldInObject.kt") + public void testJvmStaticFieldInObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFieldInObject.kt"); + } + + @TestMetadata("jvmStaticFunction.kt") + public void testJvmStaticFunction() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/jvmStaticFunction.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("compiler/testData/codegen/box/reflection/call/inlineClasses/properties.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/callBy") @@ -17166,6 +17204,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/callBy/extensionFunction.kt"); } + @TestMetadata("inlineClassFunctionsAndConstructors.kt") + public void testInlineClassFunctionsAndConstructors() throws Exception { + runTest("compiler/testData/codegen/box/reflection/callBy/inlineClassFunctionsAndConstructors.kt"); + } + @TestMetadata("jvmStaticInCompanionObject.kt") public void testJvmStaticInCompanionObject() throws Exception { runTest("compiler/testData/codegen/box/reflection/callBy/jvmStaticInCompanionObject.kt"); @@ -18022,6 +18065,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt"); } + @TestMetadata("inlineClassInSignature.kt") + public void testInlineClassInSignature() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/types/inlineClassInSignature.kt"); + } + @TestMetadata("innerGenericTypeArgument.kt") public void testInnerGenericTypeArgument() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/types/innerGenericTypeArgument.kt");