Delegate properties to property references
#KT-8658
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user