diff --git a/native/commonizer/build.gradle.kts b/native/commonizer/build.gradle.kts index c48707140c0..c9e124ef4b5 100644 --- a/native/commonizer/build.gradle.kts +++ b/native/commonizer/build.gradle.kts @@ -36,6 +36,8 @@ dependencies { testImplementation(commonDep("junit:junit")) testImplementation(projectTests(":compiler:tests-common")) + testImplementation(project(":kotlinx-metadata-klib")) { isTransitive = false } + testImplementation(project(":kotlinx-metadata")) { isTransitive = false } } val runCommonizer by tasks.registering(JavaExec::class) { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/TrivialLibraryProvider.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/KotlinMetadataLibraryProvider.kt similarity index 77% rename from native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/TrivialLibraryProvider.kt rename to native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/KotlinMetadataLibraryProvider.kt index 044c455c0bd..7bc2dcad001 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/TrivialLibraryProvider.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/KotlinMetadataLibraryProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 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. */ @@ -15,11 +15,8 @@ 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 { - +// TODO: extract to a separate module (kotlin-native-utils-metadata?) to share with C-interop tool? +class KotlinMetadataLibraryProvider(private val library: MetadataLibrary) : KlibModuleMetadata.MetadataLibraryProvider { override val moduleHeaderData: ByteArray get() = library.moduleHeaderData @@ -34,7 +31,7 @@ class TrivialLibraryProvider( check(libraryPath.exists()) { "Library does not exist: $libraryPath" } val library = resolveSingleFileKlib(KFile(libraryPath.absolutePath), strategy = ToolingSingleFileKlibResolveStrategy) - return KlibModuleMetadata.read(TrivialLibraryProvider(library)) + return KlibModuleMetadata.read(KotlinMetadataLibraryProvider(library)) } } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/MetadataDeclarationsComparator.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/MetadataDeclarationsComparator.kt index df647483633..a2cc9a88c6f 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/MetadataDeclarationsComparator.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/MetadataDeclarationsComparator.kt @@ -11,8 +11,68 @@ import org.jetbrains.kotlin.descriptors.commonizer.utils.KNI_BRIDGE_FUNCTION_PRE import java.util.* import kotlin.reflect.KProperty0 -// TODO: move to kotlinx-metadata library? +// TODO: extract to kotlinx-metadata-klib library? class MetadataDeclarationsComparator(private val config: Config = Config.Default) { + + interface Config { + val rootPathElement: String + get() = "" + + /** + * 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) : 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 + + // an entity has different non-nullable values + data class DifferentValues( + override val kind: String, + override val name: String, + override val path: List, + 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, + val existentValue: Any, + val missingInA: Boolean + ) : Mismatch() { + val missingInB: Boolean + get() = !missingInA + } + } + private val mismatches = mutableListOf() private class Context(pathElement: String, parent: Context? = null) { @@ -831,62 +891,3 @@ class MetadataDeclarationsComparator(private val config: Config = Config.Default } } } - -interface Config { - val rootPathElement: String - get() = "" - - /** - * 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) : 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 - - // an entity has different non-nullable values - data class DifferentValues( - override val kind: String, - override val name: String, - override val path: List, - 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, - val presentValue: Any, - val missingInA: Boolean - ) : Mismatch() { - val missingInB: Boolean - get() = !missingInA - } -} diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/SerializedMetadataLibraryProvider.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/SerializedMetadataLibraryProvider.kt new file mode 100644 index 00000000000..2045b9f2c17 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/metadata/utils/SerializedMetadataLibraryProvider.kt @@ -0,0 +1,44 @@ +/* + * 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 + +class SerializedMetadataLibraryProvider( + override val moduleHeaderData: ByteArray, + fragments: List>, + fragmentNames: List +) : KlibModuleMetadata.MetadataLibraryProvider { + private val fragmentMap: Map> + + init { + check(fragments.size == fragmentNames.size) + + fragmentMap = fragmentNames.mapIndexed { fragmentIndex, fragmentName -> + // fragmentName is package FQ name, fragmentShortName is right-most part of package FQ name + val fragmentShortName = fragmentName.substringAfterLast('.') + + val fragmentParts = fragments[fragmentIndex] + val digitCount = fragmentParts.size.toString().length + + // N.B. the same fragment part numbering scheme as in org.jetbrains.kotlin.library.impl.MetadataWriterImpl + val fragmentPartMap = fragmentParts.mapIndexed { partIndex, part -> + val partName = partIndex.toString().padStart(digitCount, '0') + "_" + fragmentShortName + partName to part + }.toMap() + + fragmentName to fragmentPartMap + }.toMap() + } + + override fun packageMetadataParts(fqName: String): Set { + return fragmentMap.getValue(fqName).keys + } + + override fun packageMetadata(fqName: String, partName: String): ByteArray { + return fragmentMap.getValue(fqName).getValue(partName) + } +} diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt index d971f5cfd84..84b19cc311e 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt @@ -11,11 +11,14 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory +import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer +import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -30,6 +33,7 @@ import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBo import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.library.SerializedMetadata import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.CommonPlatforms import org.jetbrains.kotlin.psi.KtFile @@ -74,25 +78,21 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() { val sharedTarget: SharedTarget = analyzedModules.sharedTarget assertEquals(sharedTarget, result.sharedTarget) - val sharedModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedModules.getValue(sharedTarget) - val sharedModuleByCommonizer: ModuleDescriptor = - (result.modulesByTargets.getValue(sharedTarget).single() as ModuleResult.Commonized).module!! + val sharedModuleAsExpected: SerializedMetadata = analyzedModules.commonizedModules.getValue(sharedTarget) + val sharedModuleByCommonizer: SerializedMetadata = + (result.modulesByTargets.getValue(sharedTarget).single() as ModuleResult.Commonized).metadata.metadata - assertValidModule(sharedModuleAsExpected) - assertValidModule(sharedModuleByCommonizer) - assertModulesAreEqual(sharedModuleAsExpected, sharedModuleByCommonizer, "\"$sharedTarget\" target") + assertModulesAreEqual(sharedModuleAsExpected, sharedModuleByCommonizer, sharedTarget) val leafTargets: Set = analyzedModules.leafTargets assertEquals(leafTargets, result.leafTargets) for (leafTarget in leafTargets) { - val leafTargetModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedModules.getValue(leafTarget) - val leafTargetModuleByCommonizer: ModuleDescriptor = - (result.modulesByTargets.getValue(leafTarget).single() as ModuleResult.Commonized).module!! + val leafTargetModuleAsExpected: SerializedMetadata = analyzedModules.commonizedModules.getValue(leafTarget) + val leafTargetModuleByCommonizer: SerializedMetadata = + (result.modulesByTargets.getValue(leafTarget).single() as ModuleResult.Commonized).metadata.metadata - assertValidModule(leafTargetModuleAsExpected) - assertValidModule(leafTargetModuleByCommonizer) - assertModulesAreEqual(leafTargetModuleAsExpected, leafTargetModuleByCommonizer, "\"$leafTarget\" target") + assertModulesAreEqual(leafTargetModuleAsExpected, leafTargetModuleByCommonizer, leafTarget) } } } @@ -182,7 +182,7 @@ private class AnalyzedModuleDependencies( private class AnalyzedModules( val originalModules: Map, - val commonizedModules: Map, + val commonizedModules: Map, val dependeeModules: Map> ) { val leafTargets: Set @@ -232,11 +232,16 @@ private class AnalyzedModules( parentDisposable: Disposable ): AnalyzedModules = with(sourceModuleRoots) { // phase 1: provide the modules that are the dependencies for "original" and "commonized" modules - val (dependeeModules, dependencies) = createDependeeModules(sharedTarget, dependeeRoots, parentDisposable) + val (dependeeModules: Map>, dependencies: AnalyzedModuleDependencies) = + createDependeeModules(sharedTarget, dependeeRoots, parentDisposable) // phase 2: build "original" and "commonized" modules - val originalModules = createModules(sharedTarget, originalRoots, dependencies, parentDisposable) - val commonizedModules = createModules(sharedTarget, commonizedRoots, dependencies, parentDisposable) + val originalModules: Map = + createModules(sharedTarget, originalRoots, dependencies, parentDisposable) + + val commonizedModules: Map = + createModules(sharedTarget, commonizedRoots, dependencies, parentDisposable) + .mapValues { (_, moduleDescriptor) -> serializer.serializeModule(moduleDescriptor) } return AnalyzedModules(originalModules, commonizedModules, dependeeModules) } @@ -334,6 +339,13 @@ private class AnalyzedModules( return module } + + private val serializer = KlibMetadataMonolithicSerializer( + languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, + metadataVersion = KlibMetadataVersion.INSTANCE, + skipExpects = false, + project = null + ) } } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt deleted file mode 100644 index f3777515570..00000000000 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/ComparingDeclarationsVisitor.kt +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright 2010-2019 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.utils - -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.descriptors.annotations.Annotations -import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.* -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.resolve.constants.AnnotationValue -import org.jetbrains.kotlin.resolve.constants.ConstantValue -import org.jetbrains.kotlin.resolve.descriptorUtil.classId -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.types.AbbreviatedType -import org.jetbrains.kotlin.types.KotlinType -import kotlin.contracts.ExperimentalContracts -import kotlin.reflect.KCallable -import kotlin.test.fail - -@ExperimentalContracts -internal class ComparingDeclarationsVisitor( - val designatorMessage: String -) : DeclarationDescriptorVisitor { - - inner class Context private constructor( - private val actual: DeclarationDescriptor?, - private val path: List - ) { - constructor(actual: DeclarationDescriptor?) : this(actual, listOf(actual.toString())) - - fun nextLevel(nextActual: DeclarationDescriptor?) = Context(nextActual, path + nextActual.toString()) - - fun nextLevel(customPathElement: String) = Context(actual, path + customPathElement) - - inline fun getActualAs() = actual as T - - override fun toString() = - """ - |Context: ${this@ComparingDeclarationsVisitor.designatorMessage} - |Path: ${path.joinToString(separator = " ->\n\t")}" - """.trimMargin() - } - - override fun visitModuleDeclaration(expected: ModuleDescriptor, context: Context) { - val actual = context.getActualAs() - - context.assertFieldsEqual(expected::getName, actual::getName) - - fun collectPackageMemberScopes(module: ModuleDescriptor): Map = mutableMapOf().also { - module.collectNonEmptyPackageMemberScopes { packageFqName, memberScope -> - if (memberScope.getContributedDescriptors().isNotEmpty()) - it[packageFqName] = memberScope - } - } - - val expectedPackageMemberScopes = collectPackageMemberScopes(expected) - val actualPackageMemberScopes = collectPackageMemberScopes(actual) - - context.assertSetsEqual(expectedPackageMemberScopes.keys, actualPackageMemberScopes.keys, "sets of packages") - - for (packageFqName in expectedPackageMemberScopes.keys) { - val expectedMemberScope = expectedPackageMemberScopes.getValue(packageFqName) - val actualMemberScope = actualPackageMemberScopes.getValue(packageFqName) - - visitMemberScopes(expectedMemberScope, actualMemberScope, context.nextLevel("package member scope [$packageFqName]")) - } - } - - private fun visitMemberScopes(expected: MemberScope, actual: MemberScope, context: Context) { - val expectedProperties = mutableMapOf() - val expectedFunctions = mutableMapOf() - val expectedClasses = mutableMapOf() - val expectedTypeAliases = mutableMapOf() - - expected.collectMembers( - PropertyCollector { expectedProperties[PropertyApproximationKey(it)] = it }, - FunctionCollector { expectedFunctions[FunctionApproximationKey(it)] = it }, - ClassCollector { expectedClasses[it.fqNameSafe] = it }, - TypeAliasCollector { expectedTypeAliases[it.fqNameSafe] = it } - ) - - val actualProperties = mutableMapOf() - val actualFunctions = mutableMapOf() - val actualClasses = mutableMapOf() - val actualTypeAliases = mutableMapOf() - - actual.collectMembers( - PropertyCollector { actualProperties[PropertyApproximationKey(it)] = it }, - FunctionCollector { actualFunctions[FunctionApproximationKey(it)] = it }, - ClassCollector { actualClasses[it.fqNameSafe] = it }, - TypeAliasCollector { actualTypeAliases[it.fqNameSafe] = it } - ) - - context.assertSetsEqual(expectedProperties.keys, actualProperties.keys, "sets of properties") - - expectedProperties.forEach { (propertyKey, expectedProperty) -> - val actualProperty = actualProperties.getValue(propertyKey) - expectedProperty.accept(this, context.nextLevel(actualProperty)) - } - - context.assertSetsEqual(expectedFunctions.keys, actualFunctions.keys, "sets of functions") - - expectedFunctions.forEach { (functionKey, expectedFunction) -> - val actualFunction = actualFunctions.getValue(functionKey) - expectedFunction.accept(this, context.nextLevel(actualFunction)) - } - - context.assertSetsEqual(expectedClasses.keys, actualClasses.keys, "sets of classes") - - expectedClasses.forEach { (classFqName, expectedClass) -> - val actualClass = actualClasses.getValue(classFqName) - expectedClass.accept(this, context.nextLevel(actualClass)) - } - - context.assertSetsEqual(expectedTypeAliases.keys, actualTypeAliases.keys, "sets of type aliases") - - expectedTypeAliases.forEach { (typeAliasFqName, expectedTypeAlias) -> - val actualTypeAlias = actualTypeAliases.getValue(typeAliasFqName) - expectedTypeAlias.accept(this, context.nextLevel(actualTypeAlias)) - } - } - - override fun visitFunctionDescriptor(expected: FunctionDescriptor, context: Context) { - @Suppress("NAME_SHADOWING") - val expected = expected as SimpleFunctionDescriptor - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Function annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - context.assertFieldsEqual(expected::getModality, actual::getModality) - context.assertFieldsEqual(expected::getKind, actual::getKind) - context.assertFieldsEqual(expected::isOperator, actual::isOperator) - context.assertFieldsEqual(expected::isInfix, actual::isInfix) - context.assertFieldsEqual(expected::isInline, actual::isInline) - context.assertFieldsEqual(expected::isTailrec, actual::isTailrec) - context.assertFieldsEqual(expected::isSuspend, actual::isSuspend) - context.assertFieldsEqual(expected::isExternal, actual::isExternal) - context.assertFieldsEqual(expected::isExpect, actual::isExpect) - context.assertFieldsEqual(expected::hasStableParameterNames, actual::hasStableParameterNames) - context.assertFieldsEqual(expected::hasSynthesizedParameterNames, actual::hasSynthesizedParameterNames) - - if (!expected.isActual || actual.kind != CallableMemberDescriptor.Kind.DELEGATION) { - context.assertFieldsEqual(expected::isActual, actual::isActual) - } /* else { - // don't check, because there can be any value in expect.isActual - // see org.jetbrains.kotlin.resolve.DelegationResolver - } */ - - visitType(expected.returnType, actual.returnType, context.nextLevel("Function type")) - - visitValueParameterDescriptorList(expected.valueParameters, actual.valueParameters, context.nextLevel("Function value parameters")) - - visitReceiverParameterDescriptor(expected.extensionReceiverParameter, context.nextLevel(actual.extensionReceiverParameter)) - visitReceiverParameterDescriptor(expected.dispatchReceiverParameter, context.nextLevel(actual.dispatchReceiverParameter)) - - visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Function type parameters")) - } - - private fun visitValueParameterDescriptorList( - expected: List, - actual: List, - context: Context - ) { - context.assertEquals(expected.size, actual.size, "Size of value parameters list") - - expected.forEachIndexed { index, expectedParam -> - val actualParam = actual[index] - expectedParam.accept(this, context.nextLevel(actualParam)) - } - } - - override fun visitValueParameterDescriptor(expected: ValueParameterDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Value parameter annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::index, actual::index) - context.assertFieldsEqual(expected::declaresDefaultValue, actual::declaresDefaultValue) - context.assertFieldsEqual(expected::isCrossinline, actual::isCrossinline) - context.assertFieldsEqual(expected::isNoinline, actual::isNoinline) - visitType(expected.type, actual.type, context.nextLevel("Value parameter type")) - visitType(expected.varargElementType, actual.varargElementType, context.nextLevel("Value parameter vararg element type")) - } - - private fun visitTypeParameters(expected: List, actual: List, context: Context) { - context.assertEquals(expected.size, actual.size, "Type parameters list size") - - expected.forEachIndexed { index, expectedParam -> - val actualParam = actual[index] - visitTypeParameterDescriptor(expectedParam, context.nextLevel(actualParam)) - } - } - - override fun visitTypeParameterDescriptor(expected: TypeParameterDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Type parameter annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::getIndex, actual::getIndex) - context.assertFieldsEqual(expected::isCapturedFromOuterDeclaration, actual::isCapturedFromOuterDeclaration) - context.assertFieldsEqual(expected::isReified, actual::isReified) - context.assertFieldsEqual(expected::getVariance, actual::getVariance) - - val expectedUpperBounds = expected.upperBounds - val actualUpperBounds = actual.upperBounds - - context.assertEquals(expectedUpperBounds.size, actualUpperBounds.size, "Size of upper bound types") - - expectedUpperBounds.forEachIndexed { index, expectedType -> - val actualType = actualUpperBounds[index] - visitType(expectedType, actualType, context.nextLevel("Type parameter type")) - } - } - - override fun visitClassDescriptor(expected: ClassDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Class annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - context.assertFieldsEqual(expected::getModality, actual::getModality) - context.assertFieldsEqual(expected::getKind, actual::getKind) - context.assertFieldsEqual(expected::isCompanionObject, actual::isCompanionObject) - context.assertFieldsEqual(expected::isData, actual::isData) - context.assertFieldsEqual(expected::isInline, actual::isInline) - context.assertFieldsEqual(expected::isValue, actual::isValue) - context.assertFieldsEqual(expected::isInner, actual::isInner) - context.assertFieldsEqual(expected::isExternal, actual::isExternal) - context.assertFieldsEqual(expected::isExpect, actual::isExpect) - context.assertFieldsEqual(expected::isActual, actual::isActual) - - visitTypeParameters( - expected.declaredTypeParameters, - actual.declaredTypeParameters, - context.nextLevel("Class declared type parameters") - ) - - if (expected.sealedSubclasses.isNotEmpty() || actual.sealedSubclasses.isNotEmpty()) { - val expectedSealedSubclassesFqNames = expected.sealedSubclasses.mapTo(HashSet()) { it.fqNameSafe } - val actualSealedSubclassesFqNames = actual.sealedSubclasses.mapTo(HashSet()) { it.fqNameSafe } - - context.assertSetsEqual(expectedSealedSubclassesFqNames, actualSealedSubclassesFqNames, "Sealed subclasses FQ names") - } - - val expectedSupertypeSignatures = expected.typeConstructor.supertypes.mapTo(HashSet()) { it.signature } - val actualSupertypeSignatures = actual.typeConstructor.supertypes.mapTo(HashSet()) { it.signature } - - context.assertSetsEqual(expectedSupertypeSignatures, actualSupertypeSignatures, "Supertypes signatures") - - if (expected.constructors.isNotEmpty() || actual.constructors.isNotEmpty()) { - val expectedConstructors = expected.constructors.associateBy { ConstructorApproximationKey(it) } - val actualConstructors = actual.constructors.associateBy { ConstructorApproximationKey(it) } - - context.assertSetsEqual(expectedConstructors.keys, actualConstructors.keys, "sets of class constructors") - - for (key in expectedConstructors.keys) { - val expectedConstructor = expectedConstructors.getValue(key) - val actualConstructor = actualConstructors.getValue(key) - - visitConstructorDescriptor(expectedConstructor, context.nextLevel(actualConstructor)) - } - } - - visitMemberScopes( - expected.unsubstitutedMemberScope, - actual.unsubstitutedMemberScope, - context.nextLevel("class member scope [${expected.fqNameSafe}]") - ) - } - - override fun visitConstructorDescriptor(expected: ConstructorDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Constructor annotations")) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - context.assertFieldsEqual(expected::isPrimary, actual::isPrimary) - context.assertFieldsEqual(expected::getKind, actual::getKind) - context.assertFieldsEqual(expected::hasStableParameterNames, actual::hasStableParameterNames) - context.assertFieldsEqual(expected::hasSynthesizedParameterNames, actual::hasSynthesizedParameterNames) - context.assertFieldsEqual(expected::isExpect, actual::isExpect) - context.assertFieldsEqual(expected::isActual, actual::isActual) - - visitValueParameterDescriptorList( - expected.valueParameters, - actual.valueParameters, - context.nextLevel("Constructor value parameters") - ) - - visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Constructor type parameters")) - } - - override fun visitTypeAliasDescriptor(expected: TypeAliasDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Type alias annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - context.assertFieldsEqual(expected::isActual, actual::isActual) - - visitTypeParameters( - expected.declaredTypeParameters, - actual.declaredTypeParameters, - context.nextLevel("Type alias declared type parameters") - ) - - visitType(expected.underlyingType, actual.underlyingType, context.nextLevel("Type alias underlying type")) - visitType(expected.expandedType, actual.expandedType, context.nextLevel("Type alias expanded type")) - } - - override fun visitPropertyDescriptor(expected: PropertyDescriptor, context: Context) { - val actual = context.getActualAs() - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property annotations")) - context.assertFieldsEqual(expected::getName, actual::getName) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - context.assertFieldsEqual(expected::getModality, actual::getModality) - context.assertFieldsEqual(expected::isVar, actual::isVar) - context.assertFieldsEqual(expected::getKind, actual::getKind) - context.assertFieldsEqual(expected::isLateInit, actual::isLateInit) - context.assertFieldsEqual(expected::isConst, actual::isConst) - context.assertFieldsEqual(expected::isExternal, actual::isExternal) - context.assertFieldsEqual(expected::isExpect, actual::isExpect) - - if (!expected.isActual || actual.kind != CallableMemberDescriptor.Kind.DELEGATION) { - context.assertFieldsEqual(expected::isActual, actual::isActual) - } /* else { - // don't check, because there can be any value in expect.isActual - // see org.jetbrains.kotlin.resolve.DelegationResolver - } */ - - context.assertFieldsEqual(expected::isDelegated, actual::isDelegated) - - visitAnnotations( - expected.delegateField?.annotations, - actual.delegateField?.annotations, - context.nextLevel("Property delegate field annotations") - ) - visitAnnotations( - expected.backingField?.annotations, - actual.backingField?.annotations, - context.nextLevel("Property backing field annotations") - ) - - context.assertEquals(expected.compileTimeInitializer.isNull(), actual.compileTimeInitializer.isNull(), "compile-time initializers") - visitType(expected.type, actual.type, context.nextLevel("Property type")) - - visitPropertyGetterDescriptor(expected.getter, context.nextLevel(actual.getter)) - visitPropertySetterDescriptor(expected.setter, context.nextLevel(actual.setter)) - - visitReceiverParameterDescriptor(expected.extensionReceiverParameter, context.nextLevel(actual.extensionReceiverParameter)) - visitReceiverParameterDescriptor(expected.dispatchReceiverParameter, context.nextLevel(actual.dispatchReceiverParameter)) - - visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Property type parameters")) - } - - override fun visitPropertyGetterDescriptor(expected: PropertyGetterDescriptor?, context: Context) { - val actual = context.getActualAs() - if (expected === actual) return - - check(actual != null && expected != null) - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property getter annotations")) - context.assertFieldsEqual(expected::isDefault, actual::isDefault) - context.assertFieldsEqual(expected::isExternal, actual::isExternal) - context.assertFieldsEqual(expected::isInline, actual::isInline) - } - - override fun visitPropertySetterDescriptor(expected: PropertySetterDescriptor?, context: Context) { - val actual = context.getActualAs() - if (expected === actual) return - - check(actual != null && expected != null) - - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property setter annotations")) - context.assertFieldsEqual(expected::isDefault, actual::isDefault) - context.assertFieldsEqual(expected::isExternal, actual::isExternal) - context.assertFieldsEqual(expected::isInline, actual::isInline) - context.assertFieldsEqual(expected::getVisibility, actual::getVisibility) - visitAnnotations( - expected.valueParameters.single().annotations, - actual.valueParameters.single().annotations, - context.nextLevel("Property setter value parameter annotations") - ) - } - - override fun visitReceiverParameterDescriptor(expected: ReceiverParameterDescriptor?, context: Context) { - val actual = context.getActualAs() - if (expected === actual) return - - check(actual != null && expected != null) - - visitType(expected.type, actual.type, context.nextLevel("Receiver parameter type")) - visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Receiver parameter annotations")) - } - - private fun visitAnnotations(expected: Annotations?, actual: Annotations?, context: Context) { - if (expected === actual || (expected?.isEmpty() != false && actual?.isEmpty() != false)) return - - fun AnnotationDescriptor.getMandatoryFqName(): FqName = fqName ?: context.fail("No FQ name for annotation $this") - - val expectedAnnotations: Map = expected?.associateBy { it.getMandatoryFqName() }.orEmpty() - val actualAnnotations: Map = actual?.associateBy { it.getMandatoryFqName() }.orEmpty() - - context.assertSetsEqual(expectedAnnotations.keys, actualAnnotations.keys, "annotation FQ names") - - for (annotationFqName in expectedAnnotations.keys) { - val expectedAnnotation = expectedAnnotations.getValue(annotationFqName) - val actualAnnotation = actualAnnotations.getValue(annotationFqName) - - visitAnnotation(expectedAnnotation, actualAnnotation, context.nextLevel("Annotation $annotationFqName")) - } - } - - private fun visitAnnotation(expected: AnnotationDescriptor, actual: AnnotationDescriptor, context: Context) { - visitType(expected.type, actual.type, context.nextLevel("annotation type")) - - val expectedValueArguments: Map> = expected.allValueArguments - val actualValueArguments: Map> = actual.allValueArguments - - context.assertSetsEqual(expectedValueArguments.keys, actualValueArguments.keys, "annotation value argument names") - - for (name in expectedValueArguments.keys) { - val expectedValueArgument = expectedValueArguments.getValue(name) - checkConstantSupportedInCommonization( - constantValue = expectedValueArgument, - constantName = name, - owner = expected, - allowAnnotationValues = true, - onError = { context.fail(it) } - ) - - val actualValueArgument = actualValueArguments.getValue(name) - checkConstantSupportedInCommonization( - constantValue = actualValueArgument, - constantName = name, - owner = actual, - allowAnnotationValues = true, - onError = { context.fail(it) } - ) - - context.assertEquals(expectedValueArgument::class, actualValueArgument::class, "annotation value argument value") - if (expectedValueArgument is AnnotationValue && actualValueArgument is AnnotationValue) { - context.assertEquals( - expectedValueArgument.value.fqName, - actualValueArgument.value.fqName, - "nested annotation FQ name" - ) - - visitAnnotation( - expectedValueArgument.value, - actualValueArgument.value, - context.nextLevel("Annotation ${expectedValueArgument.value.fqName}") - ) - } else { - context.assertEquals(expectedValueArgument.value, actualValueArgument.value, "annotation value argument value") - } - } - } - - private fun visitType(expected: KotlinType?, actual: KotlinType?, context: Context) { - if (expected === actual) return - - check(actual != null && expected != null) - - val expectedUnwrapped = expected.unwrap() - val actualUnwrapped = actual.unwrap() - - if (expectedUnwrapped === actualUnwrapped) return - - val expectedAbbreviated = expectedUnwrapped as? AbbreviatedType - val actualAbbreviated = actualUnwrapped as? AbbreviatedType - - context.assertEquals(!expectedAbbreviated.isNull(), !actualAbbreviated.isNull(), "type is abbreviated") - - if (expectedAbbreviated != null && actualAbbreviated != null) { - visitType( - expectedAbbreviated.abbreviation, - actualAbbreviated.abbreviation, - context.nextLevel("Abbreviation type") - ) - visitType( - extractExpandedType(expectedAbbreviated), - extractExpandedType(actualAbbreviated), - context.nextLevel("Expanded type") - ) - } else { - visitAnnotations( - expectedUnwrapped.annotations, - actualUnwrapped.annotations, - context.nextLevel("Type annotations") - ) - - val expectedId = expectedUnwrapped.declarationDescriptor.run { classId?.asString() ?: name.asString() } - val actualId = actualUnwrapped.declarationDescriptor.run { classId?.asString() ?: name.asString() } - - context.assertEquals(expectedId, actualId, "type class ID / name") - - val expectedArguments = expectedUnwrapped.arguments - val actualArguments = actualUnwrapped.arguments - - context.assertEquals(expectedArguments.size, actualArguments.size, "size of type arguments list") - - expectedArguments.forEachIndexed { index, expectedArgument -> - val actualArgument = actualArguments[index] - - context.assertFieldsEqual(expectedArgument::isStarProjection, actualArgument::isStarProjection) - if (!expectedArgument.isStarProjection) { - context.assertFieldsEqual(expectedArgument::getProjectionKind, actualArgument::getProjectionKind) - visitType(expectedArgument.type, actualArgument.type, context.nextLevel("Type argument type")) - } - } - } - } - - private fun Context.assertEquals(expected: T?, actual: T?, subject: String) { - if (expected != actual) - fail( - buildString { - append("Comparing <$subject>:\n") - append("\"$expected\" is not equal to \"$actual\"\n") - } - ) - } - - private fun Context.assertFieldsEqual(expected: KCallable, actual: KCallable) { - val expectedValue = expected.call() - val actualValue = actual.call() - - assertEquals(expectedValue, actualValue, "fields \"$expected\"") - } - - private fun Context.assertSetsEqual(expected: Set, actual: Set, subject: String) { - val expectedMinusActual = expected.subtract(actual) - val actualMinusExpected = actual.subtract(expected) - - if (expectedMinusActual.isNotEmpty() || actualMinusExpected.isNotEmpty()) - fail( - buildString { - appendLine("Comparing $subject:") - appendLine("$expected is not equal to $actual") - appendLine("Expected size: ${expected.size}") - appendLine("Actual size: ${actual.size}") - appendLine("Expected minus actual: $expectedMinusActual") - appendLine("Actual minus expected: $actualMinusExpected") - } - ) - } - - private fun Context.fail(message: String): Nothing { - kotlin.test.fail( - buildString { - if (message.isNotEmpty()) { - if (message.last() != '\n') appendLine(message) else append(message) - } - append(this@fail.toString()) - } - ) - } - - override fun visitPackageViewDescriptor(expected: PackageViewDescriptor, context: Context) = - fail("Comparison of package views not supported") - - override fun visitPackageFragmentDescriptor(expected: PackageFragmentDescriptor, context: Context) = - fail("Comparison of package fragments not supported") - - override fun visitScriptDescriptor(expected: ScriptDescriptor, context: Context) = - fail("Comparison of script descriptors not supported") - - override fun visitVariableDescriptor(expected: VariableDescriptor, context: Context) = - fail("Comparison of variables not supported") -} diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt index a61a2ee6ee6..be10be8f7fc 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/utils/assertions.kt @@ -5,15 +5,16 @@ package org.jetbrains.kotlin.descriptors.commonizer.utils -import org.jetbrains.kotlin.descriptors.* +import kotlinx.metadata.klib.KlibModuleMetadata import org.jetbrains.kotlin.descriptors.commonizer.CommonizerResult -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.test.util.DescriptorValidator.* -import org.jetbrains.kotlin.types.ErrorUtils +import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget +import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator +import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator.Result +import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.SerializedMetadataLibraryProvider +import org.jetbrains.kotlin.library.SerializedMetadata import java.io.File import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract -import kotlin.test.assertFalse import kotlin.test.fail fun assertIsDirectory(file: File) { @@ -32,47 +33,24 @@ fun assertCommonizationPerformed(result: CommonizerResult) { } @ExperimentalContracts -fun assertModulesAreEqual(expected: ModuleDescriptor, actual: ModuleDescriptor, designatorMessage: String) { - val visitor = ComparingDeclarationsVisitor(designatorMessage) - val context = visitor.Context(actual) +fun assertModulesAreEqual(expected: SerializedMetadata, actual: SerializedMetadata, target: CommonizerTarget) { + val expectedModule = with(expected) { KlibModuleMetadata.read(SerializedMetadataLibraryProvider(module, fragments, fragmentNames)) } + val actualModule = with(actual) { KlibModuleMetadata.read(SerializedMetadataLibraryProvider(module, fragments, fragmentNames)) } - expected.accept(visitor, context) -} - -fun assertValidModule(module: ModuleDescriptor) = validate( - object : ValidationVisitor() { - override fun validateScope(scopeOwner: DeclarationDescriptor?, scope: MemberScope, collector: DiagnosticCollector) = Unit - - override fun visitModuleDeclaration(descriptor: ModuleDescriptor, collector: DiagnosticCollector): Boolean { - assertValid(descriptor) - return super.visitModuleDeclaration(descriptor, collector) - } - - override fun visitClassDescriptor(descriptor: ClassDescriptor, collector: DiagnosticCollector): Boolean { - assertValid(descriptor) - return super.visitClassDescriptor(descriptor, collector) - } - - override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, collector: DiagnosticCollector): Boolean { - assertValid(descriptor) - return super.visitFunctionDescriptor(descriptor, collector) - } - - override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, collector: DiagnosticCollector): Boolean { - assertValid(descriptor) - return super.visitPropertyDescriptor(descriptor, collector) - } - - override fun visitConstructorDescriptor(constructorDescriptor: ConstructorDescriptor, collector: DiagnosticCollector): Boolean { - assertValid(constructorDescriptor) - return super.visitConstructorDescriptor(constructorDescriptor, collector) - } - }, - module -) - -@Suppress("NOTHING_TO_INLINE") -private inline fun assertValid(descriptor: DeclarationDescriptor) = when (descriptor) { - is ModuleDescriptor -> descriptor.assertValid() - else -> assertFalse(ErrorUtils.isError(descriptor), "$descriptor is error") + when (val result = MetadataDeclarationsComparator().compare(expectedModule, actualModule)) { + is Result.Success -> Unit + is Result.Failure -> { + val mismatches = result.mismatches.sortedBy { it::class.java.simpleName + "_" + it.kind } + val digitCount = mismatches.size.toString().length + + val failureMessage = buildString { + appendLine("${mismatches.size} mismatches found while comparing module ${expectedModule.name} and ${actualModule.name} for target ${target.prettyName}:") + mismatches.forEachIndexed { index, mismatch -> + appendLine((index + 1).toString().padStart(digitCount, ' ') + ". " + mismatch) + } + } + + fail(failureMessage) + } + } }