[Commonizer] Introduce MetadataDeclarationsComparator for metadata-based comparison of KLIBs

This commit is contained in:
Dmitriy Dolovov
2020-09-03 16:54:10 +07:00
parent 701374a646
commit 9551e0fff2
3 changed files with 935 additions and 1 deletions
@@ -0,0 +1,892 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.metadata.utils
import kotlinx.metadata.*
import kotlinx.metadata.klib.*
import org.jetbrains.kotlin.descriptors.commonizer.utils.KNI_BRIDGE_FUNCTION_PREFIX
import java.util.*
import kotlin.reflect.KProperty0
// TODO: move to kotlinx-metadata library?
class MetadataDeclarationsComparator(private val config: Config = Config.Default) {
private val mismatches = mutableListOf<Mismatch>()
private class Context(pathElement: String, parent: Context? = null) {
val path: List<String> = parent?.path.orEmpty() + pathElement
fun next(pathElement: String): Context = Context(pathElement, this)
}
private fun toResult() = if (mismatches.isEmpty()) Result.Success else Result.Failure(mismatches)
fun compare(
metadataA: KlibModuleMetadata,
metadataB: KlibModuleMetadata
): Result {
val rootContext = Context(config.rootPathElement)
compareValues(rootContext, metadataA.name, metadataB.name, "ModuleName")
if (mismatches.isNotEmpty())
return toResult()
val moduleContext = rootContext.next("Module ${metadataA.name}")
compareAnnotationLists(moduleContext, metadataA.annotations, metadataB.annotations)
compareModuleFragmentLists(moduleContext, metadataA.fragments, metadataB.fragments)
return toResult()
}
private fun compareAnnotationLists(
containerContext: Context,
annotationListA: List<KmAnnotation>,
annotationListB: List<KmAnnotation>,
annotationKind: String = "Annotation"
) {
compareRepetitiveEntityLists(
entityListA = annotationListA,
entityListB = annotationListB,
groupingKeySelector = { _, annotation -> annotation.className },
groupedEntityListsComparator = { annotationClassName: ClassName, annotationsA: List<KmAnnotation>, annotationsB: List<KmAnnotation> ->
@Suppress("NAME_SHADOWING") val annotationsB: Deque<KmAnnotation> = LinkedList(annotationsB)
// TODO: compare annotation arguments?
for (annotationA in annotationsA) {
val removed = annotationsB.removeFirstOccurrence(annotationA)
if (!removed)
mismatches += Mismatch.MissingEntity(annotationKind, annotationClassName, containerContext.path, annotationA, false)
}
for (annotationB in annotationsB) {
mismatches += Mismatch.MissingEntity(annotationKind, annotationClassName, containerContext.path, annotationB, true)
}
}
)
}
private fun compareModuleFragmentLists(
moduleContext: Context,
fragmentListA: List<KmModuleFragment>,
fragmentListB: List<KmModuleFragment>
) {
compareRepetitiveEntityLists(
entityListA = fragmentListA,
entityListB = fragmentListB,
groupingKeySelector = { _, fragment -> fragment.fqName.orEmpty() },
groupedEntityListsComparator = { packageFqName: String, fragmentsA: List<KmModuleFragment>, fragmentsB: List<KmModuleFragment> ->
val packageContext = moduleContext.next("Package ${packageFqName.ifEmpty { "<empty>" }}")
val classesA: List<KmClass> = fragmentsA.flatMap { it.classes }
val classesB: List<KmClass> = fragmentsB.flatMap { it.classes }
compareClassLists(packageContext, classesA, classesB)
val typeAliasesA: List<KmTypeAlias> = fragmentsA.flatMap { it.pkg?.typeAliases.orEmpty() }
val typeAliasesB: List<KmTypeAlias> = fragmentsB.flatMap { it.pkg?.typeAliases.orEmpty() }
compareTypeAliasLists(packageContext, typeAliasesA, typeAliasesB)
val propertiesA: List<KmProperty> = fragmentsA.flatMap { it.pkg?.properties.orEmpty() }
val propertiesB: List<KmProperty> = fragmentsB.flatMap { it.pkg?.properties.orEmpty() }
comparePropertyLists(packageContext, propertiesA, propertiesB)
val functionsA: List<KmFunction> = fragmentsA.flatMap { it.pkg?.functions.orEmpty() }
val functionsB: List<KmFunction> = fragmentsB.flatMap { it.pkg?.functions.orEmpty() }
compareFunctionLists(packageContext, functionsA, functionsB)
}
)
}
private fun compareClassLists(
containerContext: Context,
classListA: List<KmClass>,
classListB: List<KmClass>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = classListA,
entityListB = classListB,
entityKind = "Class",
groupingKeySelector = { _, clazz -> clazz.name },
entitiesComparator = ::compareClasses
)
}
private fun compareTypeAliasLists(
containerContext: Context,
typeAliasListA: List<KmTypeAlias>,
typeAliasListB: List<KmTypeAlias>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = typeAliasListA,
entityListB = typeAliasListB,
entityKind = "TypeAlias",
groupingKeySelector = { _, typeAlias -> typeAlias.name },
entitiesComparator = ::compareTypeAliases
)
}
private fun comparePropertyLists(
containerContext: Context,
propertyListA: List<KmProperty>,
propertyListB: List<KmProperty>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = propertyListA,
entityListB = propertyListB,
entityKind = "Property",
groupingKeySelector = { _, property -> property.name },
entitiesComparator = ::compareProperties
)
}
private fun compareFunctionLists(
containerContext: Context,
functionListA: List<KmFunction>,
functionListB: List<KmFunction>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = functionListA,
entityListB = functionListB,
entityKind = "Function",
groupingKeySelector = { _, function -> function.mangle() },
entitiesComparator = ::compareFunctions
)
}
private fun compareConstructorLists(
containerContext: Context,
constructorListA: List<KmConstructor>,
constructorListB: List<KmConstructor>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = constructorListA,
entityListB = constructorListB,
entityKind = "Constructor",
groupingKeySelector = { _, constructor -> constructor.mangle() },
entitiesComparator = ::compareConstructors
)
}
private fun compareValueParameterLists(
containerContext: Context,
valueParameterListA: List<KmValueParameter>,
valueParameterListB: List<KmValueParameter>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = valueParameterListA,
entityListB = valueParameterListB,
entityKind = "ValueParameter",
groupingKeySelector = { index, _ -> index.toString() },
entitiesComparator = ::compareValueParameters
)
}
private fun compareTypeLists(
containerContext: Context,
typeListA: List<KmType>,
typeListB: List<KmType>,
typeKind: String
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = typeListA,
entityListB = typeListB,
entityKind = typeKind,
groupingKeySelector = { index, _ -> index.toString() },
entitiesComparator = ::compareTypes
)
}
private fun compareTypeParameterLists(
containerContext: Context,
typeParameterListA: List<KmTypeParameter>,
typeParameterListB: List<KmTypeParameter>
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = typeParameterListA,
entityListB = typeParameterListB,
entityKind = "TypeParameter",
groupingKeySelector = { index, _ -> index.toString() },
entitiesComparator = ::compareTypeParameters
)
}
private fun compareEffectExpressionLists(
containerContext: Context,
effectExpressionListA: List<KmEffectExpression>,
effectExpressionListB: List<KmEffectExpression>,
effectExpressionKind: String
) {
compareUniqueEntityLists(
containerContext = containerContext,
entityListA = effectExpressionListA,
entityListB = effectExpressionListB,
entityKind = effectExpressionKind,
groupingKeySelector = { index, _ -> index.toString() },
entitiesComparator = ::compareEffectExpressions
)
}
private fun compareClasses(
classContext: Context,
classA: KmClass,
classB: KmClass
) {
compareFlags(classContext, classA.flags, classB.flags, CLASS_FLAGS)
compareAnnotationLists(classContext, classA.annotations, classB.annotations)
compareTypeParameterLists(classContext, classA.typeParameters, classB.typeParameters)
compareTypeLists(classContext, classA.supertypes, classB.supertypes, "Supertype")
compareConstructorLists(classContext, classA.constructors, classB.constructors)
compareTypeAliasLists(classContext, classA.typeAliases, classB.typeAliases)
comparePropertyLists(classContext, classA.properties, classB.properties)
compareFunctionLists(classContext, classA.functions, classB.functions)
compareNullableValues(classContext, classA.companionObject, classB.companionObject, "CompanionObject")
compareValueLists(classContext, classA.nestedClasses, classB.nestedClasses, "NestedClass")
compareValueLists(classContext, classA.sealedSubclasses, classB.sealedSubclasses, "SealedSubclass")
compareValueLists(classContext, classA.enumEntries, classB.enumEntries, "EnumEntry")
compareUniqueEntityLists(
containerContext = classContext,
entityListA = classA.klibEnumEntries,
entityListB = classB.klibEnumEntries,
entityKind = "KlibEnumEntry",
groupingKeySelector = { _, enumEntry -> enumEntry.name }
) { klibEnumEntryContext, entryA, entryB ->
compareAnnotationLists(klibEnumEntryContext, entryA.annotations, entryB.annotations)
compareNullableValues(klibEnumEntryContext, entryA.ordinal, entryB.ordinal, "Ordinal")
}
}
private fun compareTypeAliases(
typeAliasContext: Context,
typeAliasA: KmTypeAlias,
typeAliasB: KmTypeAlias
) {
compareFlags(typeAliasContext, typeAliasA.flags, typeAliasB.flags, TYPE_ALIAS_FLAGS)
compareAnnotationLists(typeAliasContext, typeAliasA.annotations, typeAliasB.annotations)
compareTypeParameterLists(typeAliasContext, typeAliasA.typeParameters, typeAliasB.typeParameters)
compareEntities(
containerContext = typeAliasContext,
entityA = typeAliasA.underlyingType,
entityB = typeAliasB.underlyingType,
entityKind = "UnderlyingType",
entitiesComparator = ::compareTypes
)
compareEntities(
containerContext = typeAliasContext,
entityA = typeAliasA.expandedType,
entityB = typeAliasB.expandedType,
entityKind = "ExpandedType",
entitiesComparator = ::compareTypes
)
}
@Suppress("DuplicatedCode")
private fun compareProperties(
propertyContext: Context,
propertyA: KmProperty,
propertyB: KmProperty
) {
compareFlags(propertyContext, propertyA.flags, propertyB.flags, PROPERTY_FLAGS)
compareFlags(propertyContext, propertyA.getterFlags, propertyB.getterFlags, PROPERTY_ACCESSOR_FLAGS, "GetterFlag")
compareFlags(propertyContext, propertyA.setterFlags, propertyB.setterFlags, PROPERTY_ACCESSOR_FLAGS, "SetterFlag")
compareAnnotationLists(propertyContext, propertyA.annotations, propertyB.annotations)
compareAnnotationLists(propertyContext, propertyA.getterAnnotations, propertyB.getterAnnotations, "GetterAnnotation")
compareAnnotationLists(propertyContext, propertyA.setterAnnotations, propertyB.setterAnnotations, "SetterAnnotation")
compareTypeParameterLists(propertyContext, propertyA.typeParameters, propertyB.typeParameters)
compareNullableEntities(
containerContext = propertyContext,
entityA = propertyA.receiverParameterType,
entityB = propertyB.receiverParameterType,
entityKind = "ReceiverParameterType",
entitiesComparator = ::compareTypes
)
compareEntities(
containerContext = propertyContext,
entityA = propertyA.returnType,
entityB = propertyB.returnType,
entityKind = "ReturnType",
entitiesComparator = ::compareTypes
)
compareNullableEntities(
containerContext = propertyContext,
entityA = propertyA.setterParameter,
entityB = propertyB.setterParameter,
entityKind = "SetterValueParameter",
entitiesComparator = ::compareValueParameters
)
compareNullableValues(propertyContext, propertyA.compileTimeValue, propertyB.compileTimeValue, "CompileTimeValue")
}
@Suppress("DuplicatedCode")
private fun compareFunctions(
functionContext: Context,
functionA: KmFunction,
functionB: KmFunction
) {
compareFlags(functionContext, functionA.flags, functionB.flags, FUNCTION_FLAGS)
compareAnnotationLists(functionContext, functionA.annotations, functionB.annotations)
compareTypeParameterLists(functionContext, functionA.typeParameters, functionB.typeParameters)
compareNullableEntities(
containerContext = functionContext,
entityA = functionA.receiverParameterType,
entityB = functionB.receiverParameterType,
entityKind = "ReceiverParameterType",
entitiesComparator = ::compareTypes
)
compareEntities(
containerContext = functionContext,
entityA = functionA.returnType,
entityB = functionB.returnType,
entityKind = "ReturnType",
entitiesComparator = ::compareTypes
)
compareValueParameterLists(functionContext, functionA.valueParameters, functionB.valueParameters)
compareNullableEntities(
containerContext = functionContext,
entityA = functionA.contract,
entityB = functionB.contract,
entityKind = "Contract"
) { contractContext, contractA, contractB ->
compareUniqueEntityLists(
containerContext = contractContext,
entityListA = contractA.effects,
entityListB = contractB.effects,
entityKind = "Effect",
groupingKeySelector = { index, _ -> index.toString() }
) { effectContext, effectA, effectB ->
compareValues(effectContext, effectA.type, effectB.type, "EffectType")
compareNullableValues(effectContext, effectA.invocationKind, effectB.invocationKind, "EffectInvocationKind")
compareEffectExpressionLists(
containerContext = effectContext,
effectExpressionListA = effectA.constructorArguments,
effectExpressionListB = effectB.constructorArguments,
effectExpressionKind = "ConstructorArguments"
)
compareNullableEntities(
containerContext = effectContext,
entityA = effectA.conclusion,
entityB = effectB.conclusion,
entityKind = "Conclusion",
entitiesComparator = ::compareEffectExpressions
)
}
}
}
private fun compareConstructors(
constructorContext: Context,
constructorA: KmConstructor,
constructorB: KmConstructor
) {
compareFlags(constructorContext, constructorA.flags, constructorB.flags, CONSTRUCTOR_FLAGS)
compareAnnotationLists(constructorContext, constructorA.annotations, constructorB.annotations)
compareValueParameterLists(constructorContext, constructorA.valueParameters, constructorB.valueParameters)
}
private fun compareValueParameters(
valueParameterContext: Context,
valueParameterA: KmValueParameter,
valueParameterB: KmValueParameter
) {
compareFlags(valueParameterContext, valueParameterA.flags, valueParameterB.flags, VALUE_PARAMETER_FLAGS)
compareAnnotationLists(valueParameterContext, valueParameterA.annotations, valueParameterB.annotations)
compareNullableEntities(
containerContext = valueParameterContext,
entityA = valueParameterA.type,
entityB = valueParameterB.type,
entityKind = "Type",
entitiesComparator = ::compareTypes
)
compareNullableEntities(
containerContext = valueParameterContext,
entityA = valueParameterA.varargElementType,
entityB = valueParameterB.varargElementType,
entityKind = "VarargElementType",
entitiesComparator = ::compareTypes
)
}
private fun compareTypes(
typeContext: Context,
typeA: KmType,
typeB: KmType
) {
compareFlags(typeContext, typeA.flags, typeB.flags, TYPE_FLAGS)
compareAnnotationLists(typeContext, typeA.annotations, typeB.annotations)
compareValues(typeContext, typeA.classifier, typeB.classifier, "Classifier")
compareUniqueEntityLists(
containerContext = typeContext,
entityListA = typeA.arguments,
entityListB = typeB.arguments,
entityKind = "TypeProjection",
groupingKeySelector = { index, _ -> index.toString() }
) { typeProjectionContext, typeProjectionA, typeProjectionB ->
compareNullableEntities(
containerContext = typeProjectionContext,
entityA = typeProjectionA.type,
entityB = typeProjectionB.type,
entityKind = "Type",
entitiesComparator = ::compareTypes
)
compareNullableValues(typeProjectionContext, typeProjectionA.variance, typeProjectionB.variance, "Variance")
}
compareNullableEntities(
containerContext = typeContext,
entityA = typeA.abbreviatedType,
entityB = typeB.abbreviatedType,
entityKind = "AbbreviatedType",
entitiesComparator = ::compareTypes
)
compareNullableEntities(
containerContext = typeContext,
entityA = typeA.outerType,
entityB = typeB.outerType,
entityKind = "OuterType",
entitiesComparator = ::compareTypes
)
compareNullableEntities(
containerContext = typeContext,
entityA = typeA.flexibleTypeUpperBound,
entityB = typeB.flexibleTypeUpperBound,
entityKind = "FlexibleTypeUpperBounds",
) { typeUpperBoundContext, upperBoundA, upperBoundB ->
compareEntities(
containerContext = typeUpperBoundContext,
entityA = upperBoundA.type,
entityB = upperBoundB.type,
entityKind = "Type",
entitiesComparator = ::compareTypes
)
compareNullableValues(typeUpperBoundContext, upperBoundA.typeFlexibilityId, upperBoundB.typeFlexibilityId, "TypeFlexibilityId")
}
}
private fun compareTypeParameters(
typeParameterContext: Context,
typeParameterA: KmTypeParameter,
typeParameterB: KmTypeParameter
) {
compareFlags(typeParameterContext, typeParameterA.flags, typeParameterB.flags, TYPE_PARAMETER_FLAGS)
compareAnnotationLists(typeParameterContext, typeParameterA.annotations, typeParameterB.annotations)
compareValues(typeParameterContext, typeParameterA.id, typeParameterB.id, "Id")
compareValues(typeParameterContext, typeParameterA.name, typeParameterB.name, "Name")
compareValues(typeParameterContext, typeParameterA.variance, typeParameterB.variance, "Variance")
compareTypeLists(typeParameterContext, typeParameterA.upperBounds, typeParameterB.upperBounds, "UpperBoundType")
}
private fun compareEffectExpressions(
effectExpressionContext: Context,
effectExpressionA: KmEffectExpression,
effectExpressionB: KmEffectExpression
) {
compareFlags(effectExpressionContext, effectExpressionA.flags, effectExpressionB.flags, EFFECT_EXPRESSION_FLAGS)
compareNullableValues(effectExpressionContext, effectExpressionA.parameterIndex, effectExpressionB.parameterIndex, "ParameterIndex")
compareNullableValues(effectExpressionContext, effectExpressionA.constantValue, effectExpressionB.constantValue, "ConstantValue")
compareNullableEntities(
containerContext = effectExpressionContext,
entityA = effectExpressionA.isInstanceType,
entityB = effectExpressionB.isInstanceType,
entityKind = "IsInstanceType",
entitiesComparator = ::compareTypes
)
compareEffectExpressionLists(
containerContext = effectExpressionContext,
effectExpressionListA = effectExpressionA.andArguments,
effectExpressionListB = effectExpressionB.andArguments,
effectExpressionKind = "AndArguments"
)
compareEffectExpressionLists(
containerContext = effectExpressionContext,
effectExpressionListA = effectExpressionA.orArguments,
effectExpressionListB = effectExpressionB.orArguments,
effectExpressionKind = "OrArguments"
)
}
private fun <E : Any> compareValues(
containerContext: Context,
valueA: E,
valueB: E,
valueKind: String,
valueName: String? = null
) {
if (valueA != valueB)
mismatches += Mismatch.DifferentValues(valueKind, valueName.orEmpty(), containerContext.path, valueA, valueB)
}
private fun <E : Any> compareValueLists(
containerContext: Context,
listA: Collection<E>,
listB: Collection<E>,
valueKind: String
) {
if (listA.isEmpty() && listB.isEmpty())
return
for (missingInA in listB subtract listA) {
mismatches += Mismatch.MissingEntity(valueKind, missingInA.toString(), containerContext.path, missingInA, true)
}
for (missingInB in listA subtract listB) {
mismatches += Mismatch.MissingEntity(valueKind, missingInB.toString(), containerContext.path, missingInB, false)
}
}
private fun <E : Any> compareNullableValues(
containerContext: Context,
valueA: E?,
valueB: E?,
valueKind: String
) {
when {
valueA == null && valueB != null -> {
mismatches += Mismatch.MissingEntity(valueKind, "", containerContext.path, valueB, true)
}
valueA != null && valueB == null -> {
mismatches += Mismatch.MissingEntity(valueKind, "", containerContext.path, valueA, false)
}
valueA != null && valueB != null -> {
compareValues(containerContext, valueA, valueB, valueKind)
}
}
}
private fun <E : Any> compareEntities(
containerContext: Context,
entityA: E,
entityB: E,
entityKind: String,
entityKey: String? = null,
entitiesComparator: (Context, E, E) -> Unit
) {
val entityContext = containerContext.next("$entityKind${if (entityKey.isNullOrEmpty()) "" else " $entityKey"}")
entitiesComparator(entityContext, entityA, entityB)
}
private fun <E : Any> compareNullableEntities(
containerContext: Context,
entityA: E?,
entityB: E?,
entityKind: String,
entityKey: String? = null,
entitiesComparator: (Context, E, E) -> Unit
) {
when {
entityA == null && entityB != null -> {
mismatches += Mismatch.MissingEntity(entityKind, entityKey.orEmpty(), containerContext.path, entityB, true)
}
entityA != null && entityB == null -> {
mismatches += Mismatch.MissingEntity(entityKind, entityKey.orEmpty(), containerContext.path, entityA, false)
}
entityA != null && entityB != null -> {
compareEntities(containerContext, entityA, entityB, entityKind, entityKey, entitiesComparator)
}
}
}
private fun <E : Any> compareUniqueEntityLists(
containerContext: Context,
entityListA: List<E>,
entityListB: List<E>,
entityKind: String,
groupingKeySelector: (index: Int, E) -> String,
entitiesComparator: (Context, E, E) -> Unit
) {
compareRepetitiveEntityLists(
entityListA = entityListA,
entityListB = entityListB,
groupingKeySelector = groupingKeySelector,
groupedEntityListsComparator = { entityKey: String, entitiesA: List<E>, entitiesB: List<E> ->
compareNullableEntities(
containerContext = containerContext,
entityA = entitiesA.singleOrNull(),
entityB = entitiesB.singleOrNull(),
entityKind = entityKind,
entityKey = entityKey,
entitiesComparator = entitiesComparator
)
}
)
}
private fun <E : Any> compareRepetitiveEntityLists(
entityListA: List<E>,
entityListB: List<E>,
groupingKeySelector: (index: Int, E) -> String,
groupedEntityListsComparator: (entityKey: String, List<E>, List<E>) -> Unit
) {
val filteredEntityListA: List<E> = entityListA.filter(config::shouldCheckDeclaration)
val filteredEntityListB: List<E> = entityListB.filter(config::shouldCheckDeclaration)
if (filteredEntityListA.isEmpty() && filteredEntityListB.isEmpty())
return
val groupedEntitiesA: Map<String, List<E>> =
filteredEntityListA.groupByIndexed { index, entity -> groupingKeySelector(index, entity) }
val groupedEntitiesB: Map<String, List<E>> =
filteredEntityListB.groupByIndexed { index, entity -> groupingKeySelector(index, entity) }
val entityKeys = groupedEntitiesA.keys union groupedEntitiesB.keys
for (entityKey in entityKeys) {
val entitiesA: List<E> = groupedEntitiesA[entityKey].orEmpty()
val entitiesB: List<E> = groupedEntitiesB[entityKey].orEmpty()
groupedEntityListsComparator(entityKey, entitiesA, entitiesB)
}
}
private fun compareFlags(
containerContext: Context,
flagsA: Flags,
flagsB: Flags,
flagsToCompare: Array<KProperty0<Flag>>,
flagKind: String = "Flag"
) {
for (flag in flagsToCompare) {
val valueA = flag.get()(flagsA)
val valueB = flag.get()(flagsB)
compareValues(containerContext, valueA, valueB, flagKind, flag.name)
}
}
companion object {
private val VISIBILITY_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::IS_INTERNAL,
Flag.Common::IS_PRIVATE,
Flag.Common::IS_PROTECTED,
Flag.Common::IS_PUBLIC,
Flag.Common::IS_PRIVATE_TO_THIS,
Flag.Common::IS_LOCAL
)
private val MODALITY_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::IS_FINAL,
Flag.Common::IS_OPEN,
Flag.Common::IS_ABSTRACT,
Flag.Common::IS_SEALED
)
private val CLASS_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS,
*MODALITY_FLAGS,
Flag.Class::IS_CLASS,
Flag.Class::IS_INTERFACE,
Flag.Class::IS_ENUM_CLASS,
Flag.Class::IS_ENUM_ENTRY,
Flag.Class::IS_ANNOTATION_CLASS,
Flag.Class::IS_OBJECT,
Flag.Class::IS_COMPANION_OBJECT,
Flag.Class::IS_INNER,
Flag.Class::IS_DATA,
Flag.Class::IS_EXTERNAL,
Flag.Class::IS_EXPECT,
Flag.Class::IS_INLINE,
Flag.Class::IS_FUN
)
private val TYPE_ALIAS_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS
)
private val CONSTRUCTOR_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS,
Flag.Constructor::IS_SECONDARY,
Flag.Constructor::HAS_NON_STABLE_PARAMETER_NAMES
)
private val FUNCTION_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS,
*MODALITY_FLAGS,
Flag.Function::IS_DECLARATION,
Flag.Function::IS_FAKE_OVERRIDE,
Flag.Function::IS_DELEGATION,
Flag.Function::IS_SYNTHESIZED,
Flag.Function::IS_OPERATOR,
Flag.Function::IS_INFIX,
Flag.Function::IS_INLINE,
Flag.Function::IS_TAILREC,
Flag.Function::IS_EXTERNAL,
Flag.Function::IS_SUSPEND,
Flag.Function::IS_EXPECT,
Flag.Function::HAS_NON_STABLE_PARAMETER_NAMES
)
private val PROPERTY_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS,
*MODALITY_FLAGS,
Flag.Property::IS_DECLARATION,
Flag.Property::IS_FAKE_OVERRIDE,
Flag.Property::IS_DELEGATION,
Flag.Property::IS_SYNTHESIZED,
Flag.Property::IS_VAR,
Flag.Property::HAS_GETTER,
Flag.Property::HAS_SETTER,
Flag.Property::IS_CONST,
Flag.Property::IS_LATEINIT,
Flag.Property::HAS_CONSTANT,
Flag.Property::IS_EXTERNAL,
Flag.Property::IS_DELEGATED,
Flag.Property::IS_EXPECT
)
private val PROPERTY_ACCESSOR_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
*VISIBILITY_FLAGS,
*MODALITY_FLAGS,
Flag.PropertyAccessor::IS_NOT_DEFAULT,
Flag.PropertyAccessor::IS_EXTERNAL,
Flag.PropertyAccessor::IS_INLINE
)
private val TYPE_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Type::IS_NULLABLE,
Flag.Type::IS_SUSPEND
)
private val TYPE_PARAMETER_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.TypeParameter::IS_REIFIED
)
private val VALUE_PARAMETER_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.Common::HAS_ANNOTATIONS,
Flag.ValueParameter::DECLARES_DEFAULT_VALUE,
Flag.ValueParameter::IS_CROSSINLINE,
Flag.ValueParameter::IS_NOINLINE
)
private val EFFECT_EXPRESSION_FLAGS: Array<KProperty0<Flag>> = arrayOf(
Flag.EffectExpression::IS_NEGATED,
Flag.EffectExpression::IS_NULL_CHECK_PREDICATE
)
/**
* We need a stable order for overloaded functions.
*/
private fun KmFunction.mangle(): String {
return buildString {
receiverParameterType?.classifier?.let(::append)
append('.')
append(name)
append('.')
typeParameters.joinTo(this, prefix = "<", postfix = ">", transform = KmTypeParameter::name)
append('.')
valueParameters.joinTo(this, prefix = "(", postfix = ")", transform = KmValueParameter::name)
}
}
private fun KmConstructor.mangle(): String {
return valueParameters.joinToString(prefix = "(", postfix = ")", transform = KmValueParameter::name)
}
private inline fun <T, K> Iterable<T>.groupByIndexed(keySelector: (Int, T) -> K): Map<K, List<T>> {
return mutableMapOf<K, MutableList<T>>().apply {
this@groupByIndexed.forEachIndexed { index, element ->
val key = keySelector(index, element)
getOrPut(key) { mutableListOf() } += element
}
}
}
}
}
interface Config {
val rootPathElement: String
get() = "<root>"
/**
* Certain auxiliary metadata entities may be intentionally excluded from comparison.
* Ex: Kotlin/Native interface bridge functions.
*/
fun shouldCheckDeclaration(declaration: Any): Boolean =
when (declaration) {
is KmFunction -> !declaration.name.startsWith(KNI_BRIDGE_FUNCTION_PREFIX)
else -> true
}
companion object Default : Config
}
sealed class Result {
object Success : Result() {
override fun toString() = "Success"
}
class Failure(val mismatches: Collection<Mismatch>) : Result() {
init {
check(mismatches.isNotEmpty())
}
override fun toString() = "Failure (${mismatches.size} mismatches)"
}
}
sealed class Mismatch {
abstract val kind: String
abstract val name: String
abstract val path: List<String>
// an entity has different non-nullable values
data class DifferentValues(
override val kind: String,
override val name: String,
override val path: List<String>,
val valueA: Any,
val valueB: Any
) : Mismatch()
// an entity is missing at one side and present at another side,
// or: an entity has nullable value at one side and non-nullable value at another side
data class MissingEntity(
override val kind: String,
override val name: String,
override val path: List<String>,
val presentValue: Any,
val missingInA: Boolean
) : Mismatch() {
val missingInB: Boolean
get() = !missingInA
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.metadata.utils
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.library.MetadataLibrary
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import java.io.File
import org.jetbrains.kotlin.konan.file.File as KFile
/**
* Provides access to metadata using default compiler's routine.
*/
// TODO: move to a separate module (kotlin-native-utils-metadata?) to share with C-interop tool?
class TrivialLibraryProvider(
private val library: MetadataLibrary
) : KlibModuleMetadata.MetadataLibraryProvider {
override val moduleHeaderData: ByteArray
get() = library.moduleHeaderData
override fun packageMetadata(fqName: String, partName: String): ByteArray =
library.packageMetadata(fqName, partName)
override fun packageMetadataParts(fqName: String): Set<String> =
library.packageMetadataParts(fqName)
companion object {
fun readLibraryMetadata(libraryPath: File): KlibModuleMetadata {
check(libraryPath.exists()) { "Library does not exist: $libraryPath" }
val library = resolveSingleFileKlib(KFile(libraryPath.absolutePath), strategy = ToolingSingleFileKlibResolveStrategy)
return KlibModuleMetadata.read(TrivialLibraryProvider(library))
}
}
}
@@ -9,8 +9,10 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.types.getAbbreviation
internal const val KNI_BRIDGE_FUNCTION_PREFIX = "kniBridge"
internal fun SimpleFunctionDescriptor.isKniBridgeFunction() =
name.asString().startsWith("kniBridge")
name.asString().startsWith(KNI_BRIDGE_FUNCTION_PREFIX)
internal fun SimpleFunctionDescriptor.isDeprecatedTopLevelFunction() =
containingDeclaration is PackageFragmentDescriptor && annotations.hasAnnotation(DEPRECATED_ANNOTATION_FQN)