Introduce KProperty{1,2}.getExtensionDelegate

These two functions are supposed to fix an inconvenience in the design of
KProperty{1,2}.getDelegate for extension properties, where you have to pass an
instance of the extension receiver which is going to be completely ignored

 #KT-8384 Fixed
This commit is contained in:
Alexander Udalov
2017-01-18 18:15:24 +03:00
parent 78f2515e95
commit ebd577e52a
9 changed files with 154 additions and 0 deletions
@@ -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<Foo, String, Boolean>
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"
}
@@ -0,0 +1,24 @@
@kotlin.Metadata
public final class Delegate {
public final static field INSTANCE: Delegate
private method <init>(): 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 <init>(): 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
}
@@ -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");
@@ -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");
@@ -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");
@@ -145,6 +145,8 @@ public interface KProperty1<T, out R> : KProperty<R>, (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<D, E, out R> : KProperty<R>, (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?
@@ -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<Any?, *>).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 <D> KProperty2<D, *, *>.getExtensionDelegate(receiver: D): Any? {
@Suppress("UNCHECKED_CAST")
return (this as KProperty2<D, Any?, *>).getDelegate(receiver, KPropertyImpl.EXTENSION_PROPERTY_DELEGATE)
}
@@ -87,6 +87,12 @@ internal abstract class KPropertyImpl<out R> 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<out R> private constructor(
computeCallerForAccessor(isGetter = false)
}
}
companion object {
val EXTENSION_PROPERTY_DELEGATE = Any()
}
}
@@ -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");