[Commonizer] Implement CirCommonClassifierId & CirCommonClassifierIdResolver

This common representation of a classifier id can later be used
to render typealias invariant approximation keys.

^KT-48288
This commit is contained in:
sebastian.sellmair
2021-09-08 12:52:21 +02:00
committed by Space
parent a0d36d7e29
commit 763ddcddf4
7 changed files with 314 additions and 2 deletions
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.commonizer.cir
interface CirClassifier :
sealed interface CirClassifier :
CirDeclaration,
CirHasAnnotations,
CirHasName,
@@ -28,6 +28,30 @@ interface CirClassifierIndex {
fun findTypeAliasesWithUnderlyingType(underlyingClassifier: CirEntityId): List<CirTreeTypeAlias>
}
/**
* Resolves all typealias that will either declare the given [underlyingClassifier] as their underlying type directly, *or transitively*.
* The returned List will be ordered BFS:
* Elements at the beginning of the returned List will have a 'shorter path' to the underlying classifier
*/
internal fun CirClassifierIndex.findAllTypeAliasesWithUnderlyingType(underlyingClassifier: CirEntityId): List<CirTreeTypeAlias> {
/* Fast Path (no aliases) */
val firstAliases = findTypeAliasesWithUnderlyingType(underlyingClassifier)
if (firstAliases.isEmpty()) return emptyList()
val resolved = ArrayList<CirTreeTypeAlias>(firstAliases)
val enqueued = ArrayDeque<CirEntityId>(firstAliases.size * 2)
firstAliases.forEach { alias -> enqueued.add(alias.id) }
while (enqueued.isNotEmpty()) {
val next = enqueued.removeFirst()
val aliases = findTypeAliasesWithUnderlyingType(next)
resolved.addAll(aliases)
aliases.forEach { alias -> enqueued.add(alias.id) }
}
return resolved
}
internal fun CirClassifierIndex.findClass(id: CirEntityId): CirClass? = findClassifier(id) as? CirClass
internal fun CirClassifierIndex.findTypeAlias(id: CirEntityId): CirTypeAlias? = findClassifier(id) as? CirTypeAlias
@@ -0,0 +1,19 @@
/*
* 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.cir.CirEntityId
@JvmInline
internal value class CirCommonClassifierId(val aliases: Set<CirEntityId>) {
override fun toString(): String {
return aliases.joinToString(prefix = "(", postfix = ")")
}
init {
require(aliases.isNotEmpty())
}
}
@@ -0,0 +1,62 @@
/*
* 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.
*/
@file:Suppress("MoveVariableDeclarationIntoWhen")
package org.jetbrains.kotlin.commonizer.mergedtree
import org.jetbrains.kotlin.commonizer.TargetDependent
import org.jetbrains.kotlin.commonizer.cir.CirClass
import org.jetbrains.kotlin.commonizer.cir.CirEntityId
import org.jetbrains.kotlin.commonizer.cir.CirTypeAlias
import org.jetbrains.kotlin.commonizer.forEachWithTarget
import org.jetbrains.kotlin.commonizer.mapValue
internal class CirCommonClassifierIdResolver(private val classifierIndices: TargetDependent<CirClassifierIndex>) {
fun findCommonId(id: CirEntityId): CirCommonClassifierId? {
val results = mutableSetOf<CirEntityId>()
/* Set of every classifier id that once was enqueued already */
val enqueued = mutableSetOf<CirEntityId>()
/* Actual, current queue of classifiers to resolve */
val queue = ArrayDeque<CirEntityId>()
enqueued.add(id)
queue.add(id)
while (queue.isNotEmpty()) {
val nextClassifierId = queue.removeFirst()
val foundClassifiers = classifierIndices.mapValue { index -> index.findClassifier(nextClassifierId) }
/* Classifier is available for all targets */
if (foundClassifiers.all { it != null }) {
results.add(nextClassifierId)
}
foundClassifiers.forEachWithTarget forEach@{ target, classifier ->
if (classifier == null) return@forEach
val classifierExpansionId = when (classifier) {
is CirClass -> nextClassifierId
is CirTypeAlias -> classifier.expandedType.classifierId
}
if (enqueued.add(classifierExpansionId)) {
queue.add(classifierExpansionId)
}
val aliases = classifierIndices[target].findAllTypeAliasesWithUnderlyingType(classifierExpansionId)
aliases.forEach { alias ->
if (enqueued.add(alias.id)) {
queue.add(alias.id)
}
}
}
}
return if(results.isNotEmpty()) CirCommonClassifierId(results) else null
}
}
@@ -205,7 +205,6 @@ private class CirTreeSerializationVisitor(
val fullClassName = typeAliasContext.currentPath.toString()
cirClassifier.serializeClass(typeAliasContext, fullClassName, emptyList(), emptyList(), emptyList(), emptyList())
}
else -> error("Unexpected CIR classifier: ${cirClassifier::class.java}, $cirClassifier")
}
}
@@ -0,0 +1,197 @@
/*
* 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.CirClassifierIndex
import org.jetbrains.kotlin.commonizer.mergedtree.CirCommonClassifierIdResolver
import org.jetbrains.kotlin.commonizer.tree.CirTreeRoot
import org.jetbrains.kotlin.commonizer.utils.createCirTreeRootFromSourceCode
class CirCommonClassifierIdResolverTest : AbstractInlineSourcesCommonizationTest() {
/*
Platform A: X -> A
Platform B: X -> B
Expected: X
*/
fun `test sample 0`() {
val resolver = createCommonClassifierIdResolver(
createCirTreeRootFromSourceCode(
"""
class A
typealias X = A
""".trimIndent()
),
createCirTreeRootFromSourceCode(
"""
class B
typealias X = B
""".trimIndent()
)
)
assertEquals(setOf("X"), resolver.findCommonId("A"))
assertEquals(setOf("X"), resolver.findCommonId("B"))
assertEquals(setOf("X"), resolver.findCommonId("X"))
}
/*
Platform A: A -> B -> C
Platform B: C -> D -> E
Expected: C
*/
fun `test sample 1`() {
val resolver = createCommonClassifierIdResolver(
createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias A = B
""".trimIndent()
),
createCirTreeRootFromSourceCode(
"""
class E
typealias D = E
typealias C = D
""".trimIndent()
)
)
assertEquals(setOf("C"), resolver.findCommonId("A"))
assertEquals(setOf("C"), resolver.findCommonId("B"))
assertEquals(setOf("C"), resolver.findCommonId("C"))
assertEquals(setOf("C"), resolver.findCommonId("D"))
assertEquals(setOf("C"), resolver.findCommonId("E"))
}
/*
Platform A: A -> B -> C
Platform B: A -> X -> B -> C
Expected: A, B, C
*/
fun `test sample 2`() {
val resolver = createCommonClassifierIdResolver(
createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias A = B
""".trimIndent()
),
createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias X = B
typealias A = X
""".trimIndent()
)
)
assertEquals(setOf("A", "B", "C"), resolver.findCommonId("A"))
assertEquals(setOf("A", "B", "C"), resolver.findCommonId("B"))
assertEquals(setOf("A", "B", "C"), resolver.findCommonId("C"))
assertEquals(setOf("A", "B", "C"), resolver.findCommonId("X"))
}
fun `test sample 3`() {
val resolver = createCommonClassifierIdResolver(
createCirTreeRootFromSourceCode("class A"),
createCirTreeRootFromSourceCode("class B")
)
assertEquals(emptySet<String>(), resolver.findCommonId("A"))
assertEquals(emptySet<String>(), resolver.findCommonId("B"))
}
/*
Platform A: A -> B -> C
Platform B: A -> B -> C
Platform C: A -> B
Expected: A, B
*/
fun `test sample 4`() {
val resolver = createCommonClassifierIdResolver(
createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias A = B
""".trimIndent()
),
createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias A = B
""".trimIndent()
),
createCirTreeRootFromSourceCode(
"""
class B
typealias A = B
""".trimIndent()
)
)
assertEquals(setOf("A", "B"), resolver.findCommonId("A"))
assertEquals(setOf("A", "B"), resolver.findCommonId("B"))
assertEquals(setOf("A", "B"), resolver.findCommonId("C"))
}
/*
Rather esoteric case!
Platform A: A -> B -> C D -> E
Platform B: B -> C - D
Expected: B, C, D
*/
fun `test sample 5`() {
val rootA = createCirTreeRootFromSourceCode(
"""
class C
typealias B = C
typealias A = B
class E
typealias D = E
""".trimIndent()
)
val rootB = createCirTreeRootFromSourceCode(
"""
class D
typealias C = D
typealias B = C
""".trimIndent()
)
val resolver = createCommonClassifierIdResolver(rootB, rootA)
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"))
}
}
private fun createCommonClassifierIdResolver(vararg root: CirTreeRoot): CirCommonClassifierIdResolver {
return CirCommonClassifierIdResolver(
TargetDependent(root.withIndex().associate { (index, root) -> LeafCommonizerTarget(index.toString()) to root })
.mapValue(::CirClassifierIndex)
)
}
private fun CirCommonClassifierIdResolver.findCommonId(id: String): Set<String> =
findCommonId(CirEntityId.create(id))?.aliases.orEmpty().map { it.toQualifiedNameString() }.toSet()
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.commonizer.utils
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.commonizer.AbstractInlineSourcesCommonizationTest.InlineSourcesCommonizationTestDsl
import org.jetbrains.kotlin.commonizer.tree.CirTreeModule
import org.jetbrains.kotlin.commonizer.tree.CirTreeRoot
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.library.SerializedMetadata
@@ -83,3 +84,13 @@ fun InlineSourceBuilder.createMetadata(builder: InlineSourceBuilder.ModuleBuilde
return createMetadata(createModule(builder))
}
@InlineSourceBuilder.ModuleBuilderDsl
fun InlineSourceBuilder.createCirTreeRoot(builder: InlineSourceBuilder.ModuleBuilder.() -> Unit): CirTreeRoot {
return CirTreeRoot(listOf(createCirTree(builder)))
}
@InlineSourceBuilder.ModuleBuilderDsl
fun InlineSourceBuilder.createCirTreeRootFromSourceCode(@Language("kotlin") sourceCode: String): CirTreeRoot {
return CirTreeRoot(listOf(createCirTreeFromSourceCode(sourceCode)))
}