[Commonizer] ClassOrTypeAliasTypeCommonizer: Use typeDistance and type substitution
^KT-48288
This commit is contained in:
committed by
Space
parent
4a6366ed9b
commit
31445d1471
+133
-39
@@ -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
|
||||
}
|
||||
+78
-4
@@ -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)
|
||||
}
|
||||
+82
-3
@@ -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)")
|
||||
|
||||
+1
-1
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user