From 704379934e875fa4f0c4643ba6a430497d34d718 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 21 Feb 2024 13:35:41 +0100 Subject: [PATCH] [kotlin-tooling-core] Allow nullable extra values The change is binary compatible, but not source compatible. Potential clients like Google might need to adapt a potential `IdeaKotlinExtrasSerializationExtension` implementaiton. Right now, no such implementation is known --- .../kotlin/gradle/idea/proto/extras.kt | 8 ++- .../IdeaKotlinExtrasSerializationExtension.kt | 6 +- .../serialize/IdeaKotlinExtrasSerializer.kt | 2 +- .../TestIdeaExtrasSerializationExtension.kt | 2 +- .../gradle/plugin/ide/IdeSerialization.kt | 10 +-- .../api/kotlin-tooling-core.api | 1 + .../jetbrains/kotlin/tooling/core/Extras.kt | 22 +++---- .../kotlin/tooling/core/ExtrasImpl.kt | 20 +++--- .../kotlin/tooling/core/ExtrasProperty.kt | 12 ++-- .../kotlin/tooling/core/ExtrasUtils.kt | 13 +++- .../kotlin/tooling/core/ExtrasPropertyTest.kt | 37 +++++++++++ .../kotlin/tooling/core/ExtrasTest.kt | 62 ++++++++++++++++--- 12 files changed, 142 insertions(+), 53 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/extras.kt b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/extras.kt index dc31e427b20..c58d8187273 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/extras.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/extras.kt @@ -11,9 +11,11 @@ import com.google.protobuf.ByteString import com.google.protobuf.InvalidProtocolBufferException import org.jetbrains.kotlin.gradle.idea.proto.generated.IdeaExtrasProto import org.jetbrains.kotlin.gradle.idea.proto.generated.ideaExtrasProto -import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializer import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinSerializationContext -import org.jetbrains.kotlin.tooling.core.* +import org.jetbrains.kotlin.tooling.core.Extras +import org.jetbrains.kotlin.tooling.core.MutableExtras +import org.jetbrains.kotlin.tooling.core.toMutableExtras +import org.jetbrains.kotlin.tooling.core.withValue fun Extras.toByteArray(context: IdeaKotlinSerializationContext): ByteArray { return context.IdeaExtrasProto(this).toByteArray() @@ -35,8 +37,8 @@ internal fun IdeaKotlinSerializationContext.IdeaExtrasProto(extras: Extras): Ide val context = this return ideaExtrasProto { extras.entries.forEach { (key, value) -> + key as Extras.Key val serializer = context.extrasSerializationExtension.serializer(key) ?: return@forEach - serializer as IdeaKotlinExtrasSerializer val serialized = runCatching { serializer.serialize(context, value) ?: return@forEach }.getOrElse { exception -> logger.error("Failed to serialize $key, using ${serializer.javaClass.simpleName}", exception) return@forEach diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializationExtension.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializationExtension.kt index 4ec2a876b50..9dfa4eb2e72 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializationExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializationExtension.kt @@ -18,10 +18,10 @@ import org.jetbrains.kotlin.tooling.core.Extras * inside the implementation of the given [IdeaKotlinExtrasSerializer] */ interface IdeaKotlinExtrasSerializationExtension { - fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? + fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? object Empty : IdeaKotlinExtrasSerializationExtension { - override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? = null + override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? = null } } @@ -48,7 +48,7 @@ private class IdeaKotlinExtrasSerializationExtensionBuilderImpl : IdeaKotlinExtr private class IdeaKotlinExtrasSerializationExtensionImpl( private val map: Map, IdeaKotlinExtrasSerializer<*>> ) : IdeaKotlinExtrasSerializationExtension { - override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? { + override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? { @Suppress("unchecked_cast") return map[key] as? IdeaKotlinExtrasSerializer } diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializer.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializer.kt index ce43ba92df9..56ae91880f9 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializer.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializer.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.idea.serialize import kotlin.reflect.KClass -interface IdeaKotlinExtrasSerializer { +interface IdeaKotlinExtrasSerializer { fun serialize(context: IdeaKotlinSerializationContext, value: T): ByteArray? fun deserialize(context: IdeaKotlinSerializationContext, data: ByteArray): T? diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/serialize/TestIdeaExtrasSerializationExtension.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/serialize/TestIdeaExtrasSerializationExtension.kt index 8ec29abee3f..bd7affae7eb 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/serialize/TestIdeaExtrasSerializationExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/serialize/TestIdeaExtrasSerializationExtension.kt @@ -16,7 +16,7 @@ object TestIdeaExtrasSerializationExtension : IdeaKotlinExtrasSerializationExten val ignoredStringKey = extrasKeyOf("ignored") val anySerializableKey = extrasKeyOf("serializable") - override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? = when { + override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? = when { key == ignoredStringKey -> null key == anySerializableKey -> IdeaKotlinExtrasSerializer.javaIoSerializable() key.type == extrasTypeOf() -> IdeaKotlinStringExtrasSerializer diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeSerialization.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeSerialization.kt index 22d2cd1b92f..4ff96a780e7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeSerialization.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeSerialization.kt @@ -6,14 +6,14 @@ package org.jetbrains.kotlin.gradle.plugin.ide import org.gradle.api.logging.Logger -import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializer import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtension +import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializer import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinSerializationContext import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinSerializationLogger import org.jetbrains.kotlin.tooling.core.Extras internal fun IdeaKotlinSerializationContext( - logger: Logger, extrasSerializationExtensions: List + logger: Logger, extrasSerializationExtensions: List, ): IdeaKotlinSerializationContext { return IdeaKotlinSerializationContextImpl( extrasSerializationExtension = IdeaKotlinExtrasSerializationExtensionImpl(logger, extrasSerializationExtensions), @@ -23,7 +23,7 @@ internal fun IdeaKotlinSerializationContext( private class IdeaKotlinSerializationContextImpl( override val extrasSerializationExtension: IdeaKotlinExtrasSerializationExtension, - override val logger: IdeaKotlinSerializationLogger + override val logger: IdeaKotlinSerializationLogger, ) : IdeaKotlinSerializationContext @@ -31,9 +31,9 @@ private class IdeaKotlinSerializationContextImpl( private class IdeaKotlinExtrasSerializationExtensionImpl( private val logger: Logger, - private val extensions: List + private val extensions: List, ) : IdeaKotlinExtrasSerializationExtension { - override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? { + override fun serializer(key: Extras.Key): IdeaKotlinExtrasSerializer? { val serializers = extensions.mapNotNull { it.serializer(key) } if (serializers.size == 1) { diff --git a/libraries/tools/kotlin-tooling-core/api/kotlin-tooling-core.api b/libraries/tools/kotlin-tooling-core/api/kotlin-tooling-core.api index f0466bd3d64..bd80d0567de 100644 --- a/libraries/tools/kotlin-tooling-core/api/kotlin-tooling-core.api +++ b/libraries/tools/kotlin-tooling-core/api/kotlin-tooling-core.api @@ -127,6 +127,7 @@ public final class org/jetbrains/kotlin/tooling/core/ExtrasUtilsKt { public static final fun extrasOf ()Lorg/jetbrains/kotlin/tooling/core/Extras; public static final fun extrasOf ([Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Lorg/jetbrains/kotlin/tooling/core/Extras; public static final fun getOrPut (Lorg/jetbrains/kotlin/tooling/core/MutableExtras;Lorg/jetbrains/kotlin/tooling/core/Extras$Key;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static final fun getOrPutNullable (Lorg/jetbrains/kotlin/tooling/core/MutableExtras;Lorg/jetbrains/kotlin/tooling/core/Extras$Key;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public static final fun mutableExtrasOf ()Lorg/jetbrains/kotlin/tooling/core/MutableExtras; public static final fun mutableExtrasOf ([Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Lorg/jetbrains/kotlin/tooling/core/MutableExtras; public static final fun plus (Lorg/jetbrains/kotlin/tooling/core/Extras;Ljava/lang/Iterable;)Lorg/jetbrains/kotlin/tooling/core/Extras; diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/Extras.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/Extras.kt index c2a65e02245..6016b5f7f32 100644 --- a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/Extras.kt +++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/Extras.kt @@ -62,9 +62,9 @@ import java.io.Serializable * .toExtras() * ``` */ -interface Extras : Collection> { +interface Extras : Collection> { class Type @UnsafeApi("Use 'extrasTypeOf()' instead") @PublishedApi internal constructor( - internal val signature: String + internal val signature: String, ) : Serializable { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -85,7 +85,7 @@ interface Extras : Collection> { } /* Not implemented as data class to ensure more controllable binary compatibility */ - class Key @PublishedApi internal constructor( + class Key @PublishedApi internal constructor( val type: Type, val name: String? = null, ) : Serializable { @@ -116,8 +116,8 @@ interface Extras : Collection> { fun fromString(stableString: String): Key<*> { @OptIn(UnsafeApi::class) return if (stableString.contains(';')) { val split = stableString.split(';', limit = 2) - Key(Type(split[0]), split[1]) - } else Key(Type(stableString)) + Key(Type(split[0]), split[1]) + } else Key(Type(stableString)) } private const val serialVersionUID = 0L @@ -125,7 +125,7 @@ interface Extras : Collection> { } /* Not implemented as data class to ensure more controllable binary compatibility */ - class Entry(val key: Key, val value: T) : Serializable { + class Entry(val key: Key, val value: T) : Serializable { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Entry<*>) return false @@ -152,22 +152,22 @@ interface Extras : Collection> { val keys: Set> val entries: Set> - operator fun get(key: Key): T? + operator fun get(key: Key): T? operator fun contains(key: Key<*>): Boolean - override fun iterator(): Iterator> + override fun iterator(): Iterator> } interface MutableExtras : Extras { /** * @return The previous value or null if no previous value was set */ - operator fun set(key: Key, value: T): T? + operator fun set(key: Key, value: T): T? - fun put(entry: Entry): T? + fun put(entry: Entry): T? fun putAll(from: Iterable>) - fun remove(key: Key): T? + fun remove(key: Key): T? fun clear() } diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasImpl.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasImpl.kt index 7b0a683ef82..7f99cad9721 100644 --- a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasImpl.kt +++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasImpl.kt @@ -11,7 +11,7 @@ import java.io.Serializable @Suppress("unchecked_cast") internal class MutableExtrasImpl( - initialEntries: Iterable> = emptyList() + initialEntries: Iterable> = emptyList(), ) : MutableExtras, AbstractExtras(), Serializable { private val extras: MutableMap, Entry<*>> = @@ -28,11 +28,11 @@ internal class MutableExtrasImpl( override fun isEmpty(): Boolean = extras.isEmpty() - override fun set(key: Key, value: T): T? { + override fun set(key: Key, value: T): T? { return put(Entry(key, value)) } - override fun put(entry: Entry): T? { + override fun put(entry: Entry): T? { return extras.put(entry.key, entry)?.let { it.value as T } } @@ -40,11 +40,11 @@ internal class MutableExtrasImpl( this.extras.putAll(from.associateBy { it.key }) } - override fun get(key: Key): T? { + override fun get(key: Key): T? { return extras[key]?.let { it.value as T } } - override fun remove(key: Key): T? { + override fun remove(key: Key): T? { return extras.remove(key)?.let { it.value as T } } @@ -59,7 +59,7 @@ internal class MutableExtrasImpl( @Suppress("unchecked_cast") internal class ImmutableExtrasImpl private constructor( - private val extras: Map, Entry<*>> + private val extras: Map, Entry<*>>, ) : AbstractExtras(), Serializable { constructor(extras: Iterable>) : this(extras.associateBy { it.key }) @@ -73,7 +73,7 @@ internal class ImmutableExtrasImpl private constructor( override val entries: Set> = extras.values.toSet() - override fun get(key: Key): T? { + override fun get(key: Key): T? { return extras[key]?.let { it.value as T } } @@ -107,7 +107,7 @@ abstract class AbstractExtras : Extras { override fun containsAll(elements: Collection>): Boolean = entries.containsAll(elements) - override fun iterator(): Iterator> = entries.iterator() + override fun iterator(): Iterator> = entries.iterator() override fun equals(other: Any?): Boolean { if (other === this) return true @@ -135,11 +135,11 @@ internal object EmptyExtras : AbstractExtras(), Serializable { override fun isEmpty(): Boolean = true - override fun get(key: Key): T? = null + override fun get(key: Key): T? = null override fun contains(key: Key<*>): Boolean = false - override fun contains(element: Entry): Boolean = false + override fun contains(element: Entry<*>): Boolean = false @Suppress("unused") // Necessary for java.io.Serializable stability private const val serialVersionUID = 0L diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasProperty.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasProperty.kt index 48a222999e8..c686e7d1847 100644 --- a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasProperty.kt +++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasProperty.kt @@ -11,19 +11,19 @@ import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -interface ExtrasProperty { +interface ExtrasProperty { val key: Extras.Key } val Extras.Key.readWriteProperty get() = extrasReadWriteProperty(this) -fun Extras.Key.lazyProperty(factory: Receiver.() -> T) = extrasLazyProperty(this, factory) +fun Extras.Key.lazyProperty(factory: Receiver.() -> T) = extrasLazyProperty(this, factory) fun extrasReadWriteProperty(key: Extras.Key): ExtrasReadWriteProperty = object : ExtrasReadWriteProperty { override val key: Extras.Key = key } -fun extrasLazyProperty( +fun extrasLazyProperty( key: Extras.Key, factory: Receiver.() -> T, ): ExtrasLazyProperty = object : ExtrasLazyProperty { @@ -36,7 +36,7 @@ inline fun extrasReadWriteProperty(name: String? = null) = extrasReadWriteProperty(extrasKeyOf(name)) -inline fun extrasLazyProperty(name: String? = null, noinline factory: Receiver.() -> T) = +inline fun extrasLazyProperty(name: String? = null, noinline factory: Receiver.() -> T) = extrasLazyProperty(extrasKeyOf(name), factory) @@ -76,11 +76,11 @@ interface NotNullExtrasReadWriteProperty : ExtrasProperty, ReadWrite } } -interface ExtrasLazyProperty : ExtrasProperty, ReadWriteProperty { +interface ExtrasLazyProperty : ExtrasProperty, ReadWriteProperty { val factory: Receiver.() -> T override fun getValue(thisRef: Receiver, property: KProperty<*>): T { - return thisRef.extras.getOrPut(key) { thisRef.factory() } + return thisRef.extras.getOrPutNullable(key) { thisRef.factory() } } override fun setValue(thisRef: Receiver, property: KProperty<*>, value: T) { diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasUtils.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasUtils.kt index 71cbc157144..155e5216e0b 100644 --- a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasUtils.kt +++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasUtils.kt @@ -26,7 +26,7 @@ import kotlin.reflect.typeOf * extrasKeyOf("a") != extrasKeyOf("a") * ``` */ -inline fun extrasKeyOf(name: String? = null): Extras.Key = +inline fun extrasKeyOf(name: String? = null): Extras.Key = Extras.Key(extrasTypeOf(), name) fun emptyExtras(): Extras = EmptyExtras @@ -48,7 +48,7 @@ fun Iterable>.toExtras(): Extras = ImmutableExtrasImpl(this) fun Iterable>.toMutableExtras(): MutableExtras = MutableExtrasImpl(this) -infix fun Extras.Key.withValue(value: T): Extras.Entry = Extras.Entry(this, value) +infix fun Extras.Key.withValue(value: T): Extras.Entry = Extras.Entry(this, value) operator fun Extras.plus(entry: Extras.Entry<*>): Extras = ImmutableExtrasImpl(this.entries + entry) @@ -56,4 +56,11 @@ operator fun Extras.plus(entries: Iterable>): Extras = Immutable inline fun MutableExtras.getOrPut(key: Extras.Key, defaultValue: () -> T): T { return this[key] ?: defaultValue().also { this[key] = it } -} \ No newline at end of file +} + +inline fun MutableExtras.getOrPutNullable(key: Extras.Key, defaultValue: () -> T): T { + @Suppress("UNCHECKED_CAST") + if (key in this) return this[key] as T + return defaultValue().also { this[key] = it } +} + diff --git a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasPropertyTest.kt b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasPropertyTest.kt index 3bd3fb94ac8..9f09499132c 100644 --- a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasPropertyTest.kt +++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasPropertyTest.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.tooling.core +import java.util.concurrent.atomic.AtomicInteger import kotlin.test.* @Suppress("DEPRECATION") @@ -18,6 +19,7 @@ class ExtrasPropertyTest { private val keyA = extrasKeyOf("a") private val keyB = extrasKeyOf("b") + private val keyANullable = extrasKeyOf("a") private val Subject.readA: Int? by keyA.readProperty private val Subject.readB: Int? by keyB.readProperty @@ -171,4 +173,39 @@ class ExtrasPropertyTest { assertEquals("OK", subject2.lazyNullableString) assertEquals(listOf(subject1, subject2), lazyNullableStringInvocations) } + + @Test + fun `test - lazyProperty - with nullable key`() { + val subject = Subject() + val invocationCounter = AtomicInteger(0) + + with(object { + val Subject.lazyProperty by keyANullable.lazyProperty { + assertEquals(0, invocationCounter.getAndIncrement()) + null + } + }) { + assertNull(subject.lazyProperty) + assertNull(subject.lazyProperty) + assertEquals(1, invocationCounter.get()) + + invocationCounter.set(0) + assertEquals(null, Subject().lazyProperty) + assertEquals(1, invocationCounter.get()) + } + } + + @Test + fun `test - lazyProperty - with nullable key - set key to null manually`() { + with(object { + val Subject.lazyProperty by keyANullable.lazyProperty { + fail("Unexpected call to factory function") + } + }) { + val subject = Subject() + subject.extras[keyANullable] = null + assertNull(subject.lazyProperty) + assertTrue(keyANullable in subject.extras) + } + } } diff --git a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasTest.kt b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasTest.kt index 1e8e0eca05a..de3deea30d0 100644 --- a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasTest.kt +++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasTest.kt @@ -24,22 +24,22 @@ class ExtrasTest { assertNull(extras[extrasKeyOf("a")]) assertNull(extras[extrasKeyOf("b")]) - extras[extrasKeyOf()] = "22222" - assertEquals("22222", extras[extrasKeyOf()]) - assertNull(extras[extrasKeyOf("a")]) - assertNull(extras[extrasKeyOf("b")]) + extras[extrasKeyOf()] = "22222" + assertEquals("22222", extras[extrasKeyOf()]) + assertNull(extras[extrasKeyOf("a")]) + assertNull(extras[extrasKeyOf("b")]) extras[extrasKeyOf("a")] = "value a" - assertEquals("22222", extras[extrasKeyOf()]) - assertEquals("value a", extras[extrasKeyOf("a")]) + assertEquals("22222", extras[extrasKeyOf()]) + assertEquals("value a", extras[extrasKeyOf("a")]) assertNull(extras[extrasKeyOf("b")]) extras[extrasKeyOf("b")] = "value b" - assertEquals("22222", extras[extrasKeyOf()]) - assertEquals("value a", extras[extrasKeyOf("a")]) - assertEquals("value b", extras[extrasKeyOf("b")]) + assertEquals("22222", extras[extrasKeyOf()]) + assertEquals("value a", extras[extrasKeyOf("a")]) + assertEquals("value b", extras[extrasKeyOf("b")]) - assertNull(extras[extrasKeyOf("c")]) + assertNull(extras[extrasKeyOf("c")]) } @Test @@ -286,4 +286,46 @@ class ExtrasTest { extras ) } + + @Test + fun `test - extras - nullable key`() { + val empty = extrasOf() + assertNull(empty[extrasKeyOf()]) + assertNull(empty[extrasKeyOf()]) + + val nonEmpty = extrasOf( + extrasKeyOf() withValue "!", + extrasKeyOf() withValue "?" + ) + + assertEquals("!", nonEmpty[extrasKeyOf()]) + assertEquals("?", nonEmpty[extrasKeyOf()]) + + val containsNull = extrasOf( + extrasKeyOf() withValue "!", + extrasKeyOf() withValue null + ) + + assertEquals("!", containsNull[extrasKeyOf()]) + assertNull(containsNull[extrasKeyOf()]) + assertTrue(extrasKeyOf() in containsNull) + } + + @Test + fun `test - mutable extras - nullable key`() { + val extras = mutableExtrasOf() + assertNull(extras[extrasKeyOf()]) + assertNull(extras[extrasKeyOf()]) + + extras[extrasKeyOf()] = "!" + extras[extrasKeyOf()] = "?" + + assertEquals("!", extras[extrasKeyOf()]) + assertEquals("?", extras[extrasKeyOf()]) + + extras[extrasKeyOf()] = null + assertEquals("!", extras[extrasKeyOf()]) + assertNull(extras[extrasKeyOf()]) + assertTrue(extrasKeyOf() in extras) + } }