diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 701e62396a8..3133961b1f2 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -619,6 +619,20 @@ task delegatedProperty_delegatedOverride(type: LinkKonanTest) { lib = "codegen/delegatedProperty/delegatedOverride_lib.kt" } +task delegatedProperty_lazy(type: RunKonanTest) { + goldValue = "computed!\nHello\nHello\n" + source = "codegen/delegatedProperty/lazy.kt" +} + +task delegatedProperty_observable(type: RunKonanTest) { + goldValue = " -> first\nfirst -> second\n" + source = "codegen/delegatedProperty/observable.kt" +} + +task delegatedProperty_map(type: RunKonanTest) { + goldValue = "John Doe\n25\n" + source = "codegen/delegatedProperty/map.kt" +} task array0(type: RunKonanTest) { goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n" diff --git a/backend.native/tests/codegen/delegatedProperty/lazy.kt b/backend.native/tests/codegen/delegatedProperty/lazy.kt new file mode 100644 index 00000000000..6fe68e92fce --- /dev/null +++ b/backend.native/tests/codegen/delegatedProperty/lazy.kt @@ -0,0 +1,9 @@ +val lazyValue: String by lazy { + println("computed!") + "Hello" +} + +fun main(args: Array) { + println(lazyValue) + println(lazyValue) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/delegatedProperty/map.kt b/backend.native/tests/codegen/delegatedProperty/map.kt new file mode 100644 index 00000000000..6bd335ab010 --- /dev/null +++ b/backend.native/tests/codegen/delegatedProperty/map.kt @@ -0,0 +1,13 @@ +class User(val map: Map) { + val name: String by map + val age: Int by map +} + +fun main(args: Array) { + val user = User(mapOf( + "name" to "John Doe", + "age" to 25 + )) + println(user.name) // Prints "John Doe" + println(user.age) // Prints 25 +} \ No newline at end of file diff --git a/backend.native/tests/codegen/delegatedProperty/observable.kt b/backend.native/tests/codegen/delegatedProperty/observable.kt new file mode 100644 index 00000000000..c209bc5da2f --- /dev/null +++ b/backend.native/tests/codegen/delegatedProperty/observable.kt @@ -0,0 +1,14 @@ +import kotlin.properties.Delegates + +class User { + var name: String by Delegates.observable("") { + prop, old, new -> + println("$old -> $new") + } +} + +fun main(args: Array) { + val user = User() + user.name = "first" + user.name = "second" +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/collections/MapAccessors.kt b/runtime/src/main/kotlin/kotlin/collections/MapAccessors.kt new file mode 100644 index 00000000000..5e2847903a3 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/collections/MapAccessors.kt @@ -0,0 +1,39 @@ +package kotlin.collections + +import kotlin.reflect.KProperty +import kotlin.internal.Exact + +/** + * Returns the value of the property for the given object from this read-only map. + * @param thisRef the object for which the value is requested (not used). + * @param property the metadata for the property, used to get the name of property and lookup the value corresponding to this name in the map. + * @return the property value. + * + * @throws NoSuchElementException when the map doesn't contain value for the property name and doesn't provide an implicit default (see [withDefault]). + */ +@kotlin.internal.InlineOnly +public inline operator fun Map.getValue(thisRef: Any?, property: KProperty<*>): V1 + = @Suppress("UNCHECKED_CAST", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") (getOrImplicitDefault(property.name) as V1) + +/** + * Returns the value of the property for the given object from this mutable map. + * @param thisRef the object for which the value is requested (not used). + * @param property the metadata for the property, used to get the name of property and lookup the value corresponding to this name in the map. + * @return the property value. + * + * @throws NoSuchElementException when the map doesn't contain value for the property name and doesn't provide an implicit default (see [withDefault]). + */ +@kotlin.internal.InlineOnly +public inline operator fun MutableMap.getValue(thisRef: Any?, property: KProperty<*>): V + = @Suppress("UNCHECKED_CAST", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") (getOrImplicitDefault(property.name) as V) + +/** + * Stores the value of the property for the given object in this mutable map. + * @param thisRef the object for which the value is requested (not used). + * @param property the metadata for the property, used to get the name of property and store the value associated with that name in the map. + * @param value the value to set. + */ +@kotlin.internal.InlineOnly +public inline operator fun MutableMap.setValue(thisRef: Any?, property: KProperty<*>, value: V) { + this.put(property.name, value) +} diff --git a/runtime/src/main/kotlin/kotlin/collections/MapWithDefault.kt b/runtime/src/main/kotlin/kotlin/collections/MapWithDefault.kt new file mode 100644 index 00000000000..6d26218751d --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/collections/MapWithDefault.kt @@ -0,0 +1,94 @@ +package kotlin.collections + +/** + * Returns the value for the given key, or the implicit default value for this map. + * By default no implicit value is provided for maps and a [NoSuchElementException] is thrown. + * To create a map with implicit default value use [withDefault] method. + * + * @throws NoSuchElementException when the map doesn't contain a value for the specified key and no implicit default was provided for that map. + */ +@PublishedApi +internal fun Map.getOrImplicitDefault(key: K): V { + if (this is MapWithDefault) + return this.getOrImplicitDefault(key) + + return getOrElseNullable(key, { throw NoSuchElementException("Key $key is missing in the map.") }) +} + +/** + * Returns a wrapper of this read-only map, having the implicit default value provided with the specified function [defaultValue]. + * This implicit default value is used when properties are delegated to the returned map, + * and that map doesn't contain a value for the key specified. + * + * When this map already has an implicit default value provided with a former call to [withDefault], it is being replaced by this call. + */ +public fun Map.withDefault(defaultValue: (key: K) -> V): Map = + when (this) { + is MapWithDefault -> this.map.withDefault(defaultValue) + else -> MapWithDefaultImpl(this, defaultValue) + } + +/** + * Returns a wrapper of this mutable map, having the implicit default value provided with the specified function [defaultValue]. + * This implicit default value is used when properties are delegated to the returned map, + * and that map doesn't contain a value for the key specified. + * + * When this map already has an implicit default value provided with a former call to [withDefault], it is being replaced by this call. + */ +public fun MutableMap.withDefault(defaultValue: (key: K) -> V): MutableMap = + when (this) { + is MutableMapWithDefault -> this.map.withDefault(defaultValue) + else -> MutableMapWithDefaultImpl(this, defaultValue) + } + + + + + +private interface MapWithDefault: Map { + public val map: Map + public fun getOrImplicitDefault(key: K): V +} + +private interface MutableMapWithDefault: MutableMap, MapWithDefault { + public override val map: MutableMap +} + + +private class MapWithDefaultImpl(public override val map: Map, private val default: (key: K) -> V) : MapWithDefault { + override fun equals(other: Any?): Boolean = map.equals(other) + override fun hashCode(): Int = map.hashCode() + override fun toString(): String = map.toString() + override val size: Int get() = map.size + override fun isEmpty(): Boolean = map.isEmpty() + override fun containsKey(key: K): Boolean = map.containsKey(key) + override fun containsValue(value: @UnsafeVariance V): Boolean = map.containsValue(value) + override fun get(key: K): V? = map.get(key) + override val keys: Set get() = map.keys + override val values: Collection get() = map.values + override val entries: Set> get() = map.entries + + override fun getOrImplicitDefault(key: K): V = map.getOrElseNullable(key, { default(key) }) +} + +private class MutableMapWithDefaultImpl(public override val map: MutableMap, private val default: (key: K) -> V): MutableMapWithDefault { + override fun equals(other: Any?): Boolean = map.equals(other) + override fun hashCode(): Int = map.hashCode() + override fun toString(): String = map.toString() + override val size: Int get() = map.size + override fun isEmpty(): Boolean = map.isEmpty() + override fun containsKey(key: K): Boolean = map.containsKey(key) + override fun containsValue(value: @UnsafeVariance V): Boolean = map.containsValue(value) + override fun get(key: K): V? = map.get(key) + override val keys: MutableSet get() = map.keys + override val values: MutableCollection get() = map.values + override val entries: MutableSet> get() = map.entries + + override fun put(key: K, value: V): V? = map.put(key, value) + override fun remove(key: K): V? = map.remove(key) + override fun putAll(from: Map) = map.putAll(from) + override fun clear() = map.clear() + + override fun getOrImplicitDefault(key: K): V = map.getOrElseNullable(key, { default(key) }) +} +