diff --git a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api index 55e112d997e..2c66d88cc8b 100644 --- a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api +++ b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api @@ -1106,6 +1106,7 @@ public final class kotlinx/metadata/KmVersionRequirement : kotlinx/metadata/KmVe public final fun setLevel (Lkotlinx/metadata/KmVersionRequirementLevel;)V public final fun setMessage (Ljava/lang/String;)V public final fun setVersion (Lkotlinx/metadata/KmVersion;)V + public fun toString ()Ljava/lang/String; public fun visit (Lkotlinx/metadata/KmVersionRequirementVersionKind;Lkotlinx/metadata/KmVersionRequirementLevel;Ljava/lang/Integer;Ljava/lang/String;)V public fun visitVersion (III)V } @@ -1123,6 +1124,7 @@ public final class kotlinx/metadata/KmVersionRequirementVersionKind : java/lang/ public static final field API_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind; public static final field COMPILER_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind; public static final field LANGUAGE_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind; + public static final field UNKNOWN Lkotlinx/metadata/KmVersionRequirementVersionKind; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/metadata/KmVersionRequirementVersionKind; public static fun values ()[Lkotlinx/metadata/KmVersionRequirementVersionKind; diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt index 7ee4d05f33d..c50d72318d0 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt @@ -12,23 +12,26 @@ import kotlinx.metadata.KmPackage import kotlinx.metadata.internal.toKmClass import kotlinx.metadata.internal.toKmLambda import kotlinx.metadata.internal.toKmPackage -import kotlinx.metadata.jvm.KmModule -import kotlinx.metadata.jvm.KmPackageParts -import kotlinx.metadata.jvm.KotlinClassMetadata -import kotlinx.metadata.jvm.UnstableMetadataApi -import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion +import kotlinx.metadata.jvm.* +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping internal object JvmReadUtils { + + + private fun isLessThan14(annotationData: Metadata): Boolean { + return JvmMetadataVersion(annotationData.metadataVersion) < JvmMetadataVersion(1, 4, 0) + } + internal fun readKmClass(annotationData: Metadata): KmClass { val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - return proto.toKmClass(strings) + return proto.toKmClass(strings, isLessThan14(annotationData)) } internal fun readKmPackage(annotationData: Metadata): KmPackage { val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - return proto.toKmPackage(strings) + return proto.toKmPackage(strings, isLessThan14(annotationData)) } internal fun readKmLambda(annotationData: Metadata): KmLambda? { @@ -37,7 +40,7 @@ internal object JvmReadUtils { JvmProtoBufUtil.readFunctionDataFrom(data1, annotationData.data2) } ?: return null val (strings, proto) = functionData - return proto.toKmLambda(strings) + return proto.toKmLambda(strings, isLessThan14(annotationData)) } internal fun readMetadataImpl(annotationData: Metadata, lenient: Boolean): KotlinClassMetadata { @@ -72,7 +75,7 @@ internal object JvmReadUtils { } for (classProto in data.moduleData.optionalAnnotations) { - v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver)) + v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver, false)) } return v } @@ -80,21 +83,21 @@ internal object JvmReadUtils { private fun checkMetadataVersionForRead(annotationData: Metadata, lenient: Boolean) { if (annotationData.metadataVersion.isEmpty()) throw IllegalArgumentException("Provided Metadata instance does not have metadataVersion in it and therefore is malformed and cannot be read.") - val jvmMetadataVersion = JvmMetadataVersion( + val jvmMetadataVersion = CompilerMetadataVersion( annotationData.metadataVersion, (annotationData.extraInt and (1 shl 3)/* see JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG */) != 0 ) throwIfNotCompatible(jvmMetadataVersion, lenient) } - internal fun throwIfNotCompatible(jvmMetadataVersion: JvmMetadataVersion, lenient: Boolean) { + internal fun throwIfNotCompatible(jvmMetadataVersion: CompilerMetadataVersion, lenient: Boolean) { val isAtLeast110 = jvmMetadataVersion.isAtLeast(1, 1, 0) val isCompatible = if (lenient) isAtLeast110 else jvmMetadataVersion.isCompatibleWithCurrentCompilerVersion() if (!isCompatible) { // Kotlin 1.0 produces classfiles with metadataVersion = 1.1.0, while 1.0.0 represents unsupported pre-1.0 Kotlin (see JvmMetadataVersion.kt:39) val postfix = if (!isAtLeast110) "while minimum supported version is 1.1.0 (Kotlin 1.0)." - else "while maximum supported version is ${if (jvmMetadataVersion.isStrictSemantics) JvmMetadataVersion.INSTANCE else JvmMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library." + else "while maximum supported version is ${if (jvmMetadataVersion.isStrictSemantics) CompilerMetadataVersion.INSTANCE else CompilerMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library." throw IllegalArgumentException("Provided Metadata instance has version $jvmMetadataVersion, $postfix") } } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/UnknownVersionRequirementTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/UnknownVersionRequirementTest.kt new file mode 100644 index 00000000000..7c968a322c7 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/UnknownVersionRequirementTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2023 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 kotlinx.metadata.test + +import kotlinx.metadata.KmClass +import kotlinx.metadata.internal.ClassWriter +import kotlinx.metadata.jvm.JvmMetadataVersion +import kotlinx.metadata.jvm.KotlinClassMetadata +import kotlinx.metadata.jvm.Metadata +import kotlinx.metadata.jvm.internal.writeProtoBufData +import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class UnknownVersionRequirementTest { + + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + @kotlin.internal.RequireKotlin("1.8.0", "foobar") + class Sample + + private val requireKotlinValue = "KmVersionRequirement(kind=LANGUAGE_VERSION, level=ERROR, version=1.8.0, errorCode=null, message=foobar)" + + private fun corrupt(original: KmClass): Pair, Array> { + val writer = ClassWriter(JvmStringTable()) + writer.writeClass(original) + writer.t.addVersionRequirement(239) // invalid + val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) + return d1 to d2 + } + + @Test + fun incorrectVersionRequirementFailsIfMetadataIsNew() { + val (d1, d2) = corrupt(Sample::class.java.readMetadataAsKmClass()) + val incorrect = + Metadata(KotlinClassMetadata.CLASS_KIND, JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toIntArray(), d1, d2, extraInt = 0) + assertFailsWith { + incorrect.readMetadataAsClass() + } + } + + @Test + fun incorrectVersionRequirementHandledAsUnknown() { + val original = Sample::class.java.readMetadataAsKmClass() + assertEquals(1, original.versionRequirements.size) + assertEquals( + requireKotlinValue, + original.versionRequirements.single().toString() + ) + + val (d1, d2) = corrupt(original) + val incorrect = + Metadata(KotlinClassMetadata.CLASS_KIND, intArrayOf(1, 1, 3), d1, d2, extraInt = 0) + + val withInvalidRequirement = incorrect.readMetadataAsClass() + assertEquals(2, withInvalidRequirement.kmClass.versionRequirements.size) + assertEquals( + "[$requireKotlinValue, KmVersionRequirement(kind=UNKNOWN, level=HIDDEN, version=256.256.256, errorCode=null, message=null)]", + withInvalidRequirement.kmClass.versionRequirements.toString() + ) + + withInvalidRequirement.version = JvmMetadataVersion.LATEST_STABLE_SUPPORTED + val rewritten = withInvalidRequirement.write().readAsKmClass() + assertEquals(1, rewritten.versionRequirements.size) + assertEquals( + requireKotlinValue, + rewritten.versionRequirements.single().toString() + ) + } +} diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt index d304c71e969..f46ed2e371f 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt @@ -18,4 +18,7 @@ internal fun Metadata.readAsKmClass(): KmClass { } internal fun Class<*>.readMetadataAsKmClass(): KmClass = getMetadata().readAsKmClass() -internal fun Class<*>.readMetadataAsClass(): KotlinClassMetadata.Class = KotlinClassMetadata.readStrict(getMetadata()) as KotlinClassMetadata.Class + +internal fun Class<*>.readMetadataAsClass(): KotlinClassMetadata.Class = getMetadata().readMetadataAsClass() + +internal fun Metadata.readMetadataAsClass(): KotlinClassMetadata.Class = KotlinClassMetadata.readStrict(this) as KotlinClassMetadata.Class diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/Nodes.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/Nodes.kt index 6b2ebb53bfb..d467c5def91 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/Nodes.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/Nodes.kt @@ -1034,6 +1034,10 @@ public class KmVersionRequirement : KmVersionRequirementVisitor() { visitor.visitVersion(version.major, version.minor, version.patch) visitor.visitEnd() } + + override fun toString(): String { + return "KmVersionRequirement(kind=$kind, level=$level, version=$version, errorCode=$errorCode, message=$message)" + } } /** diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/Visitors.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/Visitors.kt index ef67af299df..2dc09bdb012 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/Visitors.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/Visitors.kt @@ -983,4 +983,20 @@ public enum class KmVersionRequirementVersionKind { * Indicates that certain API version is required. */ API_VERSION, + + /** + * Represents a version requirement not successfully parsed from the metadata. + * + * The old metadata format (from Kotlin 1.3 and earlier) did not have enough information for correct parsing of version requirements in some cases, + * so a stub of this kind is inserted instead. + * + * [KmVersionRequirement] with this kind always has [KmVersionRequirementLevel.HIDDEN] level, `256.256.256` [KmVersionRequirement.version], + * and `null` [KmVersionRequirement.errorCode] and [KmVersionRequirement.message]. + * + * Version requirements of this kind are being ignored by writers (i.e., are not written back). + * + * See the following issues for details: [KT-60870](https://youtrack.jetbrains.com/issue/KT-60870), [KT-25120](https://youtrack.jetbrains.com/issue/KT-25120) + */ + UNKNOWN + ; } diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Readers.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Readers.kt index 7d0ff695edd..080c1a00bf3 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Readers.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Readers.kt @@ -24,6 +24,7 @@ public class ReadContext( public val strings: NameResolver, public val types: TypeTable, @get:IgnoreInApiDump internal val versionRequirements: VersionRequirementTable, + internal val ignoreUnknownVersionRequirements: Boolean, private val parent: ReadContext? = null, internal val contextExtensions: List = emptyList() ) { @@ -41,7 +42,7 @@ public class ReadContext( typeParameterNameToId[name] ?: parent?.getTypeParameterId(name) internal fun withTypeParameters(typeParameters: List): ReadContext = - ReadContext(strings, types, versionRequirements, this, contextExtensions).apply { + ReadContext(strings, types, versionRequirements, ignoreUnknownVersionRequirements, this, contextExtensions).apply { for (typeParameter in typeParameters) { typeParameterNameToId[typeParameter.name] = typeParameter.id } @@ -51,6 +52,7 @@ public class ReadContext( @OptIn(ExperimentalContextReceivers::class) public fun ProtoBuf.Class.toKmClass( strings: NameResolver, + ignoreUnknownVersionRequirements: Boolean = false, contextExtensions: List = emptyList(), ): KmClass { val v = KmClass() @@ -58,6 +60,7 @@ public fun ProtoBuf.Class.toKmClass( strings, TypeTable(typeTable), VersionRequirementTable.create(versionRequirementTable), + ignoreUnknownVersionRequirements, contextExtensions = contextExtensions ).withTypeParameters(typeParameterList) @@ -105,6 +108,7 @@ private fun ProtoBuf.Class.loadInlineClassUnderlyingType(c: ReadContext): ProtoB public fun ProtoBuf.Package.toKmPackage( strings: NameResolver, + ignoreUnknownVersionRequirements: Boolean = false, contextExtensions: List = emptyList(), ): KmPackage { val v = KmPackage() @@ -112,6 +116,7 @@ public fun ProtoBuf.Package.toKmPackage( strings, TypeTable(typeTable), VersionRequirementTable.create(versionRequirementTable), + ignoreUnknownVersionRequirements, contextExtensions = contextExtensions ) @@ -131,11 +136,12 @@ public fun ProtoBuf.PackageFragment.toKmModuleFragment( strings, TypeTable(ProtoBuf.TypeTable.newBuilder().build()), VersionRequirementTable.EMPTY, + false, // toKmModuleFragment is used for klib only contextExtensions = contextExtensions ) - v.pkg = `package`.toKmPackage(strings, contextExtensions) - class_List.mapTo(v.classes) { it.toKmClass(strings, contextExtensions) } + v.pkg = `package`.toKmPackage(strings, false, contextExtensions) + class_List.mapTo(v.classes) { it.toKmClass(strings, false, contextExtensions) } c.extensions.forEach { it.readModuleFragmentExtensions(v, this, c) } @@ -153,9 +159,9 @@ private fun KmDeclarationContainer.visitDeclarations( protoTypeAliases.mapTo(typeAliases) { it.toKmTypeAlias(c) } } -public fun ProtoBuf.Function.toKmLambda(strings: NameResolver): KmLambda { +public fun ProtoBuf.Function.toKmLambda(strings: NameResolver, ignoreUnknownVersionRequirements: Boolean = false): KmLambda { val v = KmLambda() - val c = ReadContext(strings, TypeTable(typeTable), VersionRequirementTable.EMPTY) + val c = ReadContext(strings, TypeTable(typeTable), VersionRequirementTable.EMPTY, ignoreUnknownVersionRequirements) v.function = this.toKmFunction(c) return v } @@ -303,26 +309,27 @@ private fun ProtoBuf.Type.toKmType(c: ReadContext): KmType { private fun readVersionRequirement(id: Int, c: ReadContext): KmVersionRequirement { val v = KmVersionRequirement() val message = VersionRequirement.create(id, c.strings, c.versionRequirements) - ?: throw InconsistentKotlinMetadataException("No VersionRequirement with the given id in the table") + if (message == null && !c.ignoreUnknownVersionRequirements) throw InconsistentKotlinMetadataException("No VersionRequirement with the given id in the table") - val kind = when (message.kind) { + val kind = when (message?.kind) { ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION -> KmVersionRequirementVersionKind.LANGUAGE_VERSION ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION -> KmVersionRequirementVersionKind.COMPILER_VERSION ProtoBuf.VersionRequirement.VersionKind.API_VERSION -> KmVersionRequirementVersionKind.API_VERSION + null -> KmVersionRequirementVersionKind.UNKNOWN } - val level = when (message.level) { + val level = when (message?.level) { DeprecationLevel.WARNING -> KmVersionRequirementLevel.WARNING DeprecationLevel.ERROR -> KmVersionRequirementLevel.ERROR - DeprecationLevel.HIDDEN -> KmVersionRequirementLevel.HIDDEN + DeprecationLevel.HIDDEN, null -> KmVersionRequirementLevel.HIDDEN } v.kind = kind v.level = level - v.errorCode = message.errorCode - v.message = message.message + v.errorCode = message?.errorCode + v.message = message?.message - val (major, minor, patch) = message.version + val (major, minor, patch) = message?.version ?: VersionRequirement.Version.INFINITY v.version = KmVersion(major, minor, patch) return v } diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Writers.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Writers.kt index 5625d5f0a38..2e77cb4b018 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Writers.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/internal/Writers.kt @@ -110,7 +110,7 @@ private fun WriteContext.writeConstructor(kmConstructor: KmConstructor): ProtoBu kmConstructor.valueParameters.forEach { t.addValueParameter(writeValueParameter(it).build()) } - kmConstructor.versionRequirements.forEach { t.addVersionRequirement(writeVersionRequirement(it)) } + t.addAllVersionRequirement(kmConstructor.versionRequirements.mapNotNull(::writeVersionRequirement)) extensions.forEach { it.writeConstructorExtensions(kmConstructor, t, this) } @@ -129,7 +129,7 @@ private fun WriteContext.writeFunction(kmFunction: KmFunction): ProtoBuf.Functio t.addAllContextReceiverType(kmFunction.contextReceiverTypes.map { writeType(it).build() }) t.addAllValueParameter(kmFunction.valueParameters.map { writeValueParameter(it).build() }) t.returnType = writeType(kmFunction.returnType).build() - t.addAllVersionRequirement(kmFunction.versionRequirements.map(::writeVersionRequirement)) + t.addAllVersionRequirement(kmFunction.versionRequirements.mapNotNull(::writeVersionRequirement)) @OptIn(ExperimentalContracts::class) kmFunction.contract?.let { t.contract = writeContract(it) } @@ -156,7 +156,7 @@ public fun WriteContext.writeProperty(kmProperty: KmProperty): ProtoBuf.Property t.addAllContextReceiverType(kmProperty.contextReceiverTypes.map { writeType(it).build() }) kmProperty.setterParameter?.let { t.setterValueParameter = writeValueParameter(it).build() } t.returnType = writeType(kmProperty.returnType).build() - t.addAllVersionRequirement(kmProperty.versionRequirements.map { writeVersionRequirement(it) }) + t.addAllVersionRequirement(kmProperty.versionRequirements.mapNotNull { writeVersionRequirement(it) }) extensions.forEach { it.writePropertyExtensions(kmProperty, t, this) } @@ -192,7 +192,7 @@ private fun WriteContext.writeTypeAlias( t.underlyingType = writeType(typeAlias.underlyingType).build() t.expandedType = writeType(typeAlias.expandedType).build() t.addAllAnnotation(typeAlias.annotations.map { it.writeAnnotation(strings).build() }) - t.addAllVersionRequirement(typeAlias.versionRequirements.map(::writeVersionRequirement)) + t.addAllVersionRequirement(typeAlias.versionRequirements.mapNotNull(::writeVersionRequirement)) extensions.forEach { it.writeTypeAliasExtensions(typeAlias, t, this) } if (typeAlias.flags != ProtoBuf.TypeAlias.getDefaultInstance().flags) { @@ -202,7 +202,7 @@ private fun WriteContext.writeTypeAlias( return t } -private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersionRequirement): Int { +private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersionRequirement): Int? { val kind = kmVersionRequirement.kind val level = kmVersionRequirement.level val errorCode = kmVersionRequirement.errorCode @@ -212,6 +212,7 @@ private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersion KmVersionRequirementVersionKind.LANGUAGE_VERSION -> ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION KmVersionRequirementVersionKind.COMPILER_VERSION -> ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION KmVersionRequirementVersionKind.API_VERSION -> ProtoBuf.VersionRequirement.VersionKind.API_VERSION + KmVersionRequirementVersionKind.UNKNOWN -> return null } if (versionKind != defaultInstanceForType.versionKind) { this.versionKind = versionKind @@ -329,7 +330,7 @@ public open class ClassWriter(stringTable: StringTable, contextExtensions: List< @OptIn(ExperimentalContextReceivers::class) t.addAllContextReceiverType(kmClass.contextReceiverTypes.map { c.writeType(it).build() }) - t.addAllVersionRequirement(kmClass.versionRequirements.map { c.writeVersionRequirement(it) }) + t.addAllVersionRequirement(kmClass.versionRequirements.mapNotNull { c.writeVersionRequirement(it) }) c.extensions.forEach { it.writeClassExtensions(kmClass, t, c) } diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt index 49f1686cd0b..1a7c54bc143 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt @@ -322,6 +322,7 @@ private fun printVersionRequirement(versionRequirement: KmVersionRequirement): S KmVersionRequirementVersionKind.LANGUAGE_VERSION -> "language version" KmVersionRequirementVersionKind.COMPILER_VERSION -> "compiler version" KmVersionRequirementVersionKind.API_VERSION -> "API version" + KmVersionRequirementVersionKind.UNKNOWN -> "unknown requirement" } ).append(" ").append(version)