diff --git a/libraries/stdlib/src/kotlin/properties/PropertyReferenceDelegates.kt b/libraries/stdlib/src/kotlin/properties/PropertyReferenceDelegates.kt new file mode 100644 index 0000000000000..f15e8419fc7f5 --- /dev/null +++ b/libraries/stdlib/src/kotlin/properties/PropertyReferenceDelegates.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("PackageDirectoryMismatch") +package kotlin + +import kotlin.reflect.* + +/** + * An extension operator that allows delegating a read-only property of type [V] + * to a property reference to a property of type [V] or its subtype. + * + * @receiver A property reference to a read-only or mutable property of type [V] or its subtype. + * The reference is without a receiver, i.e. it either references a top-level property or + * has the receiver bound to it. + * + * Example: + * + * ``` + * class Login(val username: String) + * val defaultLogin = Login("Admin") + * val defaultUsername by defaultLogin::username + * // equivalent to + * val defaultUserName get() = defaultLogin.username + * ``` + */ +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline operator fun KProperty0.getValue(thisRef: Any?, property: KProperty<*>): V { + return get() +} + +/** + * An extension operator that allows delegating a mutable property of type [V] + * to a property reference to a mutable property of the same type [V]. + * + * @receiver A property reference to a mutable property of type [V]. + * The reference is without a receiver, i.e. it either references a top-level property or + * has the receiver bound to it. + * + * Example: + * + * ``` + * class Login(val username: String, var incorrectAttemptCounter: Int = 0) + * val defaultLogin = Login("Admin") + * var defaultLoginAttempts by defaultLogin::incorrectAttemptCounter + * // equivalent to + * var defaultLoginAttempts: Int + * get() = defaultLogin.incorrectAttemptCounter + * set(value) { defaultLogin.incorrectAttemptCounter = value } + * ``` + */ +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline operator fun KMutableProperty0.setValue(thisRef: Any?, property: KProperty<*>, value: V) { + set(value) +} + + +/** + * An extension operator that allows delegating a read-only member or extension property of type [V] + * to a property reference to a member or extension property of type [V] or its subtype. + * + * @receiver A property reference to a read-only or mutable property of type [V] or its subtype. + * The reference has an unbound receiver of type [T]. + * + * Example: + * + * ``` + * class Login(val username: String) + * val Login.user by Login::username + * // equivalent to + * val Login.user get() = this.username + * ``` + */ +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline operator fun KProperty1.getValue(thisRef: T, property: KProperty<*>): V { + return get(thisRef) +} + +/** + * An extension operator that allows delegating a mutable member or extension property of type [V] + * to a property reference to a member or extension mutable property of the same type [V]. + * + * @receiver A property reference to a read-only or mutable property of type [V] or its subtype. + * The reference has an unbound receiver of type [T]. + * + * Example: + * + * ``` + * class Login(val username: String, var incorrectAttemptCounter: Int) + * var Login.attempts by Login::incorrectAttemptCounter + * // equivalent to + * var Login.attempts: Int + * get() = this.incorrectAttemptCounter + * set(value) { this.incorrectAttemptCounter = value } + * ``` + */ +@SinceKotlin("1.4") +@kotlin.internal.InlineOnly +public inline operator fun KMutableProperty1.setValue(thisRef: T, property: KProperty<*>, value: V) { + set(thisRef, value) +} \ No newline at end of file diff --git a/libraries/stdlib/test/properties/delegation/PropertyReferenceTest.kt b/libraries/stdlib/test/properties/delegation/PropertyReferenceTest.kt new file mode 100644 index 0000000000000..d3d2e69c85eb8 --- /dev/null +++ b/libraries/stdlib/test/properties/delegation/PropertyReferenceTest.kt @@ -0,0 +1,192 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.properties.delegation.references + +import kotlin.reflect.* +import kotlin.test.* +import kotlin.internal.* +import kotlin.random.* + +open class Data(val stringVal: String, var intVar: Int, var builderVar: StringBuilder = StringBuilder("default")) +val Data.formattedVal: String get() = "Hello $stringVal" +var Data.displacedVar: Int + get() = intVar + 2 + set(value) { intVar = value - 2 } + +val data = Data("bound string", 42) + +val topVal: Double get() = 3.14 +var topVar: ULong = 0xFFFFUL + +// top-level properties + +// val to bound val +val tlValBoundVal by data::stringVal +// val to bound var +val tlValBoundVar by data::intVar +// var to bound var +var tlVarBoundVar by data::intVar + +// val to top-level val +val tlValTopLevelVal by ::topVal +// val to top-level var +val tlValTopLevelVar by ::topVar +// var to top-level var +var tlVarTopLevelVar by ::topVar + +// member properties +class DataExt : Data("member string", -1) { + + // val to top-level val + val valTopLevelVal by ::topVal + + // val to top-level var + val valTopLevelVar by ::topVar + + // var to top-level var + var varTopLevelVar by ::topVar + + + // val to bound val + val valBoundVal by this::stringVal + + // val to bound var + val valBoundVar by data::intVar + + // var to bound var + var varBoundVar by ::intVar + + + // val to extension val + val valExtVal by Data::formattedVal + + // val to extension var + val valExtVar by Data::displacedVar + + // var to extension var + var varExtVar by Data::displacedVar +} + + +// extension properties +// val to bound val +val Data.extValBoundVal by data::stringVal +// val to bound var +val Data.extValBoundVar by data::intVar +// var to bound var +var Data.extVarBoundVar by data::intVar + +// val to top-level val +val Data.extValTopLevelVal by ::topVal +// val to top-level var +val Data.extValTopLevelVar by ::topVar +// var to top-level var +var Data.extVarTopLevelVar by ::topVar + +// val to member val +val Data.extValMemberVal by Data::stringVal +// val to member var +val Data.extValMemberVar by Data::intVar +// var to member var +var Data.extVarMemberVar by Data::intVar + +// val to extension val +val Data.extValExtVal by Data::formattedVal +// val to extension var +val Data.extValExtVar by Data::displacedVar +// var to extension var +var Data.extVarExtVar by Data::displacedVar + + +// covariance +val covariantVal: Number by ::topVal +val Data.extCovariantVal: CharSequence by Data::builderVar + + +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +class PropertyReferenceTest { + + private fun checkDelegate0(delegated: KProperty0<@NoInfer V>, source: KProperty0) { + assertEquals(delegated.get(), source.get()) + } + + private fun checkDelegate1(delegated: KProperty0<@NoInfer V>, source: KMutableProperty0, newValue: @NoInfer V) { + assertEquals(delegated.get(), source.get()) + source.set(newValue) + assertEquals(newValue, source.get()) + assertEquals(newValue, delegated.get()) + } + + private fun checkDelegate2(delegated: KMutableProperty0<@NoInfer V>, source: KMutableProperty0, newValue1: @NoInfer V, newValue2: @NoInfer V) { + assertEquals(delegated.get(), source.get()) + source.set(newValue1) + assertEquals(newValue1, source.get()) + assertEquals(newValue1, delegated.get()) + + delegated.set(newValue2) + assertEquals(newValue2, source.get()) + assertEquals(newValue2, delegated.get()) + } + + private fun int(): Int = Random.nextInt() + private fun ulong(): ULong = Random.nextULong() + + + @Test + fun topLevelProperties() { + checkDelegate0(::tlValBoundVal, data::stringVal) + checkDelegate1(::tlValBoundVar, data::intVar, int()) + checkDelegate2(::tlVarBoundVar, data::intVar, int(), int()) + + checkDelegate0(::tlValTopLevelVal, ::topVal) + checkDelegate1(::tlValTopLevelVar, ::topVar, ulong()) + checkDelegate2(::tlVarTopLevelVar, ::topVar, ulong(), ulong()) + } + + @Test + fun memberProperties() { + val local = DataExt() + checkDelegate0(local::valBoundVal, local::stringVal) + checkDelegate1(local::valBoundVar, data::intVar, int()) + checkDelegate2(local::varBoundVar, local::intVar, int(), int()) + + checkDelegate0(local::valTopLevelVal, ::topVal) + checkDelegate1(local::valTopLevelVar, ::topVar, ulong()) + checkDelegate2(local::varTopLevelVar, ::topVar, ulong(), ulong()) + + checkDelegate0(local::valExtVal, local::formattedVal) + checkDelegate1(local::valExtVar, local::displacedVar, int()) + checkDelegate2(local::varExtVar, local::displacedVar, int(), int()) + } + + @Test + fun extensionProperties() { + val local = Data("ext", Int.MAX_VALUE) + + checkDelegate0(local::extValBoundVal, data::stringVal) + checkDelegate1(local::extValBoundVar, data::intVar, int()) + checkDelegate2(local::extVarBoundVar, data::intVar, int(), int()) + + checkDelegate0(local::extValMemberVal, local::stringVal) + checkDelegate1(local::extValMemberVar, local::intVar, int()) + checkDelegate2(local::extVarMemberVar, local::intVar, int(), int()) + + checkDelegate0(local::extValTopLevelVal, ::topVal) + checkDelegate1(local::extValTopLevelVar, ::topVar, ulong()) + checkDelegate2(local::extVarTopLevelVar, ::topVar, ulong(), ulong()) + + checkDelegate0(local::extValExtVal, local::formattedVal) + checkDelegate1(local::extValExtVar, local::displacedVar, int()) + checkDelegate2(local::extVarExtVar, local::displacedVar, int(), int()) + } + + @Test + fun covariantProperties() { + checkDelegate0(::covariantVal, ::topVal) + checkDelegate0(data::extCovariantVal, data::builderVar) + } + +} \ No newline at end of file