From 763ddcddf47746086ebe69d1e24b78a8332e55e9 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Wed, 8 Sep 2021 12:52:21 +0200 Subject: [PATCH] [Commonizer] Implement CirCommonClassifierId & CirCommonClassifierIdResolver This common representation of a classifier id can later be used to render typealias invariant approximation keys. ^KT-48288 --- .../kotlin/commonizer/cir/CirClassifier.kt | 2 +- .../mergedtree/CirClassifierIndex.kt | 24 +++ .../mergedtree/CirCommonClassifierId.kt | 19 ++ .../CirCommonClassifierIdResolver.kt | 62 ++++++ .../commonizer/metadata/CirTreeSerializer.kt | 1 - .../CirCommonClassifierIdResolverTest.kt | 197 ++++++++++++++++++ .../commonizer/utils/InlineSourceBuilder.kt | 11 + 7 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierId.kt create mode 100644 native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierIdResolver.kt create mode 100644 native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirClassifier.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirClassifier.kt index d843a77ea60..9abddf26d7a 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirClassifier.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirClassifier.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.commonizer.cir -interface CirClassifier : +sealed interface CirClassifier : CirDeclaration, CirHasAnnotations, CirHasName, diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirClassifierIndex.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirClassifierIndex.kt index 9f8e681d3a3..d835ae3ab56 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirClassifierIndex.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirClassifierIndex.kt @@ -28,6 +28,30 @@ interface CirClassifierIndex { fun findTypeAliasesWithUnderlyingType(underlyingClassifier: CirEntityId): List } +/** + * 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 { + /* Fast Path (no aliases) */ + val firstAliases = findTypeAliasesWithUnderlyingType(underlyingClassifier) + if (firstAliases.isEmpty()) return emptyList() + + val resolved = ArrayList(firstAliases) + val enqueued = ArrayDeque(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 diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierId.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierId.kt new file mode 100644 index 00000000000..5f434d6c5e5 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierId.kt @@ -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) { + override fun toString(): String { + return aliases.joinToString(prefix = "(", postfix = ")") + } + + init { + require(aliases.isNotEmpty()) + } +} \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierIdResolver.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierIdResolver.kt new file mode 100644 index 00000000000..6a1db501755 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/CirCommonClassifierIdResolver.kt @@ -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) { + fun findCommonId(id: CirEntityId): CirCommonClassifierId? { + val results = mutableSetOf() + + /* Set of every classifier id that once was enqueued already */ + val enqueued = mutableSetOf() + + /* Actual, current queue of classifiers to resolve */ + val queue = ArrayDeque() + + 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 + } +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/metadata/CirTreeSerializer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/metadata/CirTreeSerializer.kt index aea3bea15c2..bebfa16b0a4 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/metadata/CirTreeSerializer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/metadata/CirTreeSerializer.kt @@ -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") } } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt new file mode 100644 index 00000000000..7606a4f9be9 --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/CirCommonClassifierIdResolverTest.kt @@ -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(), resolver.findCommonId("A")) + assertEquals(emptySet(), 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 = + findCommonId(CirEntityId.create(id))?.aliases.orEmpty().map { it.toQualifiedNameString() }.toSet() + diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/utils/InlineSourceBuilder.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/utils/InlineSourceBuilder.kt index 499809e89c2..aff691ea0f9 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/utils/InlineSourceBuilder.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/utils/InlineSourceBuilder.kt @@ -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))) +}