Skip to content

Commit

Permalink
Delegate properties to property references
Browse files Browse the repository at this point in the history
#KT-8658
  • Loading branch information
ilya-g committed Apr 15, 2020
1 parent 8179828 commit ea1e16e
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 0 deletions.
106 changes: 106 additions & 0 deletions libraries/stdlib/src/kotlin/properties/PropertyReferenceDelegates.kt
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 libraries/stdlib/test/properties/delegation/PropertyReferenceTest.kt
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)
}

}

0 comments on commit ea1e16e

Please sign in to comment.