[Commonizer] Implement alis type substitution as CirNodeTransformer

This has the advantage, that the substitution only has to run on
functions or properties that are 'incomplete' (missing at least
one other target declaration)

^KT-47433 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-07-12 15:15:43 +02:00
committed by Space
parent 7c450f9884
commit c4d90dc744
8 changed files with 189 additions and 41 deletions

View File

@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.commonizer.mergedtree.CirNode.Companion.indexOfCommo
import org.jetbrains.kotlin.commonizer.mergedtree.CirRootNode
import org.jetbrains.kotlin.commonizer.metadata.CirTreeSerializer
import org.jetbrains.kotlin.commonizer.transformer.InlineTypeAliasCirNodeTransformer
import org.jetbrains.kotlin.commonizer.transformer.TypeSubstitutionCirNodeTransformer
import org.jetbrains.kotlin.commonizer.tree.CirTreeRoot
import org.jetbrains.kotlin.commonizer.tree.defaultCirTreeRootDeserializer
import org.jetbrains.kotlin.commonizer.tree.mergeCirTree
@@ -56,7 +57,10 @@ internal fun commonizeTarget(
)
val mergedTree = mergeCirTree(parameters.storageManager, classifiers, availableTrees)
InlineTypeAliasCirNodeTransformer(parameters.storageManager, classifiers).invoke(mergedTree)
TypeSubstitutionCirNodeTransformer(parameters.storageManager, classifiers, availableTrees).invoke(mergedTree)
mergedTree.accept(CommonizationVisitor(classifiers, mergedTree), Unit)
return mergedTree

View File

