diff --git a/.idea/runConfigurations/Test__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml b/.idea/runConfigurations/Test__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml
deleted file mode 100644
index 6341d660eaf..00000000000
--- a/.idea/runConfigurations/Test__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- false
- true
- false
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Update__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml b/.idea/runConfigurations/Update__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml
deleted file mode 100644
index 805a22b7c4f..00000000000
--- a/.idea/runConfigurations/Update__Kotlin_Gradle_Plugin___idea___binary_compatibility.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
- true
- false
-
-
-
\ No newline at end of file
diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-gradle-plugin-idea.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-gradle-plugin-idea.txt
index 86d190f2c0e..1402dfa0e9e 100644
--- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-gradle-plugin-idea.txt
+++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-gradle-plugin-idea.txt
@@ -1,59 +1,3 @@
-public abstract class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer : java/io/Serializable {
- public static final field Companion Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer$Companion;
- public abstract fun contains (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;)Z
- public fun equals (Ljava/lang/Object;)Z
- public abstract fun get (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;)Ljava/lang/Object;
- public abstract fun getIds ()Ljava/util/Set;
- public final fun getOrThrow (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;)Ljava/lang/Object;
- public fun hashCode ()I
- public final fun isEmpty ()Z
- public final fun isNotEmpty ()Z
- public fun toString ()Ljava/lang/String;
-}
-
-public final class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer$Empty : org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer {
- public static final field INSTANCE Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer$Empty;
- public fun contains (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;)Z
- public fun get (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;)Ljava/lang/Object;
- public fun getIds ()Ljava/util/Set;
-}
-
-public final class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId : java/io/Serializable {
- public static final field Companion Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId$Companion;
- public fun (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType;Ljava/lang/String;)V
- public fun equals (Ljava/lang/Object;)Z
- public fun hashCode ()I
- public fun toString ()Ljava/lang/String;
-}
-
-public final class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey {
- public fun (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId;Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer;)V
- public fun equals (Ljava/lang/Object;)Z
- public fun hashCode ()I
-}
-
-public abstract interface class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer {
- public static final field Companion Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer$Companion;
- public abstract fun deserialize ([B)Ljava/lang/Object;
- public abstract fun serialize (Ljava/lang/Object;)[B
-}
-
-public final class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType : java/io/Serializable {
- public static final field Companion Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType$Companion;
- public fun (Ljava/lang/String;)V
- public fun equals (Ljava/lang/Object;)Z
- public fun hashCode ()I
- public fun toString ()Ljava/lang/String;
-}
-
-public final class org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelTypeKt {
- public static final fun externalModelTypeSignature (Lkotlin/reflect/KType;)Ljava/lang/String;
-}
-
-public abstract class org/jetbrains/kotlin/gradle/kpm/KotlinMutableExternalModelContainer : org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer {
- public abstract fun set (Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey;Ljava/lang/Object;)V
-}
-
public abstract interface class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinBinaryCoordinates : org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyCoordinates {
public abstract fun getGroup ()Ljava/lang/String;
public abstract fun getKotlinFragmentName ()Ljava/lang/String;
@@ -80,7 +24,7 @@ public abstract interface class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinD
public static final field DOCUMENTATION_BINARY_TYPE Ljava/lang/String;
public static final field SOURCES_BINARY_TYPE Ljava/lang/String;
public abstract fun getCoordinates ()Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyCoordinates;
- public abstract fun getExternal ()Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer;
+ public abstract fun getExtras ()Lorg/jetbrains/kotlin/tooling/core/Extras;
}
public final class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency$Companion {
@@ -101,7 +45,7 @@ public final class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyKt {
public abstract interface class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment : java/io/Serializable {
public abstract fun getCoordinates ()Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentCoordinates;
public abstract fun getDependencies ()Ljava/util/List;
- public abstract fun getExternal ()Lorg/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer;
+ public abstract fun getExtras ()Lorg/jetbrains/kotlin/tooling/core/Extras;
public abstract fun getLanguageSettings ()Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinLanguageSettings;
public abstract fun getPlatforms ()Ljava/util/Set;
public abstract fun getResourceDirectories ()Ljava/util/List;
diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-tooling-core.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-tooling-core.txt
index 8582e796e37..9ef0d034faa 100644
--- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-tooling-core.txt
+++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-tooling-core.txt
@@ -1,3 +1,28 @@
+public abstract class org/jetbrains/kotlin/tooling/core/AbstractExtras : org/jetbrains/kotlin/tooling/core/Extras {
+ public fun ()V
+ public synthetic fun add (Ljava/lang/Object;)Z
+ public fun add (Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Z
+ public fun addAll (Ljava/util/Collection;)Z
+ public fun clear ()V
+ public final fun contains (Ljava/lang/Object;)Z
+ public fun contains (Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Z
+ public fun contains (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;)Z
+ public fun containsAll (Ljava/util/Collection;)Z
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getSize ()I
+ public fun hashCode ()I
+ public fun isEmpty ()Z
+ public fun iterator ()Ljava/util/Iterator;
+ public fun remove (Ljava/lang/Object;)Z
+ public fun removeAll (Ljava/util/Collection;)Z
+ public fun removeIf (Ljava/util/function/Predicate;)Z
+ public fun retainAll (Ljava/util/Collection;)Z
+ public final fun size ()I
+ public fun toArray ()[Ljava/lang/Object;
+ public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
+ public fun toString ()Ljava/lang/String;
+}
+
public final class org/jetbrains/kotlin/tooling/core/ClosureKt {
public static final fun closureTo (Ljava/util/Set;Ljava/util/Set;Ljava/util/List;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/Set;
public static final fun createResultSet (I)Ljava/util/Set;
@@ -5,6 +30,52 @@ public final class org/jetbrains/kotlin/tooling/core/ClosureKt {
public static final fun createResultSet (Ljava/lang/Object;I)Ljava/util/Set;
}
+public abstract interface class org/jetbrains/kotlin/tooling/core/Extras : java/util/Collection, kotlin/jvm/internal/markers/KMappedMarker {
+ public abstract fun contains (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;)Z
+ public abstract fun get (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;)Ljava/lang/Object;
+ public abstract fun getEntries ()Ljava/util/Set;
+ public abstract fun getKeys ()Ljava/util/Set;
+ public abstract fun iterator ()Ljava/util/Iterator;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/Extras$Entry : java/io/Serializable {
+ public fun (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;Ljava/lang/Object;)V
+ public final fun component1 ()Lorg/jetbrains/kotlin/tooling/core/Extras$Key;
+ public final fun component2 ()Ljava/lang/Object;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getKey ()Lorg/jetbrains/kotlin/tooling/core/Extras$Key;
+ public final fun getValue ()Ljava/lang/Object;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/Extras$Key : java/io/Serializable {
+ public static final field Companion Lorg/jetbrains/kotlin/tooling/core/Extras$Key$Companion;
+ public fun (Lorg/jetbrains/kotlin/tooling/core/ReifiedTypeSignature;Ljava/lang/String;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getName ()Ljava/lang/String;
+ public final fun getStableString ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/Extras$Key$Companion {
+ public final fun fromString (Ljava/lang/String;)Lorg/jetbrains/kotlin/tooling/core/Extras$Key;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/ExtrasUtilsKt {
+ public static final fun emptyExtras ()Lorg/jetbrains/kotlin/tooling/core/Extras;
+ 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 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;
+ public static final fun plus (Lorg/jetbrains/kotlin/tooling/core/Extras;Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Lorg/jetbrains/kotlin/tooling/core/Extras;
+ public static final fun toExtras (Ljava/lang/Iterable;)Lorg/jetbrains/kotlin/tooling/core/Extras;
+ public static final fun toMutableExtras (Ljava/lang/Iterable;)Lorg/jetbrains/kotlin/tooling/core/MutableExtras;
+ public static final fun withValue (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;Ljava/lang/Object;)Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;
+}
+
public final class org/jetbrains/kotlin/tooling/core/KotlinToolingVersion : java/io/Serializable, java/lang/Comparable {
public fun (IIILjava/lang/String;)V
public synthetic fun compareTo (Ljava/lang/Object;)I
@@ -53,3 +124,27 @@ public final class org/jetbrains/kotlin/tooling/core/KotlinToolingVersionKt {
public static final fun toKotlinVersion (Lorg/jetbrains/kotlin/tooling/core/KotlinToolingVersion;)Lkotlin/KotlinVersion;
}
+public abstract interface class org/jetbrains/kotlin/tooling/core/MutableExtras : org/jetbrains/kotlin/tooling/core/Extras {
+ public abstract fun clear ()V
+ public abstract fun put (Lorg/jetbrains/kotlin/tooling/core/Extras$Entry;)Ljava/lang/Object;
+ public abstract fun putAll (Ljava/lang/Iterable;)V
+ public abstract fun remove (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;)Ljava/lang/Object;
+ public abstract fun set (Lorg/jetbrains/kotlin/tooling/core/Extras$Key;Ljava/lang/Object;)Ljava/lang/Object;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/ReifiedTypeSignature : java/io/Serializable {
+ public fun (Ljava/lang/String;)V
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getSignature ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/kotlin/tooling/core/ReifiedTypeSignatureKt {
+ public static final fun renderReifiedTypeSignatureString (Lkotlin/reflect/KType;)Ljava/lang/String;
+}
+
+public abstract interface annotation class org/jetbrains/kotlin/tooling/core/UnsafeApi : java/lang/annotation/Annotation {
+ public abstract fun message ()Ljava/lang/String;
+}
+
diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt
index c4a577c5642..2cfec87e851 100644
--- a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt
+++ b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
+import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.closure
import org.jetbrains.kotlin.tooling.core.withClosure
@@ -29,6 +30,8 @@ interface KotlinGradleFragment : KotlinModuleFragment, HasKotlinDependencies, Ko
val project: Project
get() = containingModule.project
+ val extras: MutableExtras
+
fun refines(other: KotlinGradleFragment)
fun refines(other: NamedDomainObjectProvider)
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts
index 2a671a63fc0..df3ed957d1c 100644
--- a/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts
@@ -11,6 +11,7 @@ kotlin.sourceSets.configureEach {
}
dependencies {
+ api(project(":kotlin-tooling-core"))
implementation(kotlinStdlib())
testImplementation(gradleApi())
testImplementation(gradleKotlinDsl())
@@ -24,6 +25,7 @@ dependencies {
testFixturesImplementation(gradleApi())
testFixturesImplementation(gradleKotlinDsl())
+ testFixturesImplementation(project(":kotlin-tooling-core"))
testFixturesImplementation(project(":kotlin-test:kotlin-test-junit"))
}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer.kt
deleted file mode 100644
index 9de6deb4f4e..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainer.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2010-2022 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 org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
-import java.io.Serializable
-
-sealed class KotlinExternalModelContainer : Serializable {
- abstract val ids: Set>
- abstract operator fun contains(key: KotlinExternalModelKey): Boolean
- abstract operator fun get(key: KotlinExternalModelKey): T?
- fun getOrThrow(key: KotlinExternalModelKey): T = get(key)
- ?: throw NoSuchElementException("Missing external model for ${key.id}")
-
- fun isEmpty(): Boolean = ids.isEmpty()
- fun isNotEmpty(): Boolean = ids.isNotEmpty()
-
- override fun equals(other: Any?): Boolean {
- if (other !is KotlinExternalModelContainer) return false
- if (this.isEmpty() && other.isEmpty()) return true
- return super.equals(other)
- }
-
- override fun hashCode(): Int {
- if (this.isEmpty()) return 0
- return super.hashCode()
- }
-
- @InternalKotlinGradlePluginApi
- companion object {
- fun mutable(): KotlinMutableExternalModelContainer = KotlinMutableExternalModelContainerImpl()
- }
-
- object Empty : KotlinExternalModelContainer() {
- override val ids: Set> = emptySet()
- override fun contains(key: KotlinExternalModelKey): Boolean = false
- override fun get(key: KotlinExternalModelKey): T? = null
-
- /* Necessary for stable serialization */
- @Suppress("unused")
- private const val serialVersionUID = 0L
- }
-
- override fun toString(): String {
- return "KotlinExternalModelContainer(${ids.joinToString("; ", "[", "]")}"
- }
-}
-
-sealed class KotlinMutableExternalModelContainer : KotlinExternalModelContainer() {
- abstract operator fun set(key: KotlinExternalModelKey, value: T)
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerImpl.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerImpl.kt
deleted file mode 100644
index 711f24be37f..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerImpl.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2010-2022 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 org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.WriteReplacedModel
-import java.io.Serializable
-
-@WriteReplacedModel(SerializedKotlinExternalModelContainerCarrier::class)
-internal class KotlinMutableExternalModelContainerImpl private constructor(
- private val values: MutableMap, Any>
-) : KotlinMutableExternalModelContainer() {
-
- constructor() : this(mutableMapOf())
-
- override val ids: Set>
- @Synchronized get() = values.keys.map { it.id }.toSet()
-
- @Synchronized
- override fun set(key: KotlinExternalModelKey, value: T) {
- values[key] = value
- }
-
- @Synchronized
- override fun contains(key: KotlinExternalModelKey): Boolean {
- return key in values
- }
-
- @Synchronized
- @Suppress("unchecked_cast")
- override fun get(key: KotlinExternalModelKey): T? {
- return values[key]?.let { it as T }
- }
-
- @Synchronized
- private fun writeReplace(): Any {
- return SerializedKotlinExternalModelContainerCarrier(serialize(values))
- }
-
- companion object {
- private const val serialVersionUID = 0L
- }
-}
-
-@WriteReplacedModel(SerializedKotlinExternalModelContainerCarrier::class)
-private class SerializedKotlinExternalModelContainer(
- private val serializedValues: MutableMap, ByteArray>
-) : KotlinExternalModelContainer(), Serializable {
-
- private val deserializedValues = mutableMapOf, Any>()
-
- override val ids: Set>
- @Synchronized get() = serializedValues.keys + deserializedValues.keys.map { it.id }
-
- @Synchronized
- override fun contains(key: KotlinExternalModelKey): Boolean {
- return key.id in serializedValues || key in deserializedValues
- }
-
- @Synchronized
- @Suppress("unchecked_cast")
- override fun get(key: KotlinExternalModelKey): T? {
- deserializedValues[key]?.let { return it as T }
- val serializedValue = serializedValues[key.id] ?: return null
- val deserializedValue = key.serializer?.deserialize(serializedValue) ?: return null
- deserializedValues[key] = deserializedValue
- serializedValues.remove(key.id)
- return deserializedValue
- }
-
- @Synchronized
- private fun writeReplace(): Any {
- return SerializedKotlinExternalModelContainerCarrier(serializedValues + serialize(deserializedValues))
- }
-
- companion object {
- private const val serialVersionUID = 0L
- }
-}
-
-private class SerializedKotlinExternalModelContainerCarrier(
- private val serializedValues: Map, ByteArray>
-) : Serializable {
-
- private fun readResolve(): Any {
- return SerializedKotlinExternalModelContainer(serializedValues.toMutableMap())
- }
-
- companion object {
- private const val serialVersionUID = 0L
- }
-}
-
-private fun serialize(values: Map, Any>): Map, ByteArray> {
- return values.filterKeys { it.serializer != null }
- .mapValues { (key, value) ->
- @Suppress("unchecked_cast")
- val serializer = checkNotNull(key.serializer) as KotlinExternalModelSerializer
- serializer.serialize(value)
- }.mapKeys { (key, _) -> key.id }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId.kt
deleted file mode 100644
index 3883eba07f3..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelId.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2010-2022 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("FunctionName")
-
-package org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
-import java.io.Serializable
-
-inline fun KotlinExternalModelId(disambiguationName: String? = null): KotlinExternalModelId {
- return KotlinExternalModelId(KotlinExternalModelType(), disambiguationName)
-}
-
-class KotlinExternalModelId @PublishedApi internal constructor(
- private val type: KotlinExternalModelType,
- private val disambiguationName: String? = null
-) : Serializable {
- override fun toString(): String {
- return if (disambiguationName == null) type.toString()
- else "$disambiguationName:$type"
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as KotlinExternalModelId<*>
- if (type != other.type) return false
- if (disambiguationName != other.disambiguationName) return false
- return true
- }
-
- override fun hashCode(): Int {
- var result = type.hashCode()
- result = 31 * result + (disambiguationName?.hashCode() ?: 0)
- return result
- }
-
- @InternalKotlinGradlePluginApi
- companion object {
- private const val serialVersionUID = 0L
- }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey.kt
deleted file mode 100644
index 1e95bde24cb..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelKey.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010-2022 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("FunctionName")
-
-package org.jetbrains.kotlin.gradle.kpm
-
-inline fun KotlinExternalModelKey(
- disambiguationName: String? = null, serializer: KotlinExternalModelSerializer? = null
-): KotlinExternalModelKey = KotlinExternalModelKey(KotlinExternalModelId(disambiguationName), serializer)
-
-inline fun KotlinExternalModelKey(serializer: KotlinExternalModelSerializer): KotlinExternalModelKey =
- KotlinExternalModelKey(KotlinExternalModelId(), serializer)
-
-class KotlinExternalModelKey @PublishedApi internal constructor(
- internal val id: KotlinExternalModelId,
- internal val serializer: KotlinExternalModelSerializer? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as KotlinExternalModelKey<*>
-
- if (id != other.id) return false
- if (serializer != other.serializer) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + (serializer?.hashCode() ?: 0)
- return result
- }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer.kt
deleted file mode 100644
index 34ab8a70888..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelSerializer.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2010-2022 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 org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
-import java.io.*
-
-interface KotlinExternalModelSerializer {
- fun serialize(value: T): ByteArray
- fun deserialize(data: ByteArray): T
-
- @InternalKotlinGradlePluginApi
- companion object {
- inline fun serializable(): KotlinExternalModelSerializer =
- serializable(T::class.java)
-
- fun serializable(clazz: Class): KotlinExternalModelSerializer =
- KotlinSerializableExternalModelSerializer(clazz)
- }
-}
-
-private class KotlinSerializableExternalModelSerializer(
- private val clazz: Class
-) : KotlinExternalModelSerializer {
- override fun serialize(value: T): ByteArray {
- return ByteArrayOutputStream().run {
- ObjectOutputStream(this).use { stream -> stream.writeObject(value) }
- toByteArray()
- }
- }
-
- override fun deserialize(data: ByteArray): T {
- return ObjectInputStream(ByteArrayInputStream(data)).use { stream ->
- clazz.cast(stream.readObject())
- }
- }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType.kt
deleted file mode 100644
index 37ed10e39bf..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelType.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2010-2022 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 org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
-import java.io.Serializable
-import kotlin.reflect.KClass
-import kotlin.reflect.KType
-import kotlin.reflect.typeOf
-
-@Suppress("FunctionName")
-inline fun KotlinExternalModelType(): KotlinExternalModelType {
- return KotlinExternalModelType(externalModelTypeSignature(typeOf()))
-}
-
-class KotlinExternalModelType* Used as phantom type */ @Suppress("unused") T: Any>
-@PublishedApi internal constructor(private val signature: String) : Serializable {
- override fun equals(other: Any?): Boolean {
- if (other !is KotlinExternalModelType<*>) return false
- if (other.signature != this.signature) return false
- return true
- }
-
- override fun hashCode(): Int = 31 * signature.hashCode()
- override fun toString(): String = "ExternalModelType($signature)"
-
- @InternalKotlinGradlePluginApi
- companion object {
- private const val serialVersionUID = 0L
- }
-}
-
-@PublishedApi
-internal fun externalModelTypeSignature(type: KType): String {
- require(!type.isMarkedNullable) { "Unexpected nullable type. Found $type" }
- require(type.arguments.isEmpty()) { "Parameterized types are not supported. Found $type" }
- val classifier = requireNotNull(type.classifier) { "Expected classifier. Found $type" }
- val clazz = (classifier as? KClass<*>) ?: throw IllegalArgumentException("Expected KClass classifier. Found $classifier")
- return clazz.java.name ?: throw IllegalArgumentException("Missing qualifiedName in $type")
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt
index a04ffbb7a60..65b8775147b 100644
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt
+++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt
@@ -7,17 +7,18 @@
package org.jetbrains.kotlin.gradle.kpm.idea
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.DOCUMENTATION_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.SOURCES_BINARY_TYPE
+import org.jetbrains.kotlin.tooling.core.Extras
+import org.jetbrains.kotlin.tooling.core.emptyExtras
import java.io.File
import java.io.Serializable
import java.util.*
sealed interface IdeaKotlinDependency : Serializable {
val coordinates: IdeaKotlinDependencyCoordinates?
- val external: KotlinExternalModelContainer
+ val extras: Extras
companion object {
const val CLASSPATH_BINARY_TYPE = "org.jetbrains.binary.type.classpath"
@@ -61,7 +62,7 @@ val IdeaKotlinResolvedBinaryDependency.isClasspathType get() = binaryType == CLA
data class IdeaKotlinFragmentDependencyImpl(
override val type: IdeaKotlinFragmentDependency.Type,
override val coordinates: IdeaKotlinFragmentCoordinates,
- override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty,
+ override val extras: Extras = emptyExtras()
) : IdeaKotlinFragmentDependency {
override fun toString(): String {
@@ -80,7 +81,7 @@ data class IdeaKotlinResolvedBinaryDependencyImpl(
override val coordinates: IdeaKotlinBinaryCoordinates?,
override val binaryType: String,
override val binaryFile: File,
- override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty
+ override val extras: Extras = emptyExtras()
) : IdeaKotlinResolvedBinaryDependency {
override fun toString(): String {
@@ -97,7 +98,7 @@ data class IdeaKotlinResolvedBinaryDependencyImpl(
data class IdeaKotlinUnresolvedBinaryDependencyImpl(
override val cause: String?,
override val coordinates: IdeaKotlinBinaryCoordinates?,
- override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty
+ override val extras: Extras = emptyExtras()
) : IdeaKotlinUnresolvedBinaryDependency {
@InternalKotlinGradlePluginApi
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt
index dc624576aa1..e024168c059 100644
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt
+++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.gradle.kpm.idea
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
+import org.jetbrains.kotlin.tooling.core.Extras
import java.io.Serializable
sealed interface IdeaKotlinFragment : Serializable {
@@ -15,7 +15,7 @@ sealed interface IdeaKotlinFragment : Serializable {
val dependencies: List
val sourceDirectories: List
val resourceDirectories: List
- val external: KotlinExternalModelContainer
+ val extras: Extras
}
val IdeaKotlinFragment.name get() = coordinates.fragmentName
@@ -28,7 +28,7 @@ data class IdeaKotlinFragmentImpl(
override val dependencies: List,
override val sourceDirectories: List,
override val resourceDirectories: List,
- override val external: KotlinExternalModelContainer
+ override val extras: Extras
) : IdeaKotlinFragment {
@InternalKotlinGradlePluginApi
@@ -36,4 +36,3 @@ data class IdeaKotlinFragmentImpl(
private const val serialVersionUID = 0L
}
}
-
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt
deleted file mode 100644
index 9fc5a6d0348..00000000000
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2010-2022 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 org.jetbrains.kotlin.gradle.kpm
-
-import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.deserialize
-import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.serialize
-import java.io.Serializable
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertNotSame
-import kotlin.test.assertNull
-
-class KotlinExternalModelContainerTest {
-
- private data class RetainedModel(val value: Int) : Serializable
- private data class UnretainedModel(val value: Int)
-
- private val retainedModelKey = KotlinExternalModelKey(KotlinExternalModelSerializer.serializable())
- private val retainedModelKeyFoo = KotlinExternalModelKey("foo", KotlinExternalModelSerializer.serializable())
- private val retainedModelKeyBar = KotlinExternalModelKey("bar", KotlinExternalModelSerializer.serializable())
-
- private val unretainedModelKey = KotlinExternalModelKey()
- private val unretainedModelKeyFoo = KotlinExternalModelKey("foo")
- private val unretainedModelKeyBar = KotlinExternalModelKey("bar")
-
- @Test
- fun `test - attach simple values`() {
- val container = KotlinExternalModelContainer.mutable()
- container[retainedModelKey] = RetainedModel(1)
- container[retainedModelKeyFoo] = RetainedModel(2)
- container[retainedModelKeyBar] = RetainedModel(3)
- container[unretainedModelKey] = UnretainedModel(4)
- container[unretainedModelKeyFoo] = UnretainedModel(5)
- container[unretainedModelKeyBar] = UnretainedModel(6)
-
- assertEquals(RetainedModel(1), container[retainedModelKey])
- assertEquals(RetainedModel(2), container[retainedModelKeyFoo])
- assertEquals(RetainedModel(3), container[retainedModelKeyBar])
- assertEquals(UnretainedModel(4), container[unretainedModelKey])
- assertEquals(UnretainedModel(5), container[unretainedModelKeyFoo])
- assertEquals(UnretainedModel(6), container[unretainedModelKeyBar])
- }
-
- @Test
- fun `test - accessing missing value`() {
- val container = KotlinExternalModelContainer.mutable()
- assertNull(container[retainedModelKey])
- assertNull(container[unretainedModelKey])
- }
-
- @Test
- fun `test - serializing container`() {
- val container = KotlinExternalModelContainer.mutable()
- container[retainedModelKey] = RetainedModel(1)
- container[retainedModelKeyFoo] = RetainedModel(2)
- container[retainedModelKeyBar] = RetainedModel(3)
- container[unretainedModelKey] = UnretainedModel(4)
- container[unretainedModelKeyFoo] = UnretainedModel(5)
- container[unretainedModelKeyBar] = UnretainedModel(6)
-
- val deserializedContainer = container.serialize().deserialize()
-
- assertEquals(RetainedModel(1), deserializedContainer[retainedModelKey])
- assertEquals(RetainedModel(2), deserializedContainer[retainedModelKeyFoo])
- assertEquals(RetainedModel(3), deserializedContainer[retainedModelKeyBar])
- assertNull(deserializedContainer[unretainedModelKey])
- assertNull(deserializedContainer[unretainedModelKeyFoo])
- assertNull(deserializedContainer[unretainedModelKeyBar])
- }
-
- @Test
- fun `test - serializing container twice`() {
- val container = KotlinExternalModelContainer.mutable()
- container[retainedModelKey] = RetainedModel(1)
- container[unretainedModelKey] = UnretainedModel(4)
-
- val deserializedContainer = container.serialize().deserialize()
- assertEquals(RetainedModel(1), deserializedContainer[retainedModelKey])
- assertNull(deserializedContainer[unretainedModelKey])
-
- val twiceDeserializedContainer = deserializedContainer.serialize().deserialize()
- assertEquals(RetainedModel(1), twiceDeserializedContainer[retainedModelKey])
- assertNull(twiceDeserializedContainer[unretainedModelKey])
- }
-
- @Test
- fun `test - accessing deserialized container without serializer`() {
- val container = KotlinExternalModelContainer.mutable()
- container[retainedModelKey] = RetainedModel(1)
- container[retainedModelKeyFoo] = RetainedModel(2)
-
- val deserializedContainer = container.serialize().deserialize()
- assertEquals(RetainedModel(1), deserializedContainer[retainedModelKey])
-
- /* Accessing something already deserialized without deserializer: Returning null, because the keys do not match */
- assertNull(deserializedContainer[KotlinExternalModelKey(retainedModelKey.id)])
-
- /* Accessing something not deserialized without deserializer: Cannot deserialize: Null to be lenient */
- assertNull(deserializedContainer[KotlinExternalModelKey(retainedModelKeyFoo.id)])
-
- /* Accessing something previously requested but now with deserializer */
- assertEquals(RetainedModel(2), deserializedContainer[retainedModelKeyFoo])
-
- /* Now accessing retainedModelFoo without serializer should behave like above */
- assertNull(deserializedContainer[KotlinExternalModelKey(retainedModelKeyFoo.id)])
- }
-
- @Test
- fun `test - empty containers are equal`() {
- val container1 = KotlinExternalModelContainer.mutable()
- val container2 = KotlinExternalModelContainer.mutable()
-
- assertNotSame(container1, container2)
- assertEquals(container1, container2)
- assertEquals(container2, container1)
- assertEquals(KotlinExternalModelContainer.Empty, container1)
- assertEquals(container1, KotlinExternalModelContainer.Empty)
- assertEquals(KotlinExternalModelContainer.Empty, container2)
- assertEquals(container2, KotlinExternalModelContainer.Empty)
-
- assertEquals(container1.hashCode(), container2.hashCode())
- assertEquals(container1.hashCode(), KotlinExternalModelContainer.Empty.hashCode())
- }
-}
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt
index c9a3497fdd9..77319fcfa1b 100644
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt
+++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
-@file:OptIn(InternalKotlinGradlePluginApi::class)
+@file:OptIn(InternalKotlinGradlePluginApi::class, ExternalVariantApi::class)
package org.jetbrains.kotlin.gradle.kpm.idea
@@ -13,10 +13,7 @@ import createProxyInstance
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.kotlin.dsl.create
import org.gradle.testfixtures.ProjectBuilder
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelKey
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelSerializer.Companion.serializable
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
-import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.deserialize
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.serialize
import org.jetbrains.kotlin.gradle.plugin.KotlinPm20PluginWrapper
@@ -24,6 +21,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinLinuxX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.jvm
+import org.jetbrains.kotlin.tooling.core.extrasKeyOf
import unwrapProxyInstance
import java.io.File
import java.io.Serializable
@@ -42,6 +40,9 @@ import kotlin.test.*
*/
class BackwardsCompatibilityDeserializationTest {
+ data class RetainedModel(val id: Int) : Serializable
+ data class UnretainedModel(val id: Int)
+
@Test
fun `test - simple project`() {
val (project, kotlinExtension) = createKpmProject()
@@ -79,22 +80,19 @@ class BackwardsCompatibilityDeserializationTest {
}
}
- @OptIn(ExternalVariantApi::class)
+ @Ignore("No serialisation for extras implemented, yet")
@Test
- fun `test - attaching serializable models`() {
- data class RetainedModel(val id: Int) : Serializable
- data class UnretainedModel(val id: Int)
-
- val retainedModelKey = KotlinExternalModelKey(serializable())
- val unretainedModelKey = KotlinExternalModelKey()
+ fun `test - attaching serializable extras`() {
+ val retainedModelKey = extrasKeyOf()
+ val unretainedModelKey = extrasKeyOf()
val project = ProjectBuilder.builder().build() as ProjectInternal
project.plugins.apply(KotlinPm20PluginWrapper::class.java)
/* Setup example project */
val kotlinExtension = project.extensions.getByType(KotlinPm20ProjectExtension::class.java)
- kotlinExtension.main.common.external[retainedModelKey] = RetainedModel(2411)
- kotlinExtension.main.common.external[unretainedModelKey] = UnretainedModel(510)
+ kotlinExtension.main.common.extras[retainedModelKey] = RetainedModel(2411)
+ kotlinExtension.main.common.extras[unretainedModelKey] = UnretainedModel(510)
val model = project.buildIdeaKotlinProjectModel()
val deserializedModel = deserializeModelWithBackwardsCompatibleClasses(model)
@@ -108,10 +106,10 @@ class BackwardsCompatibilityDeserializationTest {
run {
val deserializedCommonFragment = unwrapProxyInstance(deserializedCommonFragmentProxy)
- val external = deserializedCommonFragment.serialize().deserialize().external
- assertEquals(1, external.ids.size)
- assertEquals(RetainedModel(2411), external[retainedModelKey])
- assertNull(external[unretainedModelKey])
+ val extras = deserializedCommonFragment.serialize().deserialize().extras
+ assertEquals(1, extras.keys.size)
+ assertEquals(RetainedModel(2411), extras[retainedModelKey])
+ assertNull(extras[unretainedModelKey])
}
}
}
@@ -127,7 +125,7 @@ private fun getClasspathForBackwardsCompatibilityTest(): List {
return compatibilityTestClasspath.split(";").map { path -> File(path) }
.onEach { file -> if (!file.exists()) println("[WARNING] Missing $file") }
- .flatMap { file -> if(file.isDirectory) file.listFiles().orEmpty().toList() else listOf(file) }
+ .flatMap { file -> if (file.isDirectory) file.listFiles().orEmpty().toList() else listOf(file) }
}
private fun deserializeModelWithBackwardsCompatibleClasses(model: IdeaKotlinProjectModel): Any {
diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelObjectGraphTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelObjectGraphTest.kt
index ab706c71421..e21029a25b0 100644
--- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelObjectGraphTest.kt
+++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelObjectGraphTest.kt
@@ -5,6 +5,9 @@
package org.jetbrains.kotlin.gradle.kpm.idea
+import org.jetbrains.kotlin.tooling.core.AbstractExtras
+import org.jetbrains.kotlin.tooling.core.Extras
+import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.reflections.Reflections
@@ -14,10 +17,7 @@ import kotlin.reflect.KClass
import kotlin.reflect.KVisibility.PUBLIC
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
+import kotlin.test.*
@OptIn(ExperimentalStdlibApi::class)
@RunWith(Parameterized::class)
@@ -86,6 +86,13 @@ class IdeaKotlinProjectModelObjectGraphTest(private val node: KClass<*>, @Suppre
companion object {
private val reflections = Reflections("org.jetbrains.kotlin")
+ private val ignoredNodes = setOf(
+ /*
+ Extras interface and AbstractExtras are okay for now:
+ Let's check known implementations for correctness
+ */
+ Extras::class, MutableExtras::class, AbstractExtras::class
+ )
@JvmStatic
@Parameterized.Parameters(name = "{1}")
@@ -114,7 +121,14 @@ class IdeaKotlinProjectModelObjectGraphTest(private val node: KClass<*>, @Suppre
}
}
- return classes.map { clazz -> arrayOf(clazz, checkNotNull(clazz.simpleName)) }
+ fun KClass<*>.displayName() = java.name
+ .removePrefix("org.jetbrains.kotlin")
+ .removePrefix(".gradle.kpm")
+ .removePrefix(".")
+
+ return classes
+ .filter { it !in ignoredNodes }
+ .map { clazz -> arrayOf(clazz, checkNotNull(clazz.displayName())) }
}
private fun KClass<*>.resolveReachableClasses(): Set> {
diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidDsl.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidDsl.kt
index a0ddba9c48c..57c7e65c235 100644
--- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidDsl.kt
+++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidDsl.kt
@@ -3,17 +3,16 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
-@file:OptIn(InternalKotlinGradlePluginApi::class)
package org.jetbrains.kotlin.gradle.android
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelKey
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelSerializer
-import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
+import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
+import org.jetbrains.kotlin.tooling.core.extrasKeyOf
import java.io.File
import java.io.Serializable
-val androidDslKey = KotlinExternalModelKey(KotlinExternalModelSerializer.serializable())
+internal val androidDslKey = extrasKeyOf()
+val KotlinGradleFragment.androidDsl: AndroidDsl? get() = this.extras[androidDslKey]
class AndroidDsl : Serializable {
var compileSdk = 0
diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt
index e9e0e2295c8..ad6724e689e 100644
--- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt
+++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.android
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import org.gradle.api.attributes.Usage
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
-import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.kpm.external.project
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder.FragmentConstraint
@@ -19,16 +18,14 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.containingVariants
-@OptIn(ExternalVariantApi::class)
val isAndroidFragment = FragmentConstraint { fragment ->
- fragment.containingVariants.all { variant -> androidDslKey in variant.external }
+ fragment.containingVariants.all { variant -> variant.androidDsl != null }
}
-@OptIn(ExternalVariantApi::class)
val isAndroidAndJvmSharedFragment = FragmentConstraint constraint@{ fragment ->
val variants = fragment.containingVariants
if (variants.any { it.platformType != KotlinPlatformType.jvm }) return@constraint false
- variants.any { androidDslKey in it.external } && variants.any { androidDslKey !in it.external }
+ variants.any { it.androidDsl != null } && variants.any { it.androidDsl == null }
}
@OptIn(ExternalVariantApi::class, InternalKotlinGradlePluginApi::class)
diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt
index 4125ecd7c18..9b0459b8e1f 100644
--- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt
+++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt
@@ -17,7 +17,6 @@ import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMEN
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.createExternalJvmVariant
-import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
@@ -88,6 +87,5 @@ fun KotlinGradleModule.createKotlinAndroidVariant(androidVariant: BaseVariant) {
val androidDsl = AndroidDsl()
androidDsl.androidManifest = project.file("AndroidManifest.xml")
androidDsl.compileSdk = 23
- androidCommon.external[androidDslKey] = androidDsl
- kotlinVariant.external[androidDslKey] = androidDsl
+ kotlinVariant.extras[androidDslKey] = androidDsl
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt
index 12bf564b5fe..81404a3de8d 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.kpm.external
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
-import org.jetbrains.kotlin.gradle.kpm.KotlinMutableExternalModelContainer
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
@@ -36,6 +35,3 @@ fun KotlinGradleModule.createExternalJvmVariant(
val KotlinGradleVariantInternal.compilationData
get() = this.compilationData
-@ExternalVariantApi
-val KotlinGradleFragment.external: KotlinMutableExternalModelContainer
- get() = (this as KotlinGradleFragmentInternal).external
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt
index 351771dc38d..3da89fee990 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.gradle.kpm.idea
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
+import org.jetbrains.kotlin.tooling.core.emptyExtras
internal fun IdeaKotlinProjectModelBuildingContext.IdeaKotlinFragment(fragment: KotlinGradleFragment): IdeaKotlinFragment {
return if (fragment is KotlinGradleVariant) buildIdeaKotlinVariant(fragment)
@@ -20,8 +20,8 @@ private fun IdeaKotlinProjectModelBuildingContext.buildIdeaKotlinFragment(fragme
languageSettings = IdeaKotlinLanguageSettings(fragment.languageSettings),
dependencies = dependencyResolver.resolve(fragment).toList(),
sourceDirectories = fragment.kotlinSourceRoots.sourceDirectories.files.toList().map { file -> IdeaKotlinSourceDirectoryImpl(file) },
- resourceDirectories = emptyList(),
- external = (fragment as? KotlinGradleFragmentInternal)?.external ?: KotlinExternalModelContainer.Empty
+ resourceDirectories = emptyList(), // TODO
+ extras = emptyExtras() // TODO: Requires more sophisticated serialization
)
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt
index 591a0fe5e3a..3a045d778b2 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt
@@ -11,8 +11,6 @@ import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.Provider
import org.gradle.util.ConfigureUtil
-import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
-import org.jetbrains.kotlin.gradle.kpm.KotlinMutableExternalModelContainer
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
@@ -23,6 +21,8 @@ import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecks
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheckWhenEvaluated
import org.jetbrains.kotlin.project.model.KotlinModuleDependency
+import org.jetbrains.kotlin.tooling.core.MutableExtras
+import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
import javax.inject.Inject
open class KotlinGradleFragmentInternal @Inject constructor(
@@ -37,13 +37,13 @@ open class KotlinGradleFragmentInternal @Inject constructor(
final override val project: Project // overriding with final to avoid warnings
get() = super.project
+ override val extras: MutableExtras = mutableExtrasOf()
+
// TODO pull up to KotlinModuleFragment
// FIXME apply to compilation
// FIXME check for consistency
override val languageSettings: LanguageSettingsBuilder = DefaultLanguageSettingsBuilder()
- internal val external: KotlinMutableExternalModelContainer = KotlinExternalModelContainer.mutable()
-
override fun refines(other: KotlinGradleFragment) {
checkCanRefine(other)
refines(containingModule.fragments.named(other.name))
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/LegacyMappedVariant.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/LegacyMappedVariant.kt
index afa52640970..183af0fdfb2 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/LegacyMappedVariant.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/LegacyMappedVariant.kt
@@ -27,6 +27,8 @@ import org.jetbrains.kotlin.gradle.utils.filesProvider
import org.jetbrains.kotlin.project.model.KotlinAttributeKey
import org.jetbrains.kotlin.project.model.KotlinModuleDependency
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
+import org.jetbrains.kotlin.tooling.core.MutableExtras
+import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
internal open class LegacyMappedVariant(
internal val compilation: KotlinCompilation<*>,
@@ -86,6 +88,8 @@ internal open class LegacyMappedVariant(
override val languageSettings: LanguageSettingsBuilder
get() = compilation.defaultSourceSet.languageSettings
+ override val extras: MutableExtras = mutableExtrasOf()
+
override fun refines(other: KotlinGradleFragment) {
fragmentForDefaultSourceSet.refines(other)
}
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
new file mode 100644
index 00000000000..dd777de84a7
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/Extras.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+import org.jetbrains.kotlin.tooling.core.Extras.Entry
+import org.jetbrains.kotlin.tooling.core.Extras.Key
+import java.io.Serializable
+
+/**
+ * A generic container holding typed and scoped values.
+ * ### Attaching and getting simple typed values:
+ * ```kotlin
+ * val extras = mutableExtrasOf()
+ * extras[extrasKeyOf()] = 42 // Attach arbitrary Int value
+ * extras[extrasKeyOf()] = "Hello" // Attach arbitrary String value
+ *
+ * extras[extrasKeyOf()] // -> returns 42
+ * extras[extrasKeyOf] // -> returns "Hello"
+ * ```
+ *
+ * ### Attaching multiple values with the same type by naming the keys
+ * ```kotlin
+ * val extras = mutableExtrasOf()
+ * extras[extrasKeyOf("a")] = 1 // Attach Int with name 'a'
+ * extras[extrasKeyOf("b")] = 2 // Attach Int with name 'b'
+ *
+ * extras[extrasKeyOf("a")] // -> returns 1
+ * extras[extrasKeyOf("b")] // -> returns 2
+ * ```
+ *
+ * ### Creating immutable extras
+ * ```kotlin
+ * val extras = extrasOf(
+ * extrasKeyOf() withValue 1,
+ * extrasKeyOf() withValue "Hello"
+ * )
+ * ```
+ *
+ * ### Converting to immutable extras
+ * ```kotlin
+ * val extras = mutableExtrasOf(
+ * extrasKeyOf() withValue 0
+ * )
+ *
+ * // Captures the content, similar to `.toList()` or `.toSet()`
+ * val immutableExtras = extras.toExtras()
+ * ```
+ *
+ * ### Use case example: Filtering Extras
+ * ```kotlin
+ * val extras = extrasOf(
+ * extrasKeyOf() withValue 0,
+ * extrasKeyOf("keep") withValue 1,
+ * extrasKeyOf() withValue "Hello"
+ * )
+ *
+ * val filteredExtras = extras
+ * .filter { (key, value) -> key.id.name == "keep" || value is String }
+ * .toExtras()
+ * ```
+ */
+interface Extras : Collection> {
+ /* Not implemented as data class to ensure more controllable binary compatibility */
+ class Key @PublishedApi internal constructor(
+ internal val type: ReifiedTypeSignature,
+ val name: String? = null,
+ ) : Serializable {
+
+ val stableString: String
+ get() {
+ return if (name == null) type.signature
+ else "${type.signature};$name"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Key<*>) return false
+ if (name != other.name) return false
+ if (type != other.type) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = name?.hashCode() ?: 0
+ result = 31 * result + type.hashCode()
+ return result
+ }
+
+ override fun toString(): String = stableString
+
+ companion object {
+ fun fromString(stableString: String): Key<*> {
+ @OptIn(UnsafeApi::class) return if (stableString.contains(';')) {
+ val split = stableString.split(';', limit = 2)
+ Key(ReifiedTypeSignature(split[0]), split[1])
+ } else Key(ReifiedTypeSignature(stableString))
+ }
+
+ private const val serialVersionUID = 0L
+ }
+ }
+
+ /* Not implemented as data class to ensure more controllable binary compatibility */
+ 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
+ if (other.key != key) return false
+ if (other.value != value) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = key.hashCode()
+ result = 31 * result + value.hashCode()
+ return result
+ }
+
+ override fun toString(): String = "$key=$value"
+
+ operator fun component1() = key
+ operator fun component2() = value
+
+ internal companion object {
+ private const val serialVersionUID = 0L
+ }
+ }
+
+ val keys: Set>
+ val entries: Set>
+ operator fun get(key: Key): T?
+ operator fun contains(key: Key<*>): Boolean
+ 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?
+
+ fun put(entry: Entry): T?
+
+ fun putAll(from: Iterable>)
+
+ 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
new file mode 100644
index 00000000000..7b0a683ef82
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasImpl.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+import org.jetbrains.kotlin.tooling.core.Extras.Entry
+import org.jetbrains.kotlin.tooling.core.Extras.Key
+import java.io.Serializable
+
+@Suppress("unchecked_cast")
+internal class MutableExtrasImpl(
+ initialEntries: Iterable> = emptyList()
+) : MutableExtras, AbstractExtras(), Serializable {
+
+ private val extras: MutableMap, Entry<*>> =
+ initialEntries.associateByTo(mutableMapOf()) { it.key }
+
+ override val keys: Set>
+ get() = extras.keys.toSet()
+
+ override val entries: Set>
+ get() = extras.values.toSet()
+
+ override val size: Int
+ get() = extras.size
+
+ override fun isEmpty(): Boolean = extras.isEmpty()
+
+ override fun set(key: Key, value: T): T? {
+ return put(Entry(key, value))
+ }
+
+ override fun put(entry: Entry): T? {
+ return extras.put(entry.key, entry)?.let { it.value as T }
+ }
+
+ override fun putAll(from: Iterable>) {
+ this.extras.putAll(from.associateBy { it.key })
+ }
+
+ override fun get(key: Key): T? {
+ return extras[key]?.let { it.value as T }
+ }
+
+ override fun remove(key: Key): T? {
+ return extras.remove(key)?.let { it.value as T }
+ }
+
+ override fun clear() {
+ extras.clear()
+ }
+
+ internal companion object {
+ private const val serialVersionUID = 0L
+ }
+}
+
+@Suppress("unchecked_cast")
+internal class ImmutableExtrasImpl private constructor(
+ private val extras: Map, Entry<*>>
+) : AbstractExtras(), Serializable {
+ constructor(extras: Iterable>) : this(extras.associateBy { it.key })
+
+ constructor(extras: Array>) : this(extras.associateBy { it.key })
+
+ override val keys: Set> = extras.keys
+
+ override fun isEmpty(): Boolean = extras.isEmpty()
+
+ override val size: Int = extras.size
+
+ override val entries: Set> = extras.values.toSet()
+
+ override fun get(key: Key): T? {
+ return extras[key]?.let { it.value as T }
+ }
+
+ internal companion object {
+ private const val serialVersionUID = 0L
+ }
+
+ /* Replace during serialization */
+ private fun writeReplace(): Any = Surrogate(entries)
+
+ private class Surrogate(private val entries: Set>) : Serializable {
+ fun readResolve(): Any = ImmutableExtrasImpl(entries)
+
+ private companion object {
+ private const val serialVersionUID = 0L
+ }
+ }
+}
+
+abstract class AbstractExtras : Extras {
+
+ override val size: Int get() = keys.size
+
+ override fun isEmpty(): Boolean = keys.isEmpty()
+
+ override fun contains(key: Key<*>): Boolean = key in keys
+
+ override fun contains(element: Entry<*>): Boolean =
+ entries.contains(element)
+
+ override fun containsAll(elements: Collection>): Boolean =
+ entries.containsAll(elements)
+
+ override fun iterator(): Iterator> = entries.iterator()
+
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ if (other !is Extras) return false
+ if (other.entries != this.entries) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return 31 * entries.hashCode()
+ }
+
+ override fun toString(): String {
+ return "Extras($entries)"
+ }
+}
+
+internal object EmptyExtras : AbstractExtras(), Serializable {
+
+ override val size: Int = 0
+
+ override val keys: Set> = emptySet()
+
+ override val entries: Set> = emptySet()
+
+ override fun isEmpty(): Boolean = true
+
+ override fun get(key: Key): T? = null
+
+ override fun contains(key: Key<*>): Boolean = false
+
+ override fun contains(element: Entry): Boolean = false
+
+ @Suppress("unused") // Necessary for java.io.Serializable stability
+ private const val serialVersionUID = 0L
+
+ /* Ensure single instance, even after deserialization */
+ private fun readResolve(): Any = EmptyExtras
+}
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
new file mode 100644
index 00000000000..6324691ac1a
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasUtils.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+/**
+ * Creates a value based key for accessing any [Extras] container
+ *
+ * @param T The type of data that is stored in the extras container
+ * ```kotlin
+ * extrasKeyOf() == extrasKeyOf()
+ * extrasKeyOf() != extrasKeyOf()
+ * extrasKeyOf>() == extrasKeyOf>()
+ * extrasKeyOf>() != extrasKeyOf>()
+ * ```
+ *
+ * @param name This typed keys can also be distinguished with an additional name. In this case
+ * ```kotlin
+ * extrasKeyOf() != extrasKeyOf("a")
+ * extrasKeyOf("a") == extrasKeyOf("a")
+ * extrasKeyOf("b") != extrasKeyOf("a")
+ * extrasKeyOf("a") != extrasKeyOf("a")
+ * ```
+ */
+inline fun extrasKeyOf(name: String? = null): Extras.Key =
+ Extras.Key(ReifiedTypeSignature(), name)
+
+fun emptyExtras(): Extras = EmptyExtras
+
+fun extrasOf() = emptyExtras()
+
+fun extrasOf(vararg entries: Extras.Entry<*>): Extras = if (entries.isEmpty()) EmptyExtras else ImmutableExtrasImpl(entries)
+
+fun mutableExtrasOf(): MutableExtras = MutableExtrasImpl()
+
+fun mutableExtrasOf(vararg entries: Extras.Entry<*>): MutableExtras = MutableExtrasImpl(entries.toList())
+
+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)
+
+operator fun Extras.plus(entry: Extras.Entry<*>): Extras = ImmutableExtrasImpl(this.entries + entry)
+
+operator fun Extras.plus(entries: Iterable>): Extras = ImmutableExtrasImpl(this.entries + entries)
+
diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ReifiedTypeSignature.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ReifiedTypeSignature.kt
new file mode 100644
index 00000000000..f676996f341
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/ReifiedTypeSignature.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010-2022 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("FunctionName")
+
+package org.jetbrains.kotlin.tooling.core
+
+import java.io.Serializable
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.KVariance
+import kotlin.reflect.typeOf
+
+@PublishedApi
+internal inline fun ReifiedTypeSignature(): ReifiedTypeSignature {
+ @OptIn(UnsafeApi::class, ExperimentalStdlibApi::class)
+ return ReifiedTypeSignature(renderReifiedTypeSignatureString(typeOf()))
+}
+
+@PublishedApi
+internal class ReifiedTypeSignature
+@UnsafeApi("Use 'reifiedTypeSignatureOf' instead")
+@PublishedApi internal constructor(val signature: String) : Serializable {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ReifiedTypeSignature<*>) return false
+ if (other.signature != this.signature) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return 31 * signature.hashCode()
+ }
+
+ override fun toString(): String = signature
+
+ internal companion object {
+ private const val serialVersionUID = 0L
+ }
+}
+
+@PublishedApi
+@UnsafeApi("Use 'ReifiedTypeSignature' instead")
+internal fun renderReifiedTypeSignatureString(type: KType): String {
+ val classifier = type.classifier ?: throw IllegalArgumentException("Expected denotable type, found $type")
+ val classifierClass = classifier as? KClass<*> ?: throw IllegalArgumentException("Expected class type, found $type")
+ val classifierName = classifierClass.qualifiedName ?: throw IllegalArgumentException(
+ "Expected non-anonymous, non-local type, found $type"
+ )
+
+ /* Fast path: Just a non-nullable class without arguments */
+ if (type.arguments.isEmpty() && !type.isMarkedNullable) {
+ return classifierName
+ }
+
+ return buildString {
+ append(classifierName)
+ if (type.arguments.isNotEmpty()) {
+ append("<")
+ type.arguments.forEachIndexed forEach@{ index, argument ->
+ if (argument.type == null || argument.variance == null) {
+ append("*")
+ return@forEach
+ }
+ when (argument.variance) {
+ KVariance.IN -> append("in ")
+ KVariance.OUT -> append("out ")
+ else -> Unit
+ }
+
+ append(renderReifiedTypeSignatureString(argument.type ?: return@forEach))
+ if (index != type.arguments.lastIndex) {
+ append(", ")
+ }
+ }
+ append(">")
+ }
+ if (type.isMarkedNullable) {
+ append("?")
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/UnsafeApi.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/UnsafeApi.kt
new file mode 100644
index 00000000000..1626cc602a8
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/UnsafeApi.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR, message = "Use safe API counterpart")
+annotation class UnsafeApi(val message: String = "")
diff --git a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasIdFromStringTest.kt b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasIdFromStringTest.kt
new file mode 100644
index 00000000000..e13049a23e4
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasIdFromStringTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ExtrasKeyStableStringTest {
+ @Test
+ fun `test - sample 0`() {
+ assertEquals("kotlin.String", extrasKeyOf().stableString)
+ }
+
+ @Test
+ fun `test - sample 1`() {
+ assertEquals("kotlin.String;withName", extrasKeyOf("withName").stableString)
+ }
+
+ @Test
+ fun `test - sample 2`() {
+ assertEquals(extrasKeyOf(), Extras.Key.fromString(extrasKeyOf().stableString))
+ }
+
+ @Test
+ fun `test - sample 3`() {
+ assertEquals(extrasKeyOf("withName"), Extras.Key.fromString(extrasKeyOf("withName").stableString))
+ }
+
+ @Test
+ fun `test - sample 4`() {
+ assertEquals(
+ extrasKeyOf>>("withName"),
+ Extras.Key.fromString(
+ extrasKeyOf>>("withName").stableString
+ )
+ )
+ }
+}
diff --git a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasSerializableTest.kt b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasSerializableTest.kt
new file mode 100644
index 00000000000..e7d1e0ce0ed
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasSerializableTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2022 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 org.jetbrains.kotlin.tooling.core
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotSame
+import kotlin.test.assertSame
+
+class ExtrasSerializableTest {
+
+ @Test
+ fun `test - serialize deserialize - MutableExtras`() {
+ val extras = mutableExtrasOf(extrasKeyOf() withValue "hello")
+ val deserializedExtras = extras.serialize().deserialize()
+ assertNotSame(extras, deserializedExtras)
+ assertEquals(extras, deserializedExtras)
+ }
+
+ @Test
+ fun `test - serialize deserialize - EmptyExtras`() {
+ assertSame(emptyExtras(), emptyExtras())
+ assertSame(emptyExtras(), emptyExtras().serialize().deserialize())
+ }
+
+ @Test
+ fun `test - serialize deserialize - ImmutableExtras`() {
+ val extras = extrasOf(extrasKeyOf() withValue "hello")
+ val deserializedExtras = extras.serialize().deserialize()
+ assertNotSame(extras, deserializedExtras)
+ assertEquals(extras, deserializedExtras)
+ assertSame(extras.javaClass, deserializedExtras.javaClass)
+ }
+
+ private fun Any.serialize(): ByteArray {
+ return ByteArrayOutputStream().use { byteArrayOutputStream ->
+ ObjectOutputStream(byteArrayOutputStream).use { objectOutputStream -> objectOutputStream.writeObject(this) }
+ byteArrayOutputStream.toByteArray()
+ }
+ }
+
+ private inline fun ByteArray.deserialize(): T {
+ return ObjectInputStream(ByteArrayInputStream(this)).use { stream ->
+ stream.readObject() as T
+ }
+ }
+}
+
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
new file mode 100644
index 00000000000..1e8e0eca05a
--- /dev/null
+++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/ExtrasTest.kt
@@ -0,0 +1,289 @@
+@file:Suppress("RemoveExplicitTypeArguments")
+
+package org.jetbrains.kotlin.tooling.core
+
+import kotlin.test.*
+
+class ExtrasTest {
+
+ @Test
+ fun `test - isEmpty`() {
+ assertTrue(mutableExtrasOf().isEmpty())
+ assertTrue(extrasOf().isEmpty())
+ assertTrue(emptyExtras().isEmpty())
+
+ assertFalse(mutableExtrasOf().isNotEmpty())
+ assertFalse(extrasOf().isNotEmpty())
+ assertFalse(emptyExtras().isNotEmpty())
+ }
+
+ @Test
+ fun `test - add and get`() {
+ val extras = mutableExtrasOf()
+ assertNull(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")])
+ 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")])
+
+ assertNull(extras[extrasKeyOf("c")])
+ }
+
+ @Test
+ fun `test - ids`() {
+ val stringKey = extrasKeyOf()
+ val stringKeyA = extrasKeyOf("a")
+ val intKey = extrasKeyOf()
+ val intKeyA = extrasKeyOf("a")
+
+ val keys = setOf(stringKey, stringKeyA, intKey, intKeyA)
+
+ val extras = extrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ val mutableExtras = mutableExtrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ assertEquals(keys, extras.keys)
+ assertEquals(keys, mutableExtras.keys)
+ }
+
+ @Test
+ fun `test - keys`() {
+ val stringKey = extrasKeyOf()
+ val stringKeyA = extrasKeyOf("a")
+ val intKey = extrasKeyOf()
+ val intKeyA = extrasKeyOf("a")
+
+ val keys = listOf(stringKey, stringKeyA, intKey, intKeyA)
+
+ val extras = extrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ val mutableExtras = mutableExtrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ assertEquals(keys, extras.entries.map { it.key })
+ assertEquals(keys, mutableExtras.entries.map { it.key })
+ }
+
+ @Test
+ fun `test - equality`() {
+ val stringKey = extrasKeyOf()
+ val stringKeyA = extrasKeyOf("a")
+ val intKey = extrasKeyOf()
+ val intKeyA = extrasKeyOf("a")
+
+ val extras = extrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ val mutableExtras = mutableExtrasOf(
+ stringKey withValue "string",
+ stringKeyA withValue "stringA",
+ intKey withValue 1,
+ intKeyA withValue 2
+ )
+
+ assertEquals(extras, mutableExtras)
+ assertEquals(extras, mutableExtras.toExtras())
+ assertEquals(extras.toExtras(), mutableExtras.toExtras())
+ assertEquals(extras.toMutableExtras(), mutableExtras.toExtras())
+ assertEquals(extras.toMutableExtras(), mutableExtras)
+ assertEquals(mutableExtras, extras)
+
+ assertNotEquals(extras, extras + extrasKeyOf("b").withValue(2))
+ assertNotEquals(mutableExtras, mutableExtras + extrasKeyOf("b").withValue(2))
+ }
+
+ @Test
+ fun `test - equality - empty`() {
+ val extras0 = extrasOf()
+ val extras1 = mutableExtrasOf()
+ val extras2 = mutableExtrasOf()
+
+ assertNotSame(extras0, extras1)
+ assertNotSame(extras1, extras2)
+ assertEquals(extras0, extras1)
+ assertEquals(extras1, extras2)
+ assertEquals(extras2, extras1)
+ assertEquals(emptyExtras(), extras0)
+ assertEquals(emptyExtras(), extras1)
+ assertEquals(extras0, emptyExtras())
+ assertEquals(extras1, emptyExtras())
+ assertEquals(emptyExtras(), extras2)
+ assertEquals(extras2, emptyExtras())
+
+ assertEquals(extras0.hashCode(), extras1.hashCode())
+ assertEquals(extras1.hashCode(), extras2.hashCode())
+ assertEquals(extras1.hashCode(), emptyExtras().hashCode())
+ }
+
+ @Test
+ fun `test - overwrite - mutable`() {
+ val key = extrasKeyOf()
+ val extras = mutableExtrasOf()
+ assertNull(extras.set(key, 1))
+ assertEquals(1, extras[key])
+ assertEquals(1, extras.set(key, 2))
+ assertEquals(2, extras[key])
+ }
+
+ @Test
+ fun `test - overwrite - immutable`() {
+ val key = extrasKeyOf()
+ val extras0 = extrasOf()
+ val extras1 = extras0 + (key withValue 1)
+ assertNull(extras0[key])
+ assertEquals(1, extras1[key])
+
+ val extras2 = extras1 + (key withValue 2)
+ assertNull(extras0[key])
+ assertEquals(1, extras1[key])
+ assertEquals(2, extras2[key])
+ }
+
+ @Test
+ fun `test - key equality`() {
+ assertEquals(extrasKeyOf(), extrasKeyOf())
+ assertEquals(extrasKeyOf>(), extrasKeyOf>())
+ assertEquals(extrasKeyOf("a"), extrasKeyOf("a"))
+ assertNotEquals>(extrasKeyOf(), extrasKeyOf())
+ assertNotEquals>(extrasKeyOf("a"), extrasKeyOf())
+ assertNotEquals>(extrasKeyOf("a"), extrasKeyOf("b"))
+ }
+
+ @Test
+ fun `test - add two extras`() {
+ val keyA = extrasKeyOf("a")
+ val keyB = extrasKeyOf("b")
+ val keyC = extrasKeyOf("c")
+ val keyD = extrasKeyOf()
+ val keyE = extrasKeyOf()
+
+ val extras1 = extrasOf(
+ keyA withValue 0,
+ keyB withValue 1,
+ keyC withValue 2,
+ keyD withValue 3
+ )
+
+ val extras2 = extrasOf(
+ keyC withValue 4,
+ keyE withValue 5
+ )
+
+ val combinedExtras = extras1 + extras2
+
+ assertEquals(
+ extrasOf(
+ keyA withValue 0,
+ keyB withValue 1,
+ keyC withValue 4,
+ keyE withValue 5
+ ),
+ combinedExtras
+ )
+ }
+
+ @Test
+ fun `test - mutable extras - remove`() {
+ val extras = mutableExtrasOf(
+ extrasKeyOf() withValue "2",
+ extrasKeyOf("other") withValue "4",
+ extrasKeyOf() withValue 1,
+ extrasKeyOf("cash") withValue 1
+ )
+
+ extras.remove(extrasKeyOf("sunny"))
+ assertEquals(1, extras[extrasKeyOf("cash")])
+
+ assertEquals(1, extras.remove(extrasKeyOf("cash")))
+ assertNull(extras[extrasKeyOf("cash")])
+
+ assertEquals(1, extras.remove(extrasKeyOf()))
+ assertNull(extras[extrasKeyOf()])
+
+ assertEquals("4", extras.remove(extrasKeyOf("other")))
+
+ assertNull(extras[extrasKeyOf("other")])
+
+ assertEquals(
+ extrasOf(extrasKeyOf() withValue "2"),
+ extras.toExtras()
+ )
+ }
+
+ @Test
+ fun `test - mutable extras - clear`() {
+ val extras = mutableExtrasOf(
+ extrasKeyOf() withValue "2",
+ extrasKeyOf("other") withValue "4",
+ extrasKeyOf() withValue 1,
+ extrasKeyOf("cash") withValue 1
+ )
+
+ assertFalse(extras.isEmpty(), "Expected non-empty extras")
+ extras.clear()
+ assertTrue(extras.isEmpty(), "Expected extras to be empty")
+
+ assertEquals(emptyExtras(), extras)
+ assertEquals(emptyExtras(), extras.toExtras())
+ }
+
+ @Test
+ fun `test mutable extras - putAll`() {
+ val extras = mutableExtrasOf(
+ extrasKeyOf() withValue "1",
+ extrasKeyOf