[Commonizer] ClassOrTypeAliasTypeCommonizer: Use typeDistance and type substitution

^KT-48288
This commit is contained in:
sebastian.sellmair
2021-09-15 13:55:58 +02:00
committed by Space
parent 4a6366ed9b
commit 31445d1471
6 changed files with 746 additions and 47 deletions
@@ -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>): 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<CirClassOrTypeAliasType, CirClassType>()?.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<CirClassOrTypeAliasType, CirClassType>()?.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<CirClassOrTypeAliasType>): CirEntityId? {
types.singleDistinctValueOrNull { it.classifierId }?.let { return it }
private fun substituteTypesIfNecessary(types: List<CirClassOrTypeAliasType>): List<CirClassOrTypeAliasType>? {
// 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<CirTypeAliasType>()?.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<CirTypeAliasType>()?.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<CirClassOrTypeAliasType>): 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<CirClassOrTypeAliasType>
): List<TypeSubstitutionCandidate> {
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 })
)
}
}
@@ -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<CirTypeDistance> {
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
}
@@ -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(
@@ -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)
}
@@ -158,13 +158,92 @@ class ParameterizedTypesCommonizationTest : AbstractInlineSourcesCommonizationTe
typealias T3<T, M> = Triple<T, String, M>
typealias T4<T, M> = Triple<T, String, M>
/* Note: No hard requirement whether T4 or T3 is chosen! */
expect fun x1(x: T4<Int, Long>)
expect fun x2(x: T4<Int, Long>)
expect fun x1(x: Triple<Int, String, Long>)
expect fun x2(x: Triple<Int, String, Long>)
""".trimIndent()
)
}
fun `test parameterized type alias - 3`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class Triple<T, R, M>
typealias T1<T, R, M> = Triple<T, R, M>
typealias T2<T, R, M> = Triple<T, R, M>
typealias T3<T, M> = Triple<T, String, M>
typealias T4<T, M> = Triple<T, String, M>
fun x1(x: T4<Int, Long>)
fun x2(x: T3<Int, Long>)
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class Triple<T, R, M>
typealias T1<T, R, M> = Triple<T, R, M>
typealias T2<T, R, M> = Triple<T, R, M>
typealias T3<T, M> = Triple<T, String, M>
typealias T4<T, M> = T3<T, M>
fun x1(x: T3<Int, Long>) // NOTE: T3 & T4 flipped
fun x2(x: T4<Int, Long>) // NOTE: T3 & T4 flipped
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class Triple<T, R, M>()
typealias T1<T, R, M> = Triple<T, R, M>
typealias T2<T, R, M> = Triple<T, R, M>
typealias T3<T, M> = Triple<T, String, M>
typealias T4<T, M> = Triple<T, String, M>
expect fun x1(x: Triple<Int, String, Long>)
expect fun x2(x: T3<Int, Long>)
""".trimIndent()
)
}
fun `test parameterized type alias - 4`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class Triple<T, R, M>
typealias A1 = String
typealias A2 = Long
typealias A3<T, R> = Triple<T, String, R>
fun x(x: A3<A1, A2>)
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class Triple<T, R, M>
typealias B1 = String
typealias B2 = Long
fun x(x: Triple<B1, String, B2>)
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class Triple<T, R, M>()
expect fun x(x: Triple<String, String, Long>)
""".trimIndent()
)
}
fun `test type alias from dependencies parameterized with library source type`() {
val result = commonize {
outputTarget("(a, b)")
@@ -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()
)
}