-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Delegate properties to property references
#KT-8658
- Loading branch information
Showing
2 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
106 changes: 106 additions & 0 deletions
106
libraries/stdlib/src/kotlin/properties/PropertyReferenceDelegates.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <V> KProperty0<V>.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 <V> KMutableProperty0<V>.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 <T, V> KProperty1<T, V>.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 <T, V> KMutableProperty1<T, V>.setValue(thisRef: T, property: KProperty<*>, value: V) { | ||
set(thisRef, value) | ||
} |
192 changes: 192 additions & 0 deletions
192
libraries/stdlib/test/properties/delegation/PropertyReferenceTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <V> checkDelegate0(delegated: KProperty0<@NoInfer V>, source: KProperty0<V>) { | ||
assertEquals(delegated.get(), source.get()) | ||
} | ||
|
||
private fun <V> checkDelegate1(delegated: KProperty0<@NoInfer V>, source: KMutableProperty0<V>, newValue: @NoInfer V) { | ||
assertEquals(delegated.get(), source.get()) | ||
source.set(newValue) | ||
assertEquals(newValue, source.get()) | ||
assertEquals(newValue, delegated.get()) | ||
} | ||
|
||
private fun <V> checkDelegate2(delegated: KMutableProperty0<@NoInfer V>, source: KMutableProperty0<V>, 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<Number>(::covariantVal, ::topVal) | ||
checkDelegate0<CharSequence>(data::extCovariantVal, data::builderVar) | ||
} | ||
|
||
} |