@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.commonizer.mergedtree
import org.jetbrains.kotlin.commonizer.cir.CirClass
internal class CirMemberContext private constructor(val classes: List<CirClass>) {
class CirMemberContext private constructor(internal val classes: List<CirClass>) {
companion object {
val empty = CirMemberContext(emptyList())

View File

@@ -3,11 +3,15 @@
* 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
package org.jetbrains.kotlin.commonizer.transformer
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.cir.CirClassType.Companion.copyInterned
import org.jetbrains.kotlin.commonizer.cir.CirTypeAliasType.Companion.copyInterned
import org.jetbrains.kotlin.commonizer.mergedtree.CirClassifierIndex
import org.jetbrains.kotlin.commonizer.mergedtree.CirProvidedClassifiers
import org.jetbrains.kotlin.commonizer.mergedtree.CirTypeSubstitutor
import org.jetbrains.kotlin.commonizer.tree.CirTreeTypeAlias
internal class CirAliasTypeSubstitutor(
private val commonDependencies: CirProvidedClassifiers,
@@ -33,7 +37,6 @@ internal class CirAliasTypeSubstitutor(
}
private fun substituteClassOrTypeAliasType(targetIndex: Int, type: CirClassOrTypeAliasType): CirClassOrTypeAliasType {
/*
Classifier id is available in all platforms or is provided by common dependencies.
The classifier itself does not require substitution, but type arguments potentially do!
@@ -46,7 +49,6 @@ internal class CirAliasTypeSubstitutor(
} else type
}
/**
* Yet, we do not have any evidence that it is worth trying to substitute types with arguments when the type itself is
* not common at all. This substitutor could try to substitute the type & it's arguments.
@@ -70,28 +72,34 @@ internal class CirAliasTypeSubstitutor(
private fun substituteClassType(targetIndex: Int, type: CirClassType): CirClassOrTypeAliasType {
if (type.arguments.isNotEmpty()) return type
if (type.outerType != null) return type
if (isCommon(type.classifierId)) return type
val classifierIndex = classifierIndices[targetIndex]
var typeAliases = classifierIndex.findTypeAliasesWithUnderlyingType(type.classifierId)
val typeAliases = classifierIndex.findTypeAliasesWithUnderlyingType(type.classifierId)
val commonTypeAlias = findSuitableCommonTypeAlias(classifierIndex, typeAliases) ?: return type
while (typeAliases.isNotEmpty()) {
val commonTypeAlias = typeAliases.firstOrNull { (id, typeAlias) -> typeAlias.typeParameters.isEmpty() && isCommon(id) }
return CirTypeAliasType.createInterned(
typeAliasId = commonTypeAlias.id,
underlyingType = commonTypeAlias.typeAlias.underlyingType,
arguments = emptyList(),
isMarkedNullable = type.isMarkedNullable
)
}
if (commonTypeAlias != null) {
return CirTypeAliasType.createInterned(
typeAliasId = commonTypeAlias.id,
underlyingType = commonTypeAlias.typeAlias.underlyingType,
arguments = emptyList(),
isMarkedNullable = type.isMarkedNullable
)
}
private tailrec fun findSuitableCommonTypeAlias(
index: CirClassifierIndex,
typeAliases: List<CirTreeTypeAlias>
): CirTreeTypeAlias? {
if (typeAliases.isEmpty()) return null
typeAliases = typeAliases.flatMap { (id, _) ->
classifierIndex.findTypeAliasesWithUnderlyingType(id)
}
val commonTypeAlias = typeAliases.firstOrNull { (id, typeAlias) -> typeAlias.typeParameters.isEmpty() && isCommon(id) }
if (commonTypeAlias != null) {
return commonTypeAlias
}
return type
return findSuitableCommonTypeAlias(
index, typeAliases.flatMap { (id, _) -> index.findTypeAliasesWithUnderlyingType(id) }
)
}
private fun isCommon(id: CirEntityId): Boolean =

View File

@@ -0,0 +1,76 @@
/*
* 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.transformer
import org.jetbrains.kotlin.commonizer.mergedtree.*
import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.ParentNode
import org.jetbrains.kotlin.commonizer.tree.CirTreeRoot
import org.jetbrains.kotlin.storage.StorageManager
internal fun TypeSubstitutionCirNodeTransformer(
storageManager: StorageManager, classifiers: CirKnownClassifiers, roots: Iterable<CirTreeRoot>
): TypeSubstitutionCirNodeTransformer {
val typeSubstitutor = CirAliasTypeSubstitutor(classifiers.commonDependencies, roots.map(::CirClassifierIndex))
return TypeSubstitutionCirNodeTransformer(storageManager, classifiers, typeSubstitutor)
}
internal class TypeSubstitutionCirNodeTransformer(
private val storageManager: StorageManager,
private val classifiers: CirKnownClassifiers,
private val typeSubstitutor: CirTypeSubstitutor
) : CirNodeTransformer {
override fun invoke(root: CirRootNode) {
for (index in 0 until root.targetDeclarations.size) {
root.modules.forEach { (_, module) -> this(module, index) }
}
}
private operator fun invoke(module: CirModuleNode, index: Int) {
module.packages.forEach { (_, pkg) -> this(pkg, index) }
}
private operator fun invoke(pkg: CirPackageNode, index: Int) {
pkg.functions.values.toList().forEach { function -> this(pkg, function, index, CirMemberContext.empty) }
pkg.properties.values.toList().forEach { property -> this(pkg, property, index, CirMemberContext.empty) }
pkg.classes.values.toList().forEach { clazz -> this(clazz, index, CirMemberContext.empty) }
}
private operator fun invoke(clazz: CirClassNode, index: Int, context: CirMemberContext) {
val contextWithClass = context.withContextOf(clazz.targetDeclarations[index] ?: return)
clazz.functions.values.toList().forEach { function -> this(clazz, function, index, contextWithClass) }
clazz.properties.values.toList().forEach { property -> this(clazz, property, index, contextWithClass) }
clazz.classes.values.toList().forEach { innerClass -> this(innerClass, index, contextWithClass) }
}
private operator fun invoke(parent: CirNodeWithMembers<*, *>, function: CirFunctionNode, index: Int, context: CirMemberContext) {
/* Only perform type substitution to nodes that are not 'complete' */
if (function.targetDeclarations.none { it == null }) return
val originalFunction = function.targetDeclarations[index] ?: return
val newFunction = typeSubstitutor.substitute(index, originalFunction)
if (originalFunction == newFunction) return
val approximationKey = FunctionApproximationKey.create(context, newFunction)
val newNode = parent.functions.getOrPut(approximationKey) {
buildFunctionNode(storageManager, parent.targetDeclarations.size, classifiers, ParentNode(parent))
}
newNode.targetDeclarations.setAllowingOverride(index, newFunction)
}
private operator fun invoke(parent: CirNodeWithMembers<*, *>, property: CirPropertyNode, index: Int, context: CirMemberContext) {
val originalProperty = property.targetDeclarations[index] ?: return
val newProperty = typeSubstitutor.substitute(index, originalProperty)
if (originalProperty == newProperty) return
val approximationKey = PropertyApproximationKey.create(context, newProperty)
val newNode = parent.properties.getOrPut(approximationKey) {
buildPropertyNode(storageManager, parent.targetDeclarations.size, classifiers, ParentNode(parent))
}
newNode.targetDeclarations.setAllowingOverride(index, newProperty)
}
}

View File

@@ -8,13 +8,14 @@ package org.jetbrains.kotlin.commonizer.tree
import org.jetbrains.kotlin.commonizer.TargetDependent
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.mergedtree.*
import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.Companion.ParentNode
import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.ParentNode
import org.jetbrains.kotlin.storage.StorageManager
internal data class TargetBuildingContext(
val storageManager: StorageManager,
val classifiers: CirKnownClassifiers,
val memberContext: CirMemberContext = CirMemberContext.empty,
val typeSubstitutor: CirTypeSubstitutor,
val targets: Int, val targetIndex: Int
) {
fun withMemberContextOf(clazz: CirClass) = copy(memberContext = memberContext.withContextOf(clazz))
@@ -31,10 +32,6 @@ internal fun mergeCirTree(
storageManager = storageManager,
classifiers = classifiers,
memberContext = CirMemberContext.empty,
typeSubstitutor = CirAliasTypeSubstitutor(
commonDependencies = classifiers.commonDependencies,
classifierIndices = roots.map(::CirClassifierIndex)
),
targets = roots.size,
targetIndex = targetIndex
), roots[target].modules
@@ -70,7 +67,7 @@ internal fun CirNodeWithMembers<*, *>.buildClass(
context: TargetBuildingContext, treeClass: CirTreeClass, parent: CirNode<*, *>? = null
) {
val classNode = classes.getOrPut(treeClass.clazz.name) {
buildClassNode(context.storageManager, context.targets, context.classifiers, CirNodeRelationship.ParentNode(parent), treeClass.id)
buildClassNode(context.storageManager, context.targets, context.classifiers, ParentNode(parent), treeClass.id)
}
classNode.targetDeclarations[context.targetIndex] = treeClass.clazz
val contextWithClass = context.withMemberContextOf(treeClass.clazz)
@@ -83,28 +80,26 @@ internal fun CirNodeWithMembers<*, *>.buildClass(
internal fun CirNodeWithMembers<*, *>.buildFunction(
context: TargetBuildingContext, function: CirFunction, parent: CirNode<*, *>? = null
) {
val newFunction = context.typeSubstitutor.substitute(context.targetIndex, function)
val functionNode = functions.getOrPut(FunctionApproximationKey.create(context.memberContext, newFunction)) {
buildFunctionNode(context.storageManager, context.targets, context.classifiers, CirNodeRelationship.ParentNode(parent))
val functionNode = functions.getOrPut(FunctionApproximationKey.create(context.memberContext, function)) {
buildFunctionNode(context.storageManager, context.targets, context.classifiers, ParentNode(parent))
}
functionNode.targetDeclarations[context.targetIndex] = newFunction
functionNode.targetDeclarations[context.targetIndex] = function
}
internal fun CirNodeWithMembers<*, *>.buildProperty(
context: TargetBuildingContext, property: CirProperty, parent: CirNode<*, *>? = null
) {
val newProperty = context.typeSubstitutor.substitute(context.targetIndex, property)
val propertyNode = properties.getOrPut(PropertyApproximationKey.create(context.memberContext, newProperty)) {
buildPropertyNode(context.storageManager, context.targets, context.classifiers, CirNodeRelationship.ParentNode(parent))
val propertyNode = properties.getOrPut(PropertyApproximationKey.create(context.memberContext, property)) {
buildPropertyNode(context.storageManager, context.targets, context.classifiers, ParentNode(parent))
}
propertyNode.targetDeclarations[context.targetIndex] = newProperty
propertyNode.targetDeclarations[context.targetIndex] = property
}
internal fun CirClassNode.buildConstructor(
context: TargetBuildingContext, constructor: CirClassConstructor, parent: CirNode<*, *>
) {
val constructorNode = constructors.getOrPut(ConstructorApproximationKey.create(context.memberContext, constructor)) {
buildClassConstructorNode(context.storageManager, context.targets, context.classifiers, CirNodeRelationship.ParentNode(parent))
buildClassConstructorNode(context.storageManager, context.targets, context.classifiers, ParentNode(parent))
}
constructorNode.targetDeclarations[context.targetIndex] = constructor
}

View File

@@ -31,7 +31,9 @@ class CommonizedGroup<T : Any>(
elements[index] = value
}
internal fun setAllowingOverride(index: Int, value: T?) {
elements[index] = value
}
}
interface I<T>

View File

@@ -182,9 +182,40 @@ class HierarchicalPropertyCommonizationTest : AbstractInlineSourcesCommonization
result.assertCommonized(
"(a, b)", """
expect class AB expect constructor()
expect typealias TA_AB = AB
typealias TA_AB = AB
// https://youtrack.jetbrains.com/issue/KT-47100
expect val x: TA_AB
expect val x: AB
""".trimIndent()
)
}
fun `test single typeAliased property and double typeAliased property - with reversed order`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class AB
typealias TA_AB = AB
typealias TA_B = TA_AB
val x: TA_B = TA_B()
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class AB
typealias TA_AB = AB
val x: TA_AB = TA_AB()
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class AB expect constructor()
typealias TA_AB = AB
// https://youtrack.jetbrains.com/issue/KT-47100
expect val x: AB
""".trimIndent()
)
}

View File

@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.commonizer.assertCommonized
class HierarchicalTypeSubstitutionCommonizationTest : AbstractInlineSourcesCommonizationTest() {
fun `test boxed function using TA and expanded type`() {
fun `test function with boxed parameter`() {
val result = commonize {
outputTarget("(a, b)")
@@ -43,7 +43,7 @@ class HierarchicalTypeSubstitutionCommonizationTest : AbstractInlineSourcesCommo
}
fun `test boxed function using TA and expanded type - with box from dependencies`() {
fun `test function with boxed parameter - with box from dependencies`() {
val result = commonize {
outputTarget("(a, b)")
@@ -76,7 +76,7 @@ class HierarchicalTypeSubstitutionCommonizationTest : AbstractInlineSourcesCommo
)
}
fun `test parameters with non-commonized TA expanding to a commonized type`() {
fun `test function parameter with suitable typealias`() {
val result = commonize {
outputTarget("(a, b)")
@@ -103,4 +103,36 @@ class HierarchicalTypeSubstitutionCommonizationTest : AbstractInlineSourcesCommo
""".trimIndent()
)
}
fun `test boxed property return type`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class Box<T>
class A
typealias X = A
val x: Box<X>
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class Box<T>
class B
typealias X = B
val x: Box<B>
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class Box<T> expect constructor()
expect class X expect constructor()
expect val x: Box<X>
""".trimIndent()
)
}
}