diff --git a/compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt b/compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt new file mode 100644 index 00000000000..1eb5cfc7cef --- /dev/null +++ b/compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt @@ -0,0 +1,38 @@ +// IGNORE_BACKEND: JS +// WITH_REFLECT + +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty2 +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.full.getExtensionDelegate +import kotlin.test.* + +object Delegate { + operator fun getValue(instance: Any?, property: KProperty<*>) = true +} + +class Foo { + val member: Boolean by Delegate + val String.memberExtension: Boolean by Delegate +} + +val Foo.extension: Boolean by Delegate + +fun box(): String { + // Top level extension + assertEquals(Delegate, Foo::extension.apply { isAccessible = true }.getExtensionDelegate()) + + // Member extension + val me = Foo::class.members.single { it.name == "memberExtension" } as KProperty2 + assertEquals(Delegate, me.apply { isAccessible = true }.getExtensionDelegate(Foo())) + + // Member (should fail) + try { + Foo::member.apply { isAccessible = true }.getExtensionDelegate() + return "Fail: getExtensionDelegate() should fail on a non-extension property" + } catch (e: Exception) { + // OK + } + + return "OK" +} diff --git a/compiler/testData/codegen/light-analysis/reflection/properties/getDelegate/getExtensionDelegate.txt b/compiler/testData/codegen/light-analysis/reflection/properties/getDelegate/getExtensionDelegate.txt new file mode 100644 index 00000000000..a406eef4d70 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/reflection/properties/getDelegate/getExtensionDelegate.txt @@ -0,0 +1,24 @@ +@kotlin.Metadata +public final class Delegate { + public final static field INSTANCE: Delegate + private method (): void + public final method getValue(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.reflect.KProperty): boolean +} + +@kotlin.Metadata +public final class Foo { + private synthetic final static field $$delegatedProperties: kotlin.reflect.KProperty[] + private final @org.jetbrains.annotations.NotNull field member$delegate: Delegate + private final @org.jetbrains.annotations.NotNull field memberExtension$delegate: Delegate + public method (): void + public final method getMember(): boolean + public final method getMemberExtension(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean +} + +@kotlin.Metadata +public final class GetExtensionDelegateKt { + private synthetic final static field $$delegatedProperties: kotlin.reflect.KProperty[] + private final static @org.jetbrains.annotations.NotNull field extension$delegate: Delegate + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method getExtension(@org.jetbrains.annotations.NotNull p0: Foo): boolean +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 6e9feaef8c5..1dd9aa5e3ae 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -14601,6 +14601,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("getExtensionDelegate.kt") + public void testGetExtensionDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt"); + doTest(fileName); + } + @TestMetadata("kPropertyForDelegatedProperty.kt") public void testKPropertyForDelegatedProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/kPropertyForDelegatedProperty.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index fd2c7ba8912..c68a921c727 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -14601,6 +14601,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("getExtensionDelegate.kt") + public void testGetExtensionDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt"); + doTest(fileName); + } + @TestMetadata("kPropertyForDelegatedProperty.kt") public void testKPropertyForDelegatedProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/kPropertyForDelegatedProperty.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java index 38bc9db2f54..685c064dc7a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java @@ -14601,6 +14601,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis doTest(fileName); } + @TestMetadata("getExtensionDelegate.kt") + public void testGetExtensionDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt"); + doTest(fileName); + } + @TestMetadata("kPropertyForDelegatedProperty.kt") public void testKPropertyForDelegatedProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/kPropertyForDelegatedProperty.kt"); diff --git a/core/builtins/src/kotlin/reflect/KProperty.kt b/core/builtins/src/kotlin/reflect/KProperty.kt index 351575728e3..a8965196b0d 100644 --- a/core/builtins/src/kotlin/reflect/KProperty.kt +++ b/core/builtins/src/kotlin/reflect/KProperty.kt @@ -145,6 +145,8 @@ public interface KProperty1 : KProperty, (T) -> R { * @param receiver the receiver which is used to obtain the value of the property delegate. * For example, it should be a class instance if this is a member property of that class, * or an extension receiver if this is a top level extension property. + * + * @see [KProperty1.getExtensionDelegate] */ @SinceKotlin("1.1") public fun getDelegate(receiver: T): Any? @@ -204,6 +206,8 @@ public interface KProperty2 : KProperty, (D, E) -> R { * * @param receiver1 the instance of the first receiver. * @param receiver2 the instance of the second receiver. + * + * @see [KProperty2.getExtensionDelegate] */ @SinceKotlin("1.1") public fun getDelegate(receiver1: D, receiver2: E): Any? diff --git a/core/reflection.jvm/src/kotlin/reflect/full/KProperties.kt b/core/reflection.jvm/src/kotlin/reflect/full/KProperties.kt new file mode 100644 index 00000000000..7c85b2d4e40 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/full/KProperties.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("KProperties") +package kotlin.reflect.full + +import kotlin.reflect.KProperty1 +import kotlin.reflect.KProperty2 +import kotlin.reflect.jvm.internal.KPropertyImpl + +/** + * Returns the instance of a delegated **extension property**, or `null` if this property is not delegated. + * Throws an exception if this is not an extension property. + * + * @see [KProperty1.getDelegate] + */ +@SinceKotlin("1.1") +fun KProperty1<*, *>.getExtensionDelegate(): Any? { + @Suppress("UNCHECKED_CAST") + return (this as KProperty1).getDelegate(KPropertyImpl.EXTENSION_PROPERTY_DELEGATE) +} + +/** + * Returns the instance of a delegated **member extension property**, or `null` if this property is not delegated. + * Throws an exception if this is not an extension property. + * + * @param receiver the instance of the class used to retrieve the value of the property delegate. + * + * @see [KProperty2.getDelegate] + */ +@SinceKotlin("1.1") +fun KProperty2.getExtensionDelegate(receiver: D): Any? { + @Suppress("UNCHECKED_CAST") + return (this as KProperty2).getDelegate(receiver, KPropertyImpl.EXTENSION_PROPERTY_DELEGATE) +} 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 9b49ce937e5..6c8ef253178 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt @@ -87,6 +87,12 @@ internal abstract class KPropertyImpl private constructor( protected fun getDelegate(field: Field?, receiver: Any?): Any? = try { + if (receiver === EXTENSION_PROPERTY_DELEGATE) { + if (descriptor.extensionReceiverParameter == null) { + throw RuntimeException("'$this' is not an extension property and thus getExtensionDelegate() " + + "is not going to work, use getDelegate() instead") + } + } field?.get(receiver) } catch (e: IllegalAccessException) { @@ -164,6 +170,10 @@ internal abstract class KPropertyImpl private constructor( computeCallerForAccessor(isGetter = false) } } + + companion object { + val EXTENSION_PROPERTY_DELEGATE = Any() + } } 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 a51a5d02109..ac00612e93c 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 @@ -18448,6 +18448,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); } + @TestMetadata("getExtensionDelegate.kt") + public void testGetExtensionDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/getExtensionDelegate.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + @TestMetadata("kPropertyForDelegatedProperty.kt") public void testKPropertyForDelegatedProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/properties/getDelegate/kPropertyForDelegatedProperty.kt");