[Commonizer] Metadata comparator: More verbose output (with more details)

^KT-62753
This commit is contained in:
Dmitriy Dolovov
2024-01-25 17:50:40 +01:00
committed by Space Team
parent 5f3eee7267
commit e021411768
@@ -56,70 +56,88 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
@Suppress("MemberVisibilityCanBePrivate", "unused") @Suppress("MemberVisibilityCanBePrivate", "unused")
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
sealed interface PathElement { sealed interface PathElement {
val name: String data object Root : PathElement
data object Root : PathElement {
override val name get() = "Root"
}
class Module(val moduleA: KlibModuleMetadata, val moduleB: KlibModuleMetadata) : PathElement { class Module(val moduleA: KlibModuleMetadata, val moduleB: KlibModuleMetadata) : PathElement {
override val name get() = moduleA.name override fun toString() = "Module ${moduleA.name}"
} }
class Package(packageFqName: String, val fragmentsA: List<KmModuleFragment>, val fragmentsB: List<KmModuleFragment>) : PathElement { class Package(
override val name = packageFqName.ifEmpty { "<root>" } val packageFqName: String,
val fragmentsA: List<KmModuleFragment>,
val fragmentsB: List<KmModuleFragment>
) : PathElement {
override fun toString() = "Package ${if (packageFqName.isEmpty()) "<root>" else "'$packageFqName'"}"
} }
class Class(val clazzA: KmClass, val clazzB: KmClass) : PathElement { class Class(val clazzA: KmClass, val clazzB: KmClass) : PathElement {
override val name get() = clazzA.name.split("/").last() override fun toString() = "Class '${clazzA.name.split("/").last()}'"
} }
class TypeAlias(val typeAliasA: KmTypeAlias, val typeAliasB: KmTypeAlias) : PathElement { class TypeAlias(val typeAliasA: KmTypeAlias, val typeAliasB: KmTypeAlias) : PathElement {
override val name get() = typeAliasA.name override fun toString() = "TypeAlias '${typeAliasA.name}'"
} }
class Property(val propertyA: KmProperty, val propertyB: KmProperty) : PathElement { class Property(val propertyA: KmProperty, val propertyB: KmProperty) : PathElement {
override val name get() = propertyA.name override fun toString() = "Property '${propertyA.name}'"
} }
class Function(val functionA: KmFunction, val functionB: KmFunction) : PathElement { class Function(val functionA: KmFunction, val functionB: KmFunction) : PathElement {
override val name get() = functionA.name override fun toString() = "Function '${functionA.name}', TxtDump: ${functionA.dumpToString()}"
} }
class Constructor(val constructorA: KmConstructor, val constructorB: KmConstructor) : PathElement { class Constructor(val constructorA: KmConstructor, val constructorB: KmConstructor) : PathElement {
override val name get() = "constructor" override fun toString() = "Constructor, TxtDump: ${constructorA.dumpToString()}"
} }
class ValueParameter(val parameterA: KmValueParameter, val parameterB: KmValueParameter, val index: Int) : PathElement { class ValueParameter(val parameterA: KmValueParameter, val parameterB: KmValueParameter, val index: Int) : PathElement {
override val name get() = index.toString() override fun toString() = "ValueParameter #$index"
} }
class TypeParameter(val parameterA: KmTypeParameter, val parameterB: KmTypeParameter, val index: Int) : PathElement { class TypeParameter(val parameterA: KmTypeParameter, val parameterB: KmTypeParameter, val index: Int) : PathElement {
override val name get() = index.toString() override fun toString() = "TypeParameter #$index"
} }
class Type(val typeA: KmType, val typeB: KmType, val kind: TypeKind, val index: Int?) : PathElement { class Type(val typeA: KmType, val typeB: KmType, val kind: TypeKind, val index: Int?) : PathElement {
override val name get() = if (index != null) "$kind $index" else kind.toString() override fun toString() = buildString {
append(kind)
if (index != null) append(" #$index")
appendLine()
val typeADump = typeA.dumpToString(dumpExtras = true)
val typeBDump = typeB.dumpToString(dumpExtras = true)
if (typeADump == typeBDump)
append(" TxtDump (A, B): ").append(typeADump)
else {
append(" TxtDump (A): ").appendLine(typeADump)
append(" TxtDump (B): ").append(typeBDump)
}
}
} }
class TypeArgument(val argumentA: KmTypeProjection, val argumentB: KmTypeProjection, val index: Int) : PathElement { class TypeArgument(val argumentA: KmTypeProjection, val argumentB: KmTypeProjection, val index: Int) : PathElement {
override val name get() = index.toString() override fun toString() = "TypeArgument #$index"
} }
class EnumEntry(val entryA: KlibEnumEntry, val entryB: KlibEnumEntry) : PathElement { class EnumEntry(val entryA: KlibEnumEntry, val entryB: KlibEnumEntry) : PathElement {
override val name get() = entryA.name override fun toString() = "EnumEntry '${entryA.name}'"
} }
class Contract(val contractA: KmContract, val contractB: KmContract) : PathElement { class Contract(val contractA: KmContract, val contractB: KmContract) : PathElement {
override val name get() = "contract" override fun toString() = "Contract"
} }
class Effect(val effectA: KmEffect, val effectB: KmEffect, val index: Int) : PathElement { class Effect(val effectA: KmEffect, val effectB: KmEffect, val index: Int) : PathElement {
override val name get() = index.toString() override fun toString() = "Effect #$index"
} }
class EffectExpression(val effectExpressionA: KmEffectExpression, val effectExpressionB: KmEffectExpression, val index: Int) : PathElement { class EffectExpression(
override val name get() = index.toString() val effectExpressionA: KmEffectExpression,
val effectExpressionB: KmEffectExpression,
val index: Int?
) : PathElement {
override fun toString() = if (index != null) "EffectExpression #$index" else "EffectExpression"
} }
companion object { companion object {
@@ -153,8 +171,8 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
Effect(entityA, entityB, index) Effect(entityA, entityB, index)
} }
entityA is KmEffectExpression && entityB is KmEffectExpression -> { entityA is KmEffectExpression && entityB is KmEffectExpression -> {
val index = entityKey!!.toInt() val optionalIndex = entityKey?.toInt()
EffectExpression(entityA, entityB, index) EffectExpression(entityA, entityB, optionalIndex)
} }
entityA is KlibEnumEntry && entityB is KlibEnumEntry -> EnumEntry(entityA, entityB) entityA is KlibEnumEntry && entityB is KlibEnumEntry -> EnumEntry(entityA, entityB)
else -> error("Unknown combination of entities: ${entityA::class.java}, ${entityB::class.java}") else -> error("Unknown combination of entities: ${entityA::class.java}, ${entityB::class.java}")
@@ -163,38 +181,41 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
} }
sealed interface EntityKind { sealed interface EntityKind {
enum class AnnotationKind : EntityKind { enum class AnnotationKind(val alias: String) : EntityKind {
REGULAR, REGULAR("Annotation"),
GETTER, GETTER("GetterAnnotation"),
SETTER; SETTER("SetterAnnotation");
override fun toString() = "AnnotationKind.$name" override fun toString() = alias
} }
enum class TypeKind : EntityKind { enum class TypeKind(val alias: String) : EntityKind {
RETURN, RETURN("ReturnType"),
SUPERTYPE, SUPERTYPE("SuperType"),
UNDERLYING, UNDERLYING("TypeAliasUnderlyingType"),
EXPANDED, EXPANDED("TypeAliasExpandedType"),
RECEIVER, RECEIVER("ReceiverParameterType"),
CONTEXT_RECEIVER, CONTEXT_RECEIVER("ContextReceiverType"),
ABBREVIATED, ABBREVIATED("AbbreviatedType"),
OUTER, OUTER("OuterType"),
UPPER_BOUND, UPPER_BOUND("UpperBoundType"),
VALUE_PARAMETER, VALUE_PARAMETER("ValueParameterType"),
VALUE_PARAMETER_VARARG, VALUE_PARAMETER_VARARG("ValueParameterVarargType"),
TYPE_ARGUMENT, TYPE_ARGUMENT("TypeArgumentType"),
INLINE_CLASS_UNDERLYING; INLINE_CLASS_UNDERLYING("InlineClassUnderlyingType"),
EFFECT_TYPE("EffectType"),
EFFECT_EXPRESSION_IS_INSTANCE_TYPE("EffectExpressionIsInstanceType"),
;
override fun toString() = "TypeKind.$name" override fun toString() = alias
} }
enum class FlagKind : EntityKind { enum class FlagKind(val alias: String) : EntityKind {
REGULAR, REGULAR("flag"),
GETTER, GETTER("getter flag"),
SETTER; SETTER("setter flag");
override fun toString() = "FlagKind.$name" override fun toString() = alias
} }
companion object { companion object {
@@ -231,13 +252,11 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
val Contract by EntityKindImpl val Contract by EntityKindImpl
val Effect by EntityKindImpl val Effect by EntityKindImpl
val EffectType by EntityKindImpl
val EffectInvocationKind by EntityKindImpl val EffectInvocationKind by EntityKindImpl
val EffectConstructorArguments by EntityKindImpl val EffectConstructorArguments by EntityKindImpl
val EffectConclusion by EntityKindImpl val EffectConclusion by EntityKindImpl
val EffectExpressionParameterIndex by EntityKindImpl val EffectExpressionParameterIndex by EntityKindImpl
val EffectExpressionConstantValue by EntityKindImpl val EffectExpressionConstantValue by EntityKindImpl
val EffectExpressionIsInstanceType by EntityKindImpl
val EffectExpressionAndArguments by EntityKindImpl val EffectExpressionAndArguments by EntityKindImpl
val EffectExpressionOrArguments by EntityKindImpl val EffectExpressionOrArguments by EntityKindImpl
@@ -260,7 +279,28 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
abstract val name: String abstract val name: String
abstract val path: List<PathElement> abstract val path: List<PathElement>
protected fun pathString() = path.joinToString("/") { it.name } protected fun StringBuilder.appendNameKind(): StringBuilder {
when (val kind = kind) {
is FlagKind -> append("state of ").append(kind)
else -> append(kind)
}
return append(if (name.isEmpty()) "" else " '$name'")
}
protected fun StringBuilder.appendPath(): StringBuilder {
val filteredPath = path.filter { it !is PathElement.Root }
filteredPath.forEachIndexed { pathElementIndex, pathElement ->
val pathElementLines = pathElement.toString().lines()
pathElementLines.forEachIndexed { pathElementLineIndex, pathElementLine ->
val prefix = when {
pathElementIndex == 0 && pathElementLineIndex == 0 -> "at "
else -> " "
}
append(prefix).appendLine(pathElementLine)
}
}
return this
}
// an entity has different non-nullable values // an entity has different non-nullable values
data class DifferentValues( data class DifferentValues(
@@ -270,10 +310,11 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
val valueA: Any, val valueA: Any,
val valueB: Any val valueB: Any
) : Mismatch() { ) : Mismatch() {
// TODO: fix Kotlin metadata rendering for values override fun toString() = buildString {
override fun toString(): String { append("Different ").appendNameKind().appendLine()
val spacedName = if (name.isEmpty()) "" else "$name " append("(A): ").appendLine(valueA)
return "$kind ${spacedName}is different in (A): $valueA and (B): $valueB; path: ${pathString()}" append("(B): ").appendLine(valueB)
appendPath()
} }
} }
@@ -287,12 +328,21 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
val missingInA: Boolean val missingInA: Boolean
) : Mismatch() { ) : Mismatch() {
@Suppress("unused") @Suppress("unused")
val missingInB: Boolean val missingInB: Boolean get() = !missingInA
get() = !missingInA
override fun toString(): String { override fun toString() = buildString {
val (missing, existing) = if (missingInA) "A" to "B" else "B" to "A" val (missing, existing) = if (missingInA) "A" to "B" else "B" to "A"
return "Missing $kind in ($missing): '$name'; in ($existing) it's: $existentValue; path: '${pathString()}'" appendNameKind().appendLine(" is missing in ($missing)")
val existentValueText = when (val existentValue = existentValue) {
is KmType -> existentValue.dumpToString(dumpExtras = true)
is KmClass -> "Class '${existentValue.name}'"
is KmFunction -> existentValue.dumpToString()
is KmConstructor -> existentValue.dumpToString()
is KmTypeProjection -> existentValue.dumpToString(dumpExtras = true)
else -> existentValue.toString()
}
appendLine("($existing): $existentValueText")
appendPath()
} }
} }
} }
@@ -438,7 +488,7 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
entityListA = functionListA, entityListA = functionListA,
entityListB = functionListB, entityListB = functionListB,
entityKind = EntityKind.Function, entityKind = EntityKind.Function,
groupingKeySelector = { _, function -> function.mangle() }, groupingKeySelector = { _, function -> function.dumpToString() },
entitiesComparator = ::compareFunctions entitiesComparator = ::compareFunctions
) )
} }
@@ -453,7 +503,7 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
entityListA = constructorListA, entityListA = constructorListA,
entityListB = constructorListB, entityListB = constructorListB,
entityKind = EntityKind.Constructor, entityKind = EntityKind.Constructor,
groupingKeySelector = { _, constructor -> constructor.mangle() }, groupingKeySelector = { _, constructor -> constructor.dumpToString() },
entitiesComparator = ::compareConstructors entitiesComparator = ::compareConstructors
) )
} }
@@ -696,7 +746,7 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
entityKind = EntityKind.Effect, entityKind = EntityKind.Effect,
groupingKeySelector = { index, _ -> index.toString() } groupingKeySelector = { index, _ -> index.toString() }
) { effectContext, effectA, effectB -> ) { effectContext, effectA, effectB ->
compareValues(effectContext, effectA.type, effectB.type, EntityKind.EffectType) compareValues(effectContext, effectA.type, effectB.type, TypeKind.EFFECT_TYPE)
compareNullableValues(effectContext, effectA.invocationKind, effectB.invocationKind, EntityKind.EffectInvocationKind) compareNullableValues(effectContext, effectA.invocationKind, effectB.invocationKind, EntityKind.EffectInvocationKind)
compareEffectExpressionLists( compareEffectExpressionLists(
@@ -859,7 +909,7 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
containerContext = effectExpressionContext, containerContext = effectExpressionContext,
entityA = effectExpressionA.isInstanceType, entityA = effectExpressionA.isInstanceType,
entityB = effectExpressionB.isInstanceType, entityB = effectExpressionB.isInstanceType,
entityKind = EntityKind.EffectExpressionIsInstanceType, entityKind = TypeKind.EFFECT_EXPRESSION_IS_INSTANCE_TYPE,
entitiesComparator = ::compareTypes entitiesComparator = ::compareTypes
) )
@@ -1123,20 +1173,69 @@ class MetadataDeclarationsComparator private constructor(private val config: Con
/** /**
* We need a stable order for overloaded functions. * We need a stable order for overloaded functions.
*/ */
private fun KmFunction.mangle(): String { private fun KmFunction.dumpToString(): String = buildString {
return buildString { receiverParameterType?.classifier?.let { classifier ->
receiverParameterType?.classifier?.let(::append) append(classifier.dumpToString(dumpClassifierType = true)).append('.')
append('.') }
append(name) append(name)
append('.') if (typeParameters.isNotEmpty()) {
typeParameters.joinTo(this, prefix = "<", postfix = ">", transform = KmTypeParameter::name) typeParameters.joinTo(this, prefix = "<", postfix = ">") { typeParameter ->
append('.') val typeParameterText = "#${typeParameter.id}"
valueParameters.joinTo(this, prefix = "(", postfix = ")", transform = KmValueParameter::name) if (typeParameter.upperBounds.isNotEmpty()) {
val upperBoundsText = typeParameter.upperBounds.joinToString { type -> type.dumpToString(dumpExtras = false) }
"$typeParameterText: $upperBoundsText"
} else typeParameterText
}
}
valueParameters.joinTo(this, prefix = "(", postfix = ")") { valueParameter ->
valueParameter.type.dumpToString(dumpExtras = false)
} }
} }
private fun KmConstructor.mangle(): String { private fun KmConstructor.dumpToString(): String =
return valueParameters.joinToString(prefix = "(", postfix = ")", transform = KmValueParameter::name) valueParameters.joinToString(prefix = "(", postfix = ")", transform = KmValueParameter::name)
private fun KmClassifier.dumpToString(dumpClassifierType: Boolean): String {
return when (this) {
is KmClassifier.Class -> if (dumpClassifierType) "Class($name)" else name
is KmClassifier.TypeAlias -> if (dumpClassifierType) "TypeAlias($name)" else name
is KmClassifier.TypeParameter -> if (dumpClassifierType) "TypeParameter(#$id)" else "#$id"
}
}
private fun KmTypeProjection.dumpToString(dumpExtras: Boolean): String {
val prefix = when (variance) {
null -> if (type == null) return "*" else "? "
KmVariance.INVARIANT -> ""
KmVariance.IN -> "in "
KmVariance.OUT -> "out "
}
val suffix = type?.dumpToString(dumpExtras) ?: "?"
return "$prefix$suffix"
}
private fun KmType.dumpToString(dumpExtras: Boolean): String = buildString {
append(classifier.dumpToString(dumpClassifierType = false))
if (arguments.isNotEmpty()) {
arguments.joinTo(this, prefix = "<", postfix = ">") { argument ->
argument.dumpToString(dumpExtras = false)
}
}
if (dumpExtras) {
abbreviatedType?.let { abbreviatedType ->
append(", abbreviation=")
append(abbreviatedType.dumpToString(dumpExtras = false))
}
outerType?.let { outerType ->
append(", outer=")
append(outerType.dumpToString(dumpExtras = false))
}
flexibleTypeUpperBound?.let { flexibleTypeUpperBound ->
append(", flexibleTypeUpperBound=")
flexibleTypeUpperBound.typeFlexibilityId?.let { append(it).append(" ") }
append(flexibleTypeUpperBound.type.dumpToString(dumpExtras = false))
}
}
} }
private inline fun <T, K> Iterable<T>.groupByIndexed(keySelector: (Int, T) -> K): Map<K, List<T>> { private inline fun <T, K> Iterable<T>.groupByIndexed(keySelector: (Int, T) -> K): Map<K, List<T>> {