From 593937d3021cc7865bd752eca5056b422c238a93 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 26 Aug 2015 14:40:16 +0300 Subject: [PATCH] Support KCallable.callBy with map of parameters to arguments callBy is able to handle optional parameters. #KT-8827 Fixed --- .../kotlin/codegen/ArgumentGenerator.java | 6 +- .../callBy/defaultAndNonDefaultIntertwined.kt | 15 +++ .../reflection/callBy/extensionFunction.kt | 9 ++ .../callBy/manyArgumentsOnlyOneDefault.kt | 97 +++++++++++++++++++ .../reflection/callBy/manyMaskArguments.kt | 95 ++++++++++++++++++ .../callBy/nonDefaultParameterOmitted.kt | 21 ++++ .../reflection/callBy/nullValue.kt | 10 ++ ...thodIsInvokedWhenNoDefaultValuesAreUsed.kt | 20 ++++ .../callBy/platformStaticInObject.kt | 28 ++++++ .../callBy/primitiveDefaultValues.kt | 26 +++++ .../callBy/privateMemberFunction.kt | 27 ++++++ .../reflection/callBy/simpleConstructor.kt | 3 + .../reflection/callBy/simpleMemberFunciton.kt | 8 ++ .../callBy/simpleTopLevelFunction.kt | 3 + ...lackBoxWithStdlibCodegenTestGenerated.java | 87 +++++++++++++++++ core/builtins/src/kotlin/reflect/KCallable.kt | 9 +- .../builtins/src/kotlin/reflect/KParameter.kt | 2 +- .../src/kotlin/reflect/jvm/callables.kt | 4 + .../reflect/jvm/internal/KCallableImpl.kt | 78 ++++++++++++++- .../jvm/internal/KDeclarationContainerImpl.kt | 85 ++++++++++++---- .../reflect/jvm/internal/KFunctionImpl.kt | 43 ++++++-- .../reflect/jvm/internal/KPropertyImpl.kt | 6 ++ .../jvm/internal/CallableReference.java | 6 ++ .../jvm/internal/FunctionReference.java | 6 ++ 24 files changed, 664 insertions(+), 30 deletions(-) create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/defaultAndNonDefaultIntertwined.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/extensionFunction.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyArgumentsOnlyOneDefault.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyMaskArguments.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/nonDefaultParameterOmitted.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/nullValue.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/platformStaticInObject.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/primitiveDefaultValues.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/privateMemberFunction.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleConstructor.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleMemberFunciton.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleTopLevelFunction.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ArgumentGenerator.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ArgumentGenerator.java index c8387c5a726..06104d7d4f8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ArgumentGenerator.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ArgumentGenerator.java @@ -25,16 +25,17 @@ import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; public abstract class ArgumentGenerator { /** * @return a {@code List} of bit masks of default arguments that should be passed as last arguments to $default method, if there were * any default arguments, or an empty {@code List} if there were none + * @see kotlin.reflect.jvm.internal.KCallableImpl#callBy(Map...) */ @NotNull public List generate(@NotNull List valueArguments) { List masks = new ArrayList(1); - boolean maskIsNeeded = false; int mask = 0; int n = valueArguments.size(); for (int i = 0; i < n; i++) { @@ -47,7 +48,6 @@ public abstract class ArgumentGenerator { generateExpression(i, (ExpressionValueArgument) argument); } else if (argument instanceof DefaultValueArgument) { - maskIsNeeded = true; mask |= 1 << (i % Integer.SIZE); generateDefault(i, (DefaultValueArgument) argument); } @@ -59,7 +59,7 @@ public abstract class ArgumentGenerator { } } - if (!maskIsNeeded) { + if (mask == 0 && masks.isEmpty()) { return Collections.emptyList(); } diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/defaultAndNonDefaultIntertwined.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/defaultAndNonDefaultIntertwined.kt new file mode 100644 index 00000000000..d6c5f8a1f1a --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/defaultAndNonDefaultIntertwined.kt @@ -0,0 +1,15 @@ +import kotlin.test.assertEquals + +fun foo(a: String, b: String = "b", c: String, d: String = "d", e: String) = + a + b + c + d + e + +fun box(): String { + val p = ::foo.parameters + assertEquals("abcde", ::foo.callBy(mapOf( + p[0] to "a", + p[2] to "c", + p[4] to "e" + ))) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/extensionFunction.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/extensionFunction.kt new file mode 100644 index 00000000000..0e590a98b6d --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/extensionFunction.kt @@ -0,0 +1,9 @@ +import kotlin.test.assertEquals + +fun String.sum(other: String = "b") = this + other + +fun box(): String { + val f = String::sum + assertEquals("ab", f.callBy(mapOf(f.parameters.first() to "a"))) + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyArgumentsOnlyOneDefault.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyArgumentsOnlyOneDefault.kt new file mode 100644 index 00000000000..e12b07e13db --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyArgumentsOnlyOneDefault.kt @@ -0,0 +1,97 @@ +import kotlin.test.assertEquals + +// Generate: +// (1..70).map { " p${"%02d".format(it)}: Int," }.joinToString("\n") + +class A { + fun foo( + p01: Int, + p02: Int, + p03: Int, + p04: Int, + p05: Int, + p06: Int, + p07: Int, + p08: Int, + p09: Int, + p10: Int, + p11: Int, + p12: Int, + p13: Int, + p14: Int, + p15: Int, + p16: Int, + p17: Int, + p18: Int, + p19: Int, + p20: Int, + p21: Int, + p22: Int, + p23: Int, + p24: Int, + p25: Int, + p26: Int, + p27: Int, + p28: Int, + p29: Int, + p30: Int, + p31: Int, + p32: Int, + p33: Int, + p34: Int, + p35: Int, + p36: Int, + p37: Int, + p38: Int, + p39: Int, + p40: Int, + p41: Int, + p42: Int = 239, + p43: Int, + p44: Int, + p45: Int, + p46: Int, + p47: Int, + p48: Int, + p49: Int, + p50: Int, + p51: Int, + p52: Int, + p53: Int, + p54: Int, + p55: Int, + p56: Int, + p57: Int, + p58: Int, + p59: Int, + p60: Int, + p61: Int, + p62: Int, + p63: Int, + p64: Int, + p65: Int, + p66: Int, + p67: Int, + p68: Int, + p69: Int, + p70: Int + ) { + assertEquals(1, p01) + assertEquals(41, p41) + assertEquals(239, p42) + assertEquals(43, p43) + assertEquals(70, p70) + } +} + +fun box(): String { + val f = A::class.members.single { it.name == "foo" } + val parameters = f.parameters + + f.callBy(mapOf( + parameters.first() to A(), + *((1..41) + (43..70)).map { i -> parameters[i] to i }.toTypedArray() + )) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyMaskArguments.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyMaskArguments.kt new file mode 100644 index 00000000000..39e0173ca90 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyMaskArguments.kt @@ -0,0 +1,95 @@ +import kotlin.test.assertEquals + +// Generate: +// (1..70).map { " p${"%02d".format(it)}: Int = $it," }.joinToString("\n") + +class A { + fun foo( + p01: Int = 1, + p02: Int = 2, + p03: Int = 3, + p04: Int = 4, + p05: Int = 5, + p06: Int = 6, + p07: Int = 7, + p08: Int = 8, + p09: Int = 9, + p10: Int = 10, + p11: Int = 11, + p12: Int = 12, + p13: Int = 13, + p14: Int = 14, + p15: Int = 15, + p16: Int = 16, + p17: Int = 17, + p18: Int = 18, + p19: Int = 19, + p20: Int = 20, + p21: Int = 21, + p22: Int = 22, + p23: Int = 23, + p24: Int = 24, + p25: Int = 25, + p26: Int = 26, + p27: Int = 27, + p28: Int = 28, + p29: Int = 29, + p30: Int = 30, + p31: Int = 31, + p32: Int = 32, + p33: Int = 33, + p34: Int = 34, + p35: Int = 35, + p36: Int = 36, + p37: Int = 37, + p38: Int = 38, + p39: Int = 39, + p40: Int = 40, + p41: Int = 41, + p42: Int, + p43: Int = 43, + p44: Int = 44, + p45: Int = 45, + p46: Int = 46, + p47: Int = 47, + p48: Int = 48, + p49: Int = 49, + p50: Int = 50, + p51: Int = 51, + p52: Int = 52, + p53: Int = 53, + p54: Int = 54, + p55: Int = 55, + p56: Int = 56, + p57: Int = 57, + p58: Int = 58, + p59: Int = 59, + p60: Int = 60, + p61: Int = 61, + p62: Int = 62, + p63: Int = 63, + p64: Int = 64, + p65: Int = 65, + p66: Int = 66, + p67: Int = 67, + p68: Int = 68, + p69: Int = 69, + p70: Int = 70 + ) { + assertEquals(1, p01) + assertEquals(41, p41) + assertEquals(239, p42) + assertEquals(43, p43) + assertEquals(70, p70) + } +} + +fun box(): String { + val f = A::class.members.single { it.name == "foo" } + val parameters = f.parameters + f.callBy(mapOf( + parameters.first() to A(), + parameters.single { it.name == "p42" } to 239 + )) + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nonDefaultParameterOmitted.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nonDefaultParameterOmitted.kt new file mode 100644 index 00000000000..dcef2cfa42c --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nonDefaultParameterOmitted.kt @@ -0,0 +1,21 @@ +fun foo(x: Int, y: Int = 2) = x + y + +fun box(): String { + try { + ::foo.callBy(mapOf()) + return "Fail: IllegalArgumentException must have been thrown" + } + catch (e: IllegalArgumentException) { + // OK + } + + try { + ::foo.callBy(mapOf(::foo.parameters.last() to 1)) + return "Fail: IllegalArgumentException must have been thrown" + } + catch (e: IllegalArgumentException) { + // OK + } + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nullValue.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nullValue.kt new file mode 100644 index 00000000000..a5d1695e243 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/nullValue.kt @@ -0,0 +1,10 @@ +import kotlin.test.assertNull + +fun foo(x: String? = "Fail") { + assertNull(x) +} + +fun box(): String { + ::foo.callBy(mapOf(::foo.parameters.single() to null)) + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt new file mode 100644 index 00000000000..76cef1f832b --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt @@ -0,0 +1,20 @@ +// FULL_JDK + +import kotlin.test.assertEquals + +fun foo(result: String = "foo") { + assertEquals("box", result) + + // Check that this function was invoked directly and not through the "foo$default", i.e. there's no "foo$default" in the stack trace + val st = Thread.currentThread().stackTrace + for (i in 0..5) { + if ("foo\$default" in st[i].methodName) { + throw AssertionError("KCallable.call should invoke the method directly if all arguments are provided") + } + } +} + +fun box(): String { + ::foo.callBy(mapOf(::foo.parameters.single() to "box")) + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/platformStaticInObject.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/platformStaticInObject.kt new file mode 100644 index 00000000000..72ac105e23e --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/platformStaticInObject.kt @@ -0,0 +1,28 @@ +import kotlin.platform.platformStatic as static +import kotlin.test.assertEquals + +object Obj { + static fun foo(a: String, b: String = "b") = a + b +} + +fun box(): String { + val f = Obj::foo + + // Any object method currently requires the object instance passed + try { + f.callBy(mapOf( + f.parameters.single { it.name == "a" } to "a" + )) + return "Fail: IllegalArgumentException should have been thrown" + } + catch (e: IllegalArgumentException) { + // OK + } + + assertEquals("ab", f.callBy(mapOf( + f.parameters.first() to Obj, + f.parameters.single { it.name == "a" } to "a" + ))) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/primitiveDefaultValues.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/primitiveDefaultValues.kt new file mode 100644 index 00000000000..17270d6ffa0 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/primitiveDefaultValues.kt @@ -0,0 +1,26 @@ +import kotlin.test.assertEquals + +fun primitives( + boolean: Boolean = true, + character: Char = 'z', + byte: Byte = 5.toByte(), + short: Short = (-5).toShort(), + int: Int = 2000000000, + float: Float = -2.72f, + long: Long = 1000000000000000000L, + double: Double = 3.14159265359 +) { + assertEquals(true, boolean) + assertEquals('z', character) + assertEquals(5.toByte(), byte) + assertEquals((-5).toShort(), short) + assertEquals(2000000000, int) + assertEquals(-2.72f, float) + assertEquals(1000000000000000000L, long) + assertEquals(3.14159265359, double) +} + +fun box(): String { + ::primitives.callBy(emptyMap()) + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/privateMemberFunction.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/privateMemberFunction.kt new file mode 100644 index 00000000000..449635c7f27 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/privateMemberFunction.kt @@ -0,0 +1,27 @@ +import kotlin.reflect.IllegalCallableAccessException +import kotlin.reflect.jvm.isAccessible + +class A { + private fun foo(default: Any? = this) { + } + + fun f() = A::foo +} + +fun box(): String { + val a = A() + val f = a.f() + + try { + f.callBy(mapOf(f.parameters.first() to a)) + return "Fail: IllegalCallableAccessException should have been thrown" + } + catch (e: IllegalCallableAccessException) { + // OK + } + + f.isAccessible = true + f.callBy(mapOf(f.parameters.first() to a)) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleConstructor.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleConstructor.kt new file mode 100644 index 00000000000..8cd6f0ea6d8 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleConstructor.kt @@ -0,0 +1,3 @@ +class A(val result: String = "OK") + +fun box(): String = ::A.callBy(mapOf()).result diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleMemberFunciton.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleMemberFunciton.kt new file mode 100644 index 00000000000..e4219f898b4 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleMemberFunciton.kt @@ -0,0 +1,8 @@ +class A(val result: String = "OK") { + fun foo(x: Int = 42): String { + assert(x == 42) { x } + return result + } +} + +fun box(): String = A::foo.callBy(mapOf(A::foo.parameters.first() to A())) diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleTopLevelFunction.kt b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleTopLevelFunction.kt new file mode 100644 index 00000000000..bf99394bd30 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleTopLevelFunction.kt @@ -0,0 +1,3 @@ +fun foo(result: String = "OK") = result + +fun box(): String = ::foo.callBy(mapOf()) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index 8e6eadf60c3..22df8e70acc 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -2947,6 +2947,93 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode } } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CallBy extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInCallBy() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/reflection/callBy"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("defaultAndNonDefaultIntertwined.kt") + public void testDefaultAndNonDefaultIntertwined() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/defaultAndNonDefaultIntertwined.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("extensionFunction.kt") + public void testExtensionFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/extensionFunction.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("manyArgumentsOnlyOneDefault.kt") + public void testManyArgumentsOnlyOneDefault() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyArgumentsOnlyOneDefault.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("manyMaskArguments.kt") + public void testManyMaskArguments() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/manyMaskArguments.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("nonDefaultParameterOmitted.kt") + public void testNonDefaultParameterOmitted() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/nonDefaultParameterOmitted.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("nullValue.kt") + public void testNullValue() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/nullValue.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt") + public void testOrdinaryMethodIsInvokedWhenNoDefaultValuesAreUsed() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/ordinaryMethodIsInvokedWhenNoDefaultValuesAreUsed.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("platformStaticInObject.kt") + public void testPlatformStaticInObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/platformStaticInObject.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("primitiveDefaultValues.kt") + public void testPrimitiveDefaultValues() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/primitiveDefaultValues.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("privateMemberFunction.kt") + public void testPrivateMemberFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/privateMemberFunction.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("simpleConstructor.kt") + public void testSimpleConstructor() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleConstructor.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("simpleMemberFunciton.kt") + public void testSimpleMemberFunciton() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleMemberFunciton.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("simpleTopLevelFunction.kt") + public void testSimpleTopLevelFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/callBy/simpleTopLevelFunction.kt"); + doTestWithStdlib(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/reflection/classLiterals") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/builtins/src/kotlin/reflect/KCallable.kt b/core/builtins/src/kotlin/reflect/KCallable.kt index 51060c533c7..e4fa3d7cfd9 100644 --- a/core/builtins/src/kotlin/reflect/KCallable.kt +++ b/core/builtins/src/kotlin/reflect/KCallable.kt @@ -45,9 +45,16 @@ public interface KCallable : KAnnotatedElement { public val returnType: KType /** - * Calls this callable with the specified arguments and returns the result. + * Calls this callable with the specified list of arguments and returns the result. * Throws an exception if the number of specified arguments is not equal to the size of [parameters], * or if their types do not match the types of the parameters. */ public fun call(vararg args: Any?): R + + /** + * Calls this callable with the specified mapping of parameters to arguments and returns the result. + * If a parameter is not found in the mapping and is not optional (as per [KParameter.isOptional]), + * or its type does not match the type of the provided value, an exception is thrown. + */ + public fun callBy(args: Map): R } diff --git a/core/builtins/src/kotlin/reflect/KParameter.kt b/core/builtins/src/kotlin/reflect/KParameter.kt index 71db9235cf6..0cc07c84eb9 100644 --- a/core/builtins/src/kotlin/reflect/KParameter.kt +++ b/core/builtins/src/kotlin/reflect/KParameter.kt @@ -62,7 +62,7 @@ public interface KParameter : KAnnotatedElement { } /** - * `true` if this parameter is optional, or `false` otherwise. + * `true` if this parameter is optional and can be omitted when making a call via [KCallable.call], or `false` otherwise. * * A parameter is optional in any of the two cases: * 1. The default value is provided at the declaration of this parameter. diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/callables.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/callables.kt index 117eda98a18..1e0db1db8a6 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/callables.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/callables.kt @@ -16,10 +16,12 @@ package kotlin.reflect.jvm +import java.lang.reflect.AccessibleObject import kotlin.reflect.KCallable import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty import kotlin.reflect.KProperty +import kotlin.reflect.jvm.internal.KCallableImpl /** * Provides a way to suppress JVM access checks for a callable. @@ -50,6 +52,7 @@ public var KCallable<*>.isAccessible: Boolean javaMethod?.isAccessible ?: true is KFunction -> javaMethod?.isAccessible ?: true && + ((this as KCallableImpl<*>).defaultCaller?.member as? AccessibleObject)?.isAccessible ?: true && this.javaConstructor?.isAccessible ?: true else -> throw UnsupportedOperationException("Unknown callable: $this ($javaClass)") } @@ -75,6 +78,7 @@ public var KCallable<*>.isAccessible: Boolean } is KFunction -> { javaMethod?.isAccessible = value + ((this as KCallableImpl<*>).defaultCaller?.member as? AccessibleObject)?.isAccessible = true this.javaConstructor?.isAccessible = value } else -> throw UnsupportedOperationException("Unknown callable: $this ($javaClass)") diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt index 5340abd1ae3..42ec1985f57 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt @@ -18,20 +18,26 @@ package kotlin.reflect.jvm.internal import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotated -import java.util.ArrayList +import java.lang.reflect.Type +import java.util.* import kotlin.reflect.KCallable import kotlin.reflect.KParameter import kotlin.reflect.KType +import kotlin.reflect.KotlinReflectionInternalError +import kotlin.reflect.jvm.javaType interface KCallableImpl : KCallable, KAnnotatedElementImpl { val descriptor: CallableMemberDescriptor val caller: FunctionCaller<*> + val defaultCaller: FunctionCaller<*>? + override val annotated: Annotated get() = descriptor override val parameters: List get() { + val descriptor = descriptor val result = ArrayList() var index = 0 @@ -58,4 +64,74 @@ interface KCallableImpl : KCallable, KAnnotatedElementImpl { override fun call(vararg args: Any?): R = reflectionCall { return caller.call(args) as R } + + // See ArgumentGenerator#generate + override fun callBy(args: Map): R { + val parameters = parameters + val arguments = ArrayList(parameters.size()) + var mask = 0 + val masks = ArrayList(1) + var index = 0 + + for (parameter in parameters) { + if (index != 0 && index % Integer.SIZE == 0) { + masks.add(mask) + mask = 0 + } + + when { + args.containsKey(parameter) -> { + arguments.add(args[parameter]) + } + parameter.isOptional -> { + arguments.add(defaultPrimitiveValue(parameter.type.javaType)) + mask = mask or (1 shl (index % Integer.SIZE)) + } + else -> { + throw IllegalArgumentException("No argument provided for a required parameter: $parameter") + } + } + + if (parameter.kind == KParameter.Kind.VALUE) { + index++ + } + } + + if (mask == 0 && masks.isEmpty()) { + return call(*arguments.toTypedArray()) + } + + masks.add(mask) + + val caller = defaultCaller ?: throw KotlinReflectionInternalError("This callable does not support a default call: $descriptor") + + arguments.addAll(masks) + + if (caller is FunctionCaller.Constructor) { + // DefaultConstructorMarker + arguments.add(null) + } + + @suppress("UNCHECKED_CAST") + return reflectionCall { + caller.call(arguments.toTypedArray()) as R + } + } + + private fun defaultPrimitiveValue(type: Type): Any? = + if (type is Class<*> && type.isPrimitive) { + when (type) { + java.lang.Boolean.TYPE -> false + java.lang.Character.TYPE -> 0.toChar() + java.lang.Byte.TYPE -> 0.toByte() + java.lang.Short.TYPE -> 0.toShort() + java.lang.Integer.TYPE -> 0 + java.lang.Float.TYPE -> 0f + java.lang.Long.TYPE -> 0L + java.lang.Double.TYPE -> 0.0 + java.lang.Void.TYPE -> throw IllegalStateException("Parameter with void type is illegal") + else -> throw UnsupportedOperationException("Unknown primitive: $type") + } + } + else null } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt index effdfd75c6d..d9a37fc547e 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt @@ -18,6 +18,7 @@ package kotlin.reflect.jvm.internal import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies +import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.structure.reflect.classId import org.jetbrains.kotlin.load.java.structure.reflect.createArrayType import org.jetbrains.kotlin.load.java.structure.reflect.safeClassLoader @@ -137,6 +138,24 @@ abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer { return functions.single() } + private fun Class<*>.tryGetMethod(name: String, parameterTypes: List>, declared: Boolean) = + try { + if (declared) getDeclaredMethod(name, *parameterTypes.toTypedArray()) + else getMethod(name, *parameterTypes.toTypedArray()) + } + catch (e: NoSuchMethodException) { + null + } + + private fun Class<*>.tryGetConstructor(parameterTypes: List>, declared: Boolean) = + try { + if (declared) getDeclaredConstructor(*parameterTypes.toTypedArray()) + else getConstructor(*parameterTypes.toTypedArray()) + } + catch (e: NoSuchMethodException) { + null + } + // TODO: check resulting method's return type fun findMethodBySignature( @suppress("UNUSED_PARAMETER") proto: ProtoBuf.Callable, @@ -144,7 +163,7 @@ abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer { nameResolver: NameResolver, declared: Boolean ): Method? { - val name = nameResolver.getString(signature.getName()) + val name = nameResolver.getString(signature.name) if (name == "") return null val parameterTypes = loadParameterTypes(nameResolver, signature) @@ -153,13 +172,25 @@ abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer { // This is likely to change after the package part reform. val owner = jClass - return try { - if (declared) owner.getDeclaredMethod(name, *parameterTypes) - else owner.getMethod(name, *parameterTypes) - } - catch (e: NoSuchMethodException) { - null + return owner.tryGetMethod(name, parameterTypes, declared) + } + + fun findDefaultMethod( + signature: JvmProtoBuf.JvmMethodSignature, + nameResolver: NameResolver, + isMember: Boolean, + declared: Boolean + ): Method? { + val name = nameResolver.getString(signature.name) + if (name == "") return null + + val parameterTypes = arrayListOf>() + if (isMember) { + parameterTypes.add(jClass) } + addParametersAndMasks(parameterTypes, nameResolver, signature) + + return jClass.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, declared) } fun findConstructorBySignature( @@ -167,24 +198,40 @@ abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer { nameResolver: NameResolver, declared: Boolean ): Constructor<*>? { - if (nameResolver.getString(signature.getName()) != "") return null + if (nameResolver.getString(signature.name) != "") return null - val parameterTypes = loadParameterTypes(nameResolver, signature) + return jClass.tryGetConstructor(loadParameterTypes(nameResolver, signature), declared) + } - return try { - if (declared) jClass.getDeclaredConstructor(*parameterTypes) - else jClass.getConstructor(*parameterTypes) - } - catch (e: NoSuchMethodException) { - null + fun findDefaultConstructor( + signature: JvmProtoBuf.JvmMethodSignature, + nameResolver: NameResolver, + declared: Boolean + ): Constructor<*>? { + if (nameResolver.getString(signature.name) != "") return null + + val parameterTypes = arrayListOf>() + addParametersAndMasks(parameterTypes, nameResolver, signature) + parameterTypes.add(DEFAULT_CONSTRUCTOR_MARKER) + + return jClass.tryGetConstructor(parameterTypes, declared) + } + + private fun addParametersAndMasks( + result: MutableList>, nameResolver: NameResolver, signature: JvmProtoBuf.JvmMethodSignature + ) { + val valueParameters = loadParameterTypes(nameResolver, signature) + result.addAll(valueParameters) + repeat((valueParameters.size() + Integer.SIZE - 1) / Integer.SIZE) { + result.add(Integer.TYPE) } } - private fun loadParameterTypes(nameResolver: NameResolver, signature: JvmProtoBuf.JvmMethodSignature): Array> { + private fun loadParameterTypes(nameResolver: NameResolver, signature: JvmProtoBuf.JvmMethodSignature): List> { val classLoader = jClass.safeClassLoader - return signature.getParameterTypeList().map { jvmType -> + return signature.parameterTypeList.map { jvmType -> loadJvmType(jvmType, nameResolver, classLoader) - }.toTypedArray() + } } // TODO: check resulting field's type @@ -256,5 +303,7 @@ abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer { LONG to java.lang.Long.TYPE, DOUBLE to java.lang.Double.TYPE ) + + private val DEFAULT_CONSTRUCTOR_MARKER = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker") } } 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 12b754019b7..01b80c985d2 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt @@ -49,14 +49,14 @@ open class KFunctionImpl protected constructor( override val name: String get() = descriptor.name.asString() + private fun isDeclared(): Boolean = Visibilities.isPrivate(descriptor.visibility) + override val caller: FunctionCaller<*> by ReflectProperties.lazySoft { val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor) val member: Member? = when (jvmSignature) { is KotlinFunction -> - if (name == "") container.findConstructorBySignature(jvmSignature.signature, jvmSignature.nameResolver, - Visibilities.isPrivate(descriptor.visibility)) - else container.findMethodBySignature(jvmSignature.proto, jvmSignature.signature, jvmSignature.nameResolver, - Visibilities.isPrivate(descriptor.visibility)) + if (name == "") container.findConstructorBySignature(jvmSignature.signature, jvmSignature.nameResolver, isDeclared()) + else container.findMethodBySignature(jvmSignature.proto, jvmSignature.signature, jvmSignature.nameResolver, isDeclared()) is JavaMethod -> jvmSignature.method is JavaConstructor -> jvmSignature.constructor is BuiltInFunction -> jvmSignature.getMember(container) @@ -68,12 +68,43 @@ open class KFunctionImpl protected constructor( !Modifier.isStatic(member.modifiers) -> FunctionCaller.InstanceMethod(member) descriptor.annotations.findAnnotation(PLATFORM_STATIC) != null, - descriptor.annotations.findAnnotation(JVM_STATIC) != null-> + descriptor.annotations.findAnnotation(JVM_STATIC) != null -> FunctionCaller.PlatformStaticInObject(member) else -> FunctionCaller.StaticMethod(member) } - else -> throw KotlinReflectionInternalError("Call is not yet supported for this function: $descriptor") + else -> throw KotlinReflectionInternalError("Call is not yet supported for this function: $descriptor (member = $member)") + } + } + + override val defaultCaller: FunctionCaller<*>? by ReflectProperties.lazySoft { + val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor) + val member: Member? = when (jvmSignature) { + is KotlinFunction -> { + if (name == "") { + container.findDefaultConstructor(jvmSignature.signature, jvmSignature.nameResolver, isDeclared()) + } + else { + val isMember = !Modifier.isStatic(caller.member.modifiers) + container.findDefaultMethod(jvmSignature.signature, jvmSignature.nameResolver, isMember, isDeclared()) + } + } + else -> { + // Java methods, Java constructors and built-ins don't have $default methods + null + } + } + + when (member) { + is Constructor<*> -> FunctionCaller.Constructor(member) + is Method -> when { + descriptor.annotations.findAnnotation(PLATFORM_STATIC) != null, + descriptor.annotations.findAnnotation(JVM_STATIC) != null -> + FunctionCaller.PlatformStaticInObject(member) + + else -> FunctionCaller.StaticMethod(member) + } + else -> null } } 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 c9157cef6be..63aa048862a 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt @@ -37,6 +37,8 @@ interface KPropertyImpl : KProperty, KCallableImpl { override val caller: FunctionCaller<*> get() = getter.caller + override val defaultCaller: FunctionCaller<*>? get() = getter.defaultCaller + abstract class Accessor : KProperty.Accessor { abstract override val property: KPropertyImpl @@ -54,6 +56,8 @@ interface KPropertyImpl : KProperty, KCallableImpl { override val caller: FunctionCaller<*> by ReflectProperties.lazySoft { computeCallerForAccessor(isGetter = true) } + + override val defaultCaller: FunctionCaller<*>? get() = null } } @@ -74,6 +78,8 @@ interface KMutablePropertyImpl : KMutableProperty, KPropertyImpl { override val caller: FunctionCaller<*> by ReflectProperties.lazySoft { computeCallerForAccessor(isGetter = false) } + + override val defaultCaller: FunctionCaller<*>? get() = null } } diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java b/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java index 1db0736aedb..e1a87f2e193 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java +++ b/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull; import java.lang.annotation.Annotation; import java.util.List; +import java.util.Map; /** * A superclass for all classes generated by Kotlin compiler for callable references. @@ -86,6 +87,11 @@ public abstract class CallableReference implements KCallable { throw error(); } + @Override + public Object callBy(@NotNull Map args) { + throw error(); + } + protected static Error error() { throw new KotlinReflectionNotSupportedError(); } diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/FunctionReference.java b/core/runtime.jvm/src/kotlin/jvm/internal/FunctionReference.java index 2aadabb524b..d4f2759f06c 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/FunctionReference.java +++ b/core/runtime.jvm/src/kotlin/jvm/internal/FunctionReference.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import java.lang.annotation.Annotation; import java.util.List; +import java.util.Map; @SuppressWarnings("deprecation") public class FunctionReference @@ -82,6 +83,11 @@ public class FunctionReference throw error(); } + @Override + public Object callBy(@NotNull Map args) { + throw error(); + } + protected static Error error() { throw new KotlinReflectionNotSupportedError(); }