From 31445d14718ca1e34e5c8a59a977ce0dc0be0ea5 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Wed, 15 Sep 2021 13:55:58 +0200 Subject: [PATCH] [Commonizer] ClassOrTypeAliasTypeCommonizer: Use typeDistance and type substitution ^KT-48288 --- .../core/ClassOrTypeAliasTypeCommonizer.kt | 172 +++++++--- .../commonizer/mergedtree/CirTypeDistance.kt | 130 +++++++ .../CirCommonClassifierIdResolverTest.kt | 82 ++++- .../kotlin/commonizer/CirTypeDistanceTest.kt | 322 ++++++++++++++++++ .../ParameterizedTypesCommonizationTest.kt | 85 ++++- .../CirUnderscoredTypeAliasSubstitutorTest.kt | 2 +- 6 files changed, 746 insertions(+), 47 deletions(-) create mode 100644 native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirTypeDistance.kt create mode 100644 native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirTypeDistanceTest.kt diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt index 3c4ea773613..a485402459c 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt @@ -6,13 +6,13 @@ package org.jetbrains.kotlin.commonizer.core import org.jetbrains.kotlin.commonizer.cir.* -import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers -import org.jetbrains.kotlin.commonizer.mergedtree.CirProvided +import org.jetbrains.kotlin.commonizer.mergedtree.* import org.jetbrains.kotlin.commonizer.utils.isUnderKotlinNativeSyntheticPackages import org.jetbrains.kotlin.commonizer.utils.safeCastValues import org.jetbrains.kotlin.commonizer.utils.singleDistinctValueOrNull import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.utils.addToStdlib.ifTrue +import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class ClassOrTypeAliasTypeCommonizer( private val typeCommonizer: TypeCommonizer, @@ -23,16 +23,18 @@ internal class ClassOrTypeAliasTypeCommonizer( override fun invoke(values: List): CirClassOrTypeAliasType? { if (values.isEmpty()) return null - val expansions = values.map { it.expandedType() } val isMarkedNullable = isMarkedNullableCommonizer.commonize(expansions.map { it.isMarkedNullable }) ?: return null - val arguments = TypeArgumentListCommonizer(typeCommonizer).commonize(values.map { it.arguments }) ?: return null - val classifierId = selectClassifierId(values) - ?: typeCommonizer.options.enableOptimisticNumberTypeCommonization.ifTrue { - return OptimisticNumbersTypeCommonizer.commonize(expansions) - } ?: return null - val outerTypes = values.safeCastValues()?.map { it.outerType } + val types = substituteTypesIfNecessary(values) ?: typeCommonizer.options.enableOptimisticNumberTypeCommonization.ifTrue { + return OptimisticNumbersTypeCommonizer.commonize(expansions) + } ?: return null + + val classifierId = types.singleDistinctValueOrNull { it.classifierId } ?: return null + + val arguments = TypeArgumentListCommonizer(typeCommonizer).commonize(types.map { it.arguments }) ?: return null + + val outerTypes = types.safeCastValues()?.map { it.outerType } val outerType = when { outerTypes == null -> null outerTypes.all { it == null } -> null @@ -94,10 +96,98 @@ internal class ClassOrTypeAliasTypeCommonizer( } } - @Suppress("KotlinConstantConditions") // Improved readability - private fun selectClassifierId(types: List): CirEntityId? { - types.singleDistinctValueOrNull { it.classifierId }?.let { return it } + private fun substituteTypesIfNecessary(types: List): List? { + // Not necessary + if (types.singleDistinctValueOrNull { it.classifierId } != null) return types + val classifierId = selectSubstitutionClassifierId(types) ?: return null + return types.mapIndexed { targetIndex, type -> substituteIfNecessary(targetIndex, type, classifierId) ?: return null } + } + private fun substituteIfNecessary( + targetIndex: Int, sourceType: CirClassOrTypeAliasType, destinationClassifierId: CirEntityId + ): CirClassOrTypeAliasType? { + if (sourceType.classifierId == destinationClassifierId) { + return sourceType + } + + if (sourceType is CirTypeAliasType) { + forwardSubstitute(sourceType, destinationClassifierId)?.let { return it } + } + + val resolvedClassifierFromDependencies = classifiers.commonDependencies.classifier(destinationClassifierId) + ?: classifiers.targetDependencies[targetIndex].classifier(destinationClassifierId) + + if (resolvedClassifierFromDependencies != null && resolvedClassifierFromDependencies is CirProvided.TypeAlias) { + return backwardsSubstitute(targetIndex, sourceType, destinationClassifierId, resolvedClassifierFromDependencies) + } + + val resolvedClassifier = classifiers.classifierIndices[targetIndex].findClassifier(destinationClassifierId) + if (resolvedClassifier != null && resolvedClassifier is CirTypeAlias) { + return backwardsSubstitute(sourceType, destinationClassifierId, resolvedClassifier) + } + + return null + } + + private fun forwardSubstitute( + sourceType: CirTypeAliasType, + destinationClassifierId: CirEntityId, + ): CirClassOrTypeAliasType? { + return generateSequence(sourceType.underlyingType) { type -> type.safeAs()?.underlyingType } + .firstOrNull { underlyingType -> underlyingType.classifierId == destinationClassifierId } + ?.withParentArguments(sourceType.arguments, sourceType.isMarkedNullable) + } + + private fun backwardsSubstitute( + sourceType: CirClassOrTypeAliasType, + destinationTypeAliasId: CirEntityId, + destinationTypeAlias: CirTypeAlias + ): CirTypeAliasType? { + // Limitation: No backwards substitution with arguments + if (sourceType.arguments.isNotEmpty()) return null + if (destinationTypeAlias.typeParameters.isNotEmpty()) return null + + // Check if any 'backward' type has arguments + generateSequence(destinationTypeAlias.underlyingType) { type -> type.safeAs()?.underlyingType } + .takeWhile { type -> type.classifierId != sourceType.classifierId } + .forEach { type -> if (type.arguments.isNotEmpty()) return null } // limitation! + + return CirTypeAliasType.createInterned( + destinationTypeAliasId, + underlyingType = destinationTypeAlias.underlyingType, + arguments = emptyList(), + isMarkedNullable = sourceType.isMarkedNullable + ) + } + + private fun backwardsSubstitute( + targetIndex: Int, + sourceType: CirClassOrTypeAliasType, + destinationTypeAliasId: CirEntityId, + destinationTypeAlias: CirProvided.TypeAlias + ): CirClassOrTypeAliasType? { + // Limitation: No backwards substitution with arguments + if (sourceType.arguments.isNotEmpty()) return null + if (destinationTypeAlias.typeParameters.isNotEmpty()) return null + + val providedClassifiers = CirProvidedClassifiers.of(classifiers.commonDependencies, classifiers.targetDependencies[targetIndex]) + + generateSequence(destinationTypeAlias.underlyingType) next@{ type -> + val typeAliasType = type as? CirProvided.TypeAliasType ?: return@next null + val typeAlias = providedClassifiers.classifier(typeAliasType.classifierId) as? CirProvided.TypeAlias ?: return@next null + typeAlias.underlyingType + }.takeWhile { type -> type.classifierId != sourceType.classifierId } + .forEach { type -> if (type.arguments.isNotEmpty()) return null } + + return CirTypeAliasType.createInterned( + destinationTypeAliasId, + underlyingType = providedClassifiers.toCirClassOrTypeAliasTypeOrNull(destinationTypeAlias.underlyingType) ?: return null, + arguments = emptyList(), + isMarkedNullable = sourceType.isMarkedNullable + ) + } + + private fun selectSubstitutionClassifierId(types: List): CirEntityId? { val forwardSubstitutionAllowed = typeCommonizer.options.enableForwardTypeAliasSubstitution val backwardsSubstitutionAllowed = typeCommonizer.options.enableBackwardsTypeAliasSubstitution @@ -110,36 +200,40 @@ internal class ClassOrTypeAliasTypeCommonizer( classifiers.commonClassifierIdResolver.findCommonId(it.classifierId) } ?: return null - val candidates = when { - forwardSubstitutionAllowed && backwardsSubstitutionAllowed -> commonId.aliases - - /* Only forward substitutions allowed -> Only substitute with any underlying type */ - forwardSubstitutionAllowed -> commonId.aliases.filter { candidate -> - types.all { type -> candidate == type.classifierId || type.isAnyUnderlyingClassifier(candidate) } + val typeSubstitutionCandidates = resolveTypeSubstitutionCandidates(classifiers, commonId, types) + .filter { typeSubstitutionCandidate -> + if (typeSubstitutionCandidate.typeDistance.isNotReachable) return@filter false + if (typeSubstitutionCandidate.typeDistance.isNegative && !backwardsSubstitutionAllowed) return@filter false + if (typeSubstitutionCandidate.typeDistance.isPositive && !forwardSubstitutionAllowed) return@filter false + true } - /* Only backward substitutions allowed -> Only substitute with any typealias pointing to this types */ - backwardsSubstitutionAllowed -> commonId.aliases.filter { candidate -> - types.none { type -> type.isAnyUnderlyingClassifier(candidate) } - } + if (typeSubstitutionCandidates.isEmpty()) return null + if (typeSubstitutionCandidates.size == 1) return typeSubstitutionCandidates.first().id - else -> error("Failed to select classifierId: Unexpected when branch") - } + val forwardSubstitutionCandidates = typeSubstitutionCandidates.filter { it.typeDistance.isPositive } + val backwardsSubstitutionCandidates = typeSubstitutionCandidates.filter { it.typeDistance.isNegative } - if (candidates.isEmpty()) return null - if (candidates.size == 1) return candidates.first() - - return candidates.maxByOrNull { candidate -> types.count { it.classifierId == candidate } }!! - } - - private fun CirClassOrTypeAliasType.isAnyUnderlyingClassifier(classifierId: CirEntityId): Boolean { - return when (this) { - is CirClassType -> false - is CirTypeAliasType -> this.isAnyUnderlyingClassifier(classifierId) - } - } - - private fun CirTypeAliasType.isAnyUnderlyingClassifier(classifierId: CirEntityId): Boolean { - return underlyingType.classifierId == classifierId || underlyingType.isAnyUnderlyingClassifier(classifierId) + return forwardSubstitutionCandidates.minByOrNull { it.typeDistance }?.id + ?: backwardsSubstitutionCandidates.maxByOrNull { it.typeDistance }?.id + } +} + +private class TypeSubstitutionCandidate( + val id: CirEntityId, + val typeDistance: CirTypeDistance +) + +private fun resolveTypeSubstitutionCandidates( + classifiers: CirKnownClassifiers, commonId: CirCommonClassifierId, types: List +): List { + return commonId.aliases.mapNotNull mapCandidateId@{ candidateId -> + val typeDistances = types.mapIndexed { targetIndex, type -> typeDistance(classifiers, targetIndex, type, candidateId) } + if (typeDistances.any { it.isNotReachable }) return@mapCandidateId null + TypeSubstitutionCandidate( + id = candidateId, + // TODO NOW: Any negative should outperform any positive substitution!, so negative will be chosen here as 'worst' + typeDistance = checkNotNull(typeDistances.maxByOrNull { distance -> distance.absoluteValue }) + ) } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirTypeDistance.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirTypeDistance.kt new file mode 100644 index 00000000000..bb310560960 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirTypeDistance.kt @@ -0,0 +1,130 @@ +/* + * 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. + */ + +package org.jetbrains.kotlin.commonizer.mergedtree + +import org.jetbrains.kotlin.commonizer.CommonizerTarget +import org.jetbrains.kotlin.commonizer.cir.* +import org.jetbrains.kotlin.commonizer.mergedtree.CirTypeDistance.Companion.unreachable +import kotlin.math.absoluteValue + +@JvmInline +value class CirTypeDistance(private val value: Int) : Comparable { + + val isReachable: Boolean get() = value != Int.MAX_VALUE + + val isNotReachable: Boolean get() = !isReachable + + val isNegative: Boolean get() = isReachable && value < 0 + + val isPositive: Boolean get() = isReachable && value > 0 + + val isZero: Boolean get() = value == 0 + + val absoluteValue get() = CirTypeDistance(value.absoluteValue) + + operator fun plus(value: Int) = if (isReachable) CirTypeDistance(this.value + value) else this + + operator fun plus(value: CirTypeDistance) = when { + this.isNotReachable -> this + value.isNotReachable -> value + else -> CirTypeDistance(this.value + value.value) + } + + operator fun minus(value: Int) = if (isReachable) CirTypeDistance(this.value - value) else this + + operator fun minus(value: CirTypeDistance) = when { + this.isNotReachable -> this + value.isNotReachable -> value + else -> CirTypeDistance(this.value - value.value) + } + + operator fun inc() = this + 1 + + operator fun dec() = this - 1 + + override fun compareTo(other: CirTypeDistance): Int { + return value.compareTo(other.value) + } + + override fun toString(): String { + return if (isNotReachable) "CirTypeDistance([unreachable])" + else "CirTypeDistance($value)" + } + + companion object { + fun unreachable(): CirTypeDistance = CirTypeDistance(Int.MAX_VALUE) + } +} + +internal fun typeDistance( + classifiers: CirKnownClassifiers, + target: CommonizerTarget, + from: CirClassOrTypeAliasType, + to: CirEntityId +): CirTypeDistance { + return typeDistance(classifiers, classifiers.classifierIndices.indexOf(target), from = from, to = to) +} + +internal fun typeDistance( + classifiers: CirKnownClassifiers, + targetIndex: Int, + from: CirClassOrTypeAliasType, + to: CirEntityId +): CirTypeDistance { + if (from.classifierId == to) return CirTypeDistance(0) + val forwardDistance = forwardTypeDistance(from, to) + if (forwardDistance.isReachable) return forwardDistance + + val backwardsDistance = backwardsTypeDistance(classifiers, targetIndex, from.classifierId, to) + if (backwardsDistance.isReachable) return backwardsDistance + + val fromExpansion = from.expandedType() + val distanceToExpansion = typeDistance(classifiers, targetIndex, from, fromExpansion.classifierId) + return backwardsTypeDistance(classifiers, targetIndex, from.expandedType().classifierId, to) - distanceToExpansion +} + +private fun forwardTypeDistance(from: CirClassOrTypeAliasType, to: CirEntityId): CirTypeDistance { + if (from !is CirTypeAliasType) return unreachable() + + var iteration = 0 + var underlyingType: CirClassOrTypeAliasType? = from.underlyingType + + while (true) { + iteration++ + val capturedUnderlyingType = underlyingType ?: return unreachable() + if (capturedUnderlyingType.classifierId == to) return CirTypeDistance(iteration) + underlyingType = (capturedUnderlyingType as? CirTypeAliasType)?.underlyingType + } +} + +private fun backwardsTypeDistance( + classifiers: CirKnownClassifiers, targetIndex: Int, from: CirEntityId, to: CirEntityId +): CirTypeDistance { + val resolvedDependencyClassifier = + classifiers.commonDependencies.classifier(to) ?: classifiers.targetDependencies[targetIndex].classifier(to) + if (resolvedDependencyClassifier != null) { + return classifiers.backwardsTypeDistance(targetIndex, from, resolvedDependencyClassifier) + } + + val resolvedClassifier = classifiers.classifierIndices[targetIndex].findClassifier(to) ?: return unreachable() + return classifiers.backwardsTypeDistance(targetIndex, from, resolvedClassifier) +} + +private fun CirKnownClassifiers.backwardsTypeDistance( + targetIndex: Int, from: CirEntityId, to: CirProvided.Classifier +): CirTypeDistance { + if (to !is CirProvided.TypeAlias) return unreachable() + if (to.underlyingType.classifierId == from) return CirTypeDistance(-1) + return backwardsTypeDistance(this, targetIndex, from, to.underlyingType.classifierId) - 1 +} + +private fun CirKnownClassifiers.backwardsTypeDistance( + targetIndex: Int, from: CirEntityId, to: CirClassifier +): CirTypeDistance { + if (to !is CirTypeAlias) return unreachable() + if (to.underlyingType.classifierId == from) return CirTypeDistance(-1) + return backwardsTypeDistance(this, targetIndex, from, to.underlyingType.classifierId) - 1 +} \ No newline at end of file diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt index 378f4a58e5a..c9ee649cf44 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt @@ -154,8 +154,9 @@ class CirCommonClassifierIdResolverTest : AbstractInlineSourcesCommonizationTest /* Rather esoteric case! - Platform A: A -> B -> C D -> E - Platform B: B -> C - D + Platform A: A -> B -> C + D -> E + Platform B: B -> C -> D Expected: B, C, D */ @@ -180,10 +181,10 @@ class CirCommonClassifierIdResolverTest : AbstractInlineSourcesCommonizationTest ) val resolver = createCommonClassifierIdResolver(rootB, rootA) + assertEquals(setOf("B", "C", "D"), resolver.findCommonId("A")) assertEquals(setOf("B", "C", "D"), resolver.findCommonId("B")) assertEquals(setOf("B", "C", "D"), resolver.findCommonId("C")) assertEquals(setOf("B", "C", "D"), resolver.findCommonId("D")) - assertEquals(setOf("B", "C", "D"), resolver.findCommonId("A")) assertEquals(setOf("B", "C", "D"), resolver.findCommonId("E")) } @@ -232,7 +233,7 @@ class CirCommonClassifierIdResolverTest : AbstractInlineSourcesCommonizationTest source( """ class D_X - class D_TA = D_X + typealias D_TA = D_X """.trimIndent() ) } @@ -268,6 +269,79 @@ class CirCommonClassifierIdResolverTest : AbstractInlineSourcesCommonizationTest assertEquals(setOf("D_X", "D_TA", "A", "C"), resolver.findCommonId("B")) assertEquals(setOf("D_X", "D_TA", "A", "C"), resolver.findCommonId("C")) } + + /* + Platform A: A1 -> A -> B -> C + Platform B: A2 -> A -> D -> E + + Expected: A + */ + fun `test sample 8`() { + val resolver = createCommonClassifierIdResolver( + createCirTreeRootFromSourceCode( + """ + class C + typealias B = C + typealias A = B + typealias A1 = A + """.trimIndent() + ), + createCirTreeRootFromSourceCode( + """ + class E + typealias D = E + typealias A = D + typealias A2 = A + """.trimIndent() + ) + ) + + assertEquals(setOf("A"), resolver.findCommonId("A")) + assertEquals(setOf("A"), resolver.findCommonId("A1")) + assertEquals(setOf("A"), resolver.findCommonId("A2")) + assertEquals(setOf("A"), resolver.findCommonId("B")) + assertEquals(setOf("A"), resolver.findCommonId("C")) + assertEquals(setOf("A"), resolver.findCommonId("E")) + assertEquals(setOf("A"), resolver.findCommonId("D")) + } + + /* + Platform A: A -> B -> C + Platform B: A -> D -> E + F -> G -> C + + Expected: A, C + */ + fun `test sample 9`() { + val resolver = createCommonClassifierIdResolver( + createCirTreeRootFromSourceCode( + """ + class C + typealias B = C + typealias A = B + """.trimIndent() + ), + createCirTreeRootFromSourceCode( + """ + class E + typealias D = E + typealias A = D + + class C + typealias G = C + typealias F = G + """.trimIndent() + ) + ) + + assertEquals(setOf("A", "C"), resolver.findCommonId("A")) + assertEquals(setOf("A", "C"), resolver.findCommonId("B")) + assertEquals(setOf("A", "C"), resolver.findCommonId("C")) + assertEquals(setOf("A", "C"), resolver.findCommonId("D")) + assertEquals(setOf("A", "C"), resolver.findCommonId("E")) + assertEquals(setOf("A", "C"), resolver.findCommonId("F")) + assertEquals(setOf("A", "C"), resolver.findCommonId("G")) + } } private fun createCommonClassifierIdResolver( diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirTypeDistanceTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirTypeDistanceTest.kt new file mode 100644 index 00000000000..31416d1179c --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirTypeDistanceTest.kt @@ -0,0 +1,322 @@ +/* + * 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. + */ + +package org.jetbrains.kotlin.commonizer + +import org.jetbrains.kotlin.commonizer.cir.CirEntityId +import org.jetbrains.kotlin.commonizer.mergedtree.* +import org.jetbrains.kotlin.commonizer.utils.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class CirTypeDistanceTest : KtInlineSourceCommonizerTestCase() { + + private val target = LeafCommonizerTarget("a") + + fun `test sample 0`() { + + val root = createCirTreeRootFromSourceCode( + """ + class A + typealias B = A + typealias C = B + typealias D = C + """.trimIndent() + ) + + val classifiers = CirKnownClassifiers( + classifierIndices = TargetDependent(target to CirClassifierIndex(root)), + targetDependencies = TargetDependent(target to CirProvidedClassifiers.EMPTY), + commonizedNodes = CirCommonizedClassifierNodes.default(), + commonDependencies = CirProvidedClassifiers.EMPTY + ) + + val idOfA = CirEntityId.create("A") + val idOfB = CirEntityId.create("B") + val idOfC = CirEntityId.create("C") + val idOfD = CirEntityId.create("D") + + val typeA = mockClassType("A") + val typeB = mockTAType("B") { typeA } + val typeC = mockTAType("C") { typeB } + val typeD = mockTAType("D") { typeC } + + + assertEquals( + CirTypeDistance(0), + typeDistance(classifiers, target, typeA, idOfA) + ) + + assertEquals( + CirTypeDistance(1), + typeDistance(classifiers, target, typeB, idOfA) + ) + + assertEquals( + CirTypeDistance(-1), + typeDistance(classifiers, target, typeA, idOfB) + ) + + assertEquals( + CirTypeDistance(2), + typeDistance(classifiers, target, typeC, idOfA) + ) + + assertEquals( + CirTypeDistance(-2), + typeDistance(classifiers, target, typeA, idOfC) + ) + + assertEquals( + CirTypeDistance(3), + typeDistance(classifiers, target, typeD, idOfA) + ) + + assertEquals( + CirTypeDistance(-3), + typeDistance(classifiers, target, typeA, idOfD) + ) + + assertEquals( + CirTypeDistance(2), + typeDistance(classifiers, target, typeD, idOfB) + ) + + assertEquals( + CirTypeDistance(-2), + typeDistance(classifiers, target, typeB, idOfD) + ) + } + + fun `test sample 1 - unreachable types`() { + val root = createCirTreeRootFromSourceCode( + """ + class X + class Y + typealias A = X + typealias B = Y + typealias C = B + """.trimIndent() + ) + + val classifiers = CirKnownClassifiers( + classifierIndices = TargetDependent(target to CirClassifierIndex(root)), + targetDependencies = TargetDependent(target to CirProvidedClassifiers.EMPTY), + commonizedNodes = CirCommonizedClassifierNodes.default(), + commonDependencies = CirProvidedClassifiers.EMPTY + ) + + val idOfX = CirEntityId.create("X") + val idOfY = CirEntityId.create("Y") + val idOfA = CirEntityId.create("A") + val idOfB = CirEntityId.create("B") + val idOfC = CirEntityId.create("C") + + val typeX = mockClassType("X") + val typeY = mockClassType("Y") + val typeA = mockTAType("A") { typeX } + val typeB = mockTAType("B") { typeY } + val typeC = mockTAType("C") { typeB } + + assertUnreachable(typeDistance(classifiers, target, typeX, idOfY)) + assertUnreachable(typeDistance(classifiers, target, typeY, idOfX)) + assertUnreachable(typeDistance(classifiers, target, typeA, idOfY)) + assertUnreachable(typeDistance(classifiers, target, typeY, idOfA)) + assertUnreachable(typeDistance(classifiers, target, typeB, idOfA)) + assertUnreachable(typeDistance(classifiers, target, typeA, idOfB)) + assertUnreachable(typeDistance(classifiers, target, typeC, idOfA)) + assertUnreachable(typeDistance(classifiers, target, typeA, idOfC)) + assertUnreachable(typeDistance(classifiers, target, typeC, idOfX)) + } + + fun `test sample 2 - indirect backwards reachable types`() { + val root = createCirTreeRootFromSourceCode( + """ + class X + typealias A = X + typealias B = X + typealias C = B + """.trimIndent() + ) + + val idOfX = CirEntityId.create("X") + val idOfA = CirEntityId.create("A") + val idOfB = CirEntityId.create("B") + val idOfC = CirEntityId.create("C") + + val typeX = mockClassType("X") + val typeA = mockTAType("A") { typeX } + val typeB = mockTAType("B") { typeX } + val typeC = mockTAType("C") { typeB } + + val classifiers = CirKnownClassifiers( + classifierIndices = TargetDependent(target to CirClassifierIndex(root)), + targetDependencies = TargetDependent(target to CirProvidedClassifiers.EMPTY), + commonizedNodes = CirCommonizedClassifierNodes.default(), + commonDependencies = CirProvidedClassifiers.EMPTY + ) + + assertEquals( + CirTypeDistance(1), + typeDistance(classifiers, target, typeA, idOfX) + ) + + assertEquals( + CirTypeDistance(-2), + typeDistance(classifiers, target, typeB, idOfA) + ) + + assertEquals( + CirTypeDistance(-3), + typeDistance(classifiers, target, typeC, idOfA) + ) + + assertEquals( + CirTypeDistance(-2), + typeDistance(classifiers, target, typeA, idOfB) + ) + + assertEquals( + CirTypeDistance(-3), + typeDistance(classifiers, target, typeA, idOfC) + ) + } + + /** + * Type Alias Chains: + * E -> Z + * B -> Y + * D -> C -> A -> X + */ + fun `test sample 3 - with dependencies`() { + fun InlineSourceBuilder.ModuleBuilder.commonDependencies() { + source( + """ + class X + typealias A = X + """.trimIndent(), "commonDependencies.kt" + ) + } + + fun InlineSourceBuilder.ModuleBuilder.targetDependencies() { + dependency { commonDependencies() } + source( + """ + class Y + typealias B = Y + typealias C = A + """.trimIndent(), "targetDependencies.kt" + ) + } + + val root = createCirTreeRoot { + dependency { commonDependencies() } + dependency { targetDependencies() } + source( + """ + class Z + typealias D = C + typealias E = Z + """.trimIndent() + ) + } + + val classifiers = CirKnownClassifiers( + classifierIndices = TargetDependent(target to CirClassifierIndex(root)), + commonDependencies = createCirProvidedClassifiers { commonDependencies() }, + targetDependencies = TargetDependent(target to createCirProvidedClassifiers { targetDependencies() }), + commonizedNodes = CirCommonizedClassifierNodes.default() + ) + + val idOfX = CirEntityId.create("X") + val idOfY = CirEntityId.create("Y") + val idOfZ = CirEntityId.create("Z") + val idOfA = CirEntityId.create("A") + val idOfB = CirEntityId.create("B") + val idOfC = CirEntityId.create("C") + val idOfD = CirEntityId.create("D") + val idOfE = CirEntityId.create("E") + + val typeX = mockClassType("X") + val typeY = mockClassType("Y") + val typeZ = mockClassType("Z") + val typeA = mockTAType("A") { typeX } + val typeB = mockTAType("B") { typeY } + val typeC = mockTAType("C") { typeA } + val typeD = mockTAType("D") { typeC } + val typeE = mockTAType("E") { typeZ } + + assertEquals( + CirTypeDistance(1), typeDistance(classifiers, target, typeA, idOfX) + ) + + assertEquals( + CirTypeDistance(-1), typeDistance(classifiers, target, typeX, idOfA) + ) + + assertEquals( + CirTypeDistance(1), typeDistance(classifiers, target, typeB, idOfY) + ) + + assertEquals( + CirTypeDistance(-1), typeDistance(classifiers, target, typeY, idOfB) + ) + + assertEquals( + CirTypeDistance(2), typeDistance(classifiers, target, typeC, idOfX) + ) + + assertEquals( + CirTypeDistance(-2), typeDistance(classifiers, target, typeX, idOfC) + ) + + assertEquals( + CirTypeDistance(3), typeDistance(classifiers, target, typeD, idOfX) + ) + + assertEquals( + CirTypeDistance(-3), typeDistance(classifiers, target, typeX, idOfD) + ) + + assertEquals( + CirTypeDistance(2), typeDistance(classifiers, target, typeD, idOfA) + ) + + assertEquals( + CirTypeDistance(-2), typeDistance(classifiers, target, typeA, idOfD) + ) + + assertEquals( + CirTypeDistance(1), typeDistance(classifiers, target, typeE, idOfZ) + ) + + assertEquals( + CirTypeDistance(-1), typeDistance(classifiers, target, typeZ, idOfE) + ) + } + + fun `test unreachable distance`() { + assertUnreachable(CirTypeDistance.unreachable() + CirTypeDistance.unreachable()) + assertUnreachable(CirTypeDistance.unreachable() + 1) + assertUnreachable(CirTypeDistance.unreachable().inc()) + assertUnreachable(CirTypeDistance.unreachable() + CirTypeDistance(1)) + assertUnreachable(CirTypeDistance(1) + CirTypeDistance.unreachable()) + assertUnreachable(CirTypeDistance.unreachable() - 1) + assertUnreachable(CirTypeDistance.unreachable().dec()) + assertUnreachable(CirTypeDistance.unreachable() - CirTypeDistance(1)) + assertUnreachable(CirTypeDistance(1) - CirTypeDistance.unreachable()) + assertUnreachable(CirTypeDistance.unreachable().absoluteValue) + } +} + +private fun assertUnreachable(typeDistance: CirTypeDistance) { + assertEquals(CirTypeDistance.unreachable(), typeDistance) + assertTrue(typeDistance.isNotReachable) + assertFalse(typeDistance.isReachable) + assertFalse(typeDistance.isPositive) + assertFalse(typeDistance.isNegative) + assertFalse(typeDistance.isZero) +} \ No newline at end of file diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ParameterizedTypesCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ParameterizedTypesCommonizationTest.kt index e23d406df4a..05210cbcfa6 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ParameterizedTypesCommonizationTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ParameterizedTypesCommonizationTest.kt @@ -158,13 +158,92 @@ class ParameterizedTypesCommonizationTest : AbstractInlineSourcesCommonizationTe typealias T3 = Triple typealias T4 = Triple - /* Note: No hard requirement whether T4 or T3 is chosen! */ - expect fun x1(x: T4) - expect fun x2(x: T4) + expect fun x1(x: Triple) + expect fun x2(x: Triple) """.trimIndent() ) } + fun `test parameterized type alias - 3`() { + val result = commonize { + outputTarget("(a, b)") + + simpleSingleSourceTarget( + "a", """ + class Triple + typealias T1 = Triple + typealias T2 = Triple + typealias T3 = Triple + typealias T4 = Triple + + fun x1(x: T4) + fun x2(x: T3) + """.trimIndent() + ) + + simpleSingleSourceTarget( + "b", """ + class Triple + typealias T1 = Triple + typealias T2 = Triple + typealias T3 = Triple + typealias T4 = T3 + + fun x1(x: T3) // NOTE: T3 & T4 flipped + fun x2(x: T4) // NOTE: T3 & T4 flipped + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + expect class Triple() + typealias T1 = Triple + typealias T2 = Triple + typealias T3 = Triple + typealias T4 = Triple + + expect fun x1(x: Triple) + expect fun x2(x: T3) + """.trimIndent() + ) + } + + fun `test parameterized type alias - 4`() { + val result = commonize { + outputTarget("(a, b)") + + simpleSingleSourceTarget( + "a", """ + class Triple + typealias A1 = String + typealias A2 = Long + typealias A3 = Triple + + fun x(x: A3) + """.trimIndent() + ) + + simpleSingleSourceTarget( + "b", """ + class Triple + typealias B1 = String + typealias B2 = Long + + fun x(x: Triple) + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + expect class Triple() + expect fun x(x: Triple) + """.trimIndent() + ) + } + + fun `test type alias from dependencies parameterized with library source type`() { val result = commonize { outputTarget("(a, b)") diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/CirUnderscoredTypeAliasSubstitutorTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/CirUnderscoredTypeAliasSubstitutorTest.kt index 70e1f1ad56d..5cd5b5de6a2 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/CirUnderscoredTypeAliasSubstitutorTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/CirUnderscoredTypeAliasSubstitutorTest.kt @@ -62,7 +62,7 @@ class UnderscoredTypeAliasTypeSubstitutonTest : AbstractInlineSourcesCommonizati typealias X = Int typealias __X = X /* No hard requirement. Picking X over __X seems equally fine here */ - expect fun x(x: __X) + expect fun x(x: X) """.trimIndent() ) }