[Commonizer] Prototype: Commonize TypeAliases with Classes

^KT-45992 Fixed
^KT-46061 Fixed
This commit is contained in:
sebastian.sellmair
2021-05-28 18:12:32 +02:00
committed by Space
parent b394dde339
commit 8272564ca0
23 changed files with 615 additions and 155 deletions
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this delegate code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.commonizer.cir
/**
* Marker for classifiers that are made up to support commonization.
* E.g. TypeAliases might create artificial classes for the types they are pointing to.
*/
internal interface ArtificialCirDeclaration : CirDeclaration
internal class ArtificialCirModule(val delegate: CirModule) : CirModule by delegate, ArtificialCirDeclaration
internal fun CirModule.markedArtificial() = ArtificialCirModule(this)
internal class ArtificialCirClass(val delegate: CirClass) : CirClass by delegate, ArtificialCirDeclaration
internal fun CirClass.markedArtificial(): ArtificialCirClass = ArtificialCirClass(this)
internal class ArtificialCirClassConstructor(val delegate: CirClassConstructor) : CirClassConstructor by delegate, ArtificialCirDeclaration
internal fun CirClassConstructor.markedArtificial() = ArtificialCirClassConstructor(this)
internal class ArtificialCirFunction(val delegate: CirFunction) : CirFunction by delegate, ArtificialCirDeclaration
internal fun CirFunction.markedArtificial() = ArtificialCirFunction(this)
internal class ArtificialCirProperty(val delegate: CirProperty) : CirProperty by delegate, ArtificialCirDeclaration
internal fun CirProperty.markedArtificial() = ArtificialCirProperty(this)
@@ -18,6 +18,8 @@ interface CirClassConstructor :
val isPrimary: Boolean
override val containingClass: CirContainingClass // non-nullable
override fun withContainingClass(containingClass: CirContainingClass): CirClassConstructor
companion object {
@Suppress("NOTHING_TO_INLINE")
inline fun create(
@@ -48,4 +50,8 @@ data class CirClassConstructorImpl(
override var valueParameters: List<CirValueParameter>,
override var hasStableParameterNames: Boolean,
override val isPrimary: Boolean
) : CirClassConstructor
) : CirClassConstructor {
override fun withContainingClass(containingClass: CirContainingClass): CirClassConstructor {
return copy(containingClass = containingClass)
}
}
@@ -41,8 +41,13 @@ interface CirHasModality {
val modality: Modality
}
interface CirMaybeCallableMemberOfClass {
val containingClass: CirContainingClass? // null assumes no containing class
fun withContainingClass(containingClass: CirContainingClass): CirMaybeCallableMemberOfClass = object : CirMaybeCallableMemberOfClass {
override val containingClass: CirContainingClass = containingClass
}
}
/**
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.descriptors.Visibility
interface CirFunction : CirFunctionOrProperty, CirCallableMemberWithParameters {
val modifiers: CirFunctionModifiers
override fun withContainingClass(containingClass: CirContainingClass): CirFunction
companion object {
@Suppress("NOTHING_TO_INLINE")
inline fun create(
@@ -57,4 +59,8 @@ data class CirFunctionImpl(
override val returnType: CirType,
override val kind: CallableMemberDescriptor.Kind,
override val modifiers: CirFunctionModifiers
) : CirFunction
) : CirFunction {
override fun withContainingClass(containingClass: CirContainingClass): CirFunction {
return copy(containingClass = containingClass)
}
}
@@ -21,6 +21,8 @@ interface CirProperty : CirFunctionOrProperty, CirLiftedUpDeclaration {
val delegateFieldAnnotations: List<CirAnnotation>
val compileTimeInitializer: CirConstantValue
override fun withContainingClass(containingClass: CirContainingClass): CirProperty
companion object {
@Suppress("NOTHING_TO_INLINE")
inline fun create(
@@ -90,4 +92,8 @@ data class CirPropertyImpl(
) : CirProperty {
// const property in "common" fragment is already lifted up
override val isLiftedUp get() = isConst
override fun withContainingClass(containingClass: CirContainingClass): CirProperty {
return copy(containingClass = containingClass)
}
}
@@ -16,26 +16,21 @@ import org.jetbrains.kotlin.descriptors.Modality
*
* Secondary (less optimistic) branch:
* - Make sure that all TAs are identical, so the resulting TA can be lifted up into "common" fragment.
*
* Tertiary (backup) branch:
* - Produce an "expect class" for "common" fragment and the corresponding "actual typealias" declarations for each platform fragment.
*/
class TypeAliasCommonizer(classifiers: CirKnownClassifiers) : AbstractStandardCommonizer<CirTypeAlias, CirClassifier>() {
private val primary = TypeAliasShortCircuitingCommonizer(classifiers)
private val secondary = TypeAliasLiftingUpCommonizer(classifiers)
private val tertiary = TypeAliasExpectClassCommonizer()
override fun commonizationResult(): CirClassifier = primary.resultOrNull ?: secondary.resultOrNull ?: tertiary.result
override fun commonizationResult(): CirClassifier = primary.resultOrNull ?: secondary.result
override fun initialize(first: CirTypeAlias) = Unit
override fun doCommonizeWith(next: CirTypeAlias): Boolean {
val primaryResult = primary.commonizeWith(next)
val secondaryResult = secondary.commonizeWith(next)
val tertiaryResult = tertiary.commonizeWith(next)
// Note: don't call commonizeWith() functions in return statement to avoid short-circuiting!
return primaryResult || secondaryResult || tertiaryResult
return primaryResult || secondaryResult
}
}
@@ -94,9 +89,6 @@ private class TypeAliasLiftingUpCommonizer(classifiers: CirKnownClassifiers) : A
)
}
val resultOrNull: CirTypeAlias?
get() = if (hasResult) commonizationResult() else null
override fun initialize(first: CirTypeAlias) {
name = first.name
}
@@ -106,43 +98,3 @@ private class TypeAliasLiftingUpCommonizer(classifiers: CirKnownClassifiers) : A
&& underlyingType.commonizeWith(next.underlyingType)
&& visibility.commonizeWith(next)
}
private class TypeAliasExpectClassCommonizer : AbstractStandardCommonizer<CirTypeAlias, CirClass>() {
private lateinit var name: CirName
private val classVisibility = VisibilityCommonizer.equalizing()
override fun commonizationResult(): CirClass = CirClass.create(
annotations = emptyList(),
name = name,
typeParameters = emptyList(),
visibility = classVisibility.result,
modality = Modality.FINAL,
kind = ClassKind.CLASS,
companion = null,
isCompanion = false,
isData = false,
isValue = false,
isInner = false,
isExternal = false
)
override fun initialize(first: CirTypeAlias) {
name = first.name
}
override fun doCommonizeWith(next: CirTypeAlias): Boolean {
if (next.typeParameters.isNotEmpty())
return false // TAs with declared type parameters can't be commonized
val underlyingType = next.underlyingType as? CirClassType ?: return false // right-hand side could have only class
return hasNoArguments(underlyingType) // TAs with functional types or types with arguments at the right-hand side can't be commonized
&& classVisibility.commonizeWith(underlyingType) // the visibilities of the right-hand classes should be equal
}
private tailrec fun hasNoArguments(type: CirClassType?): Boolean =
when {
type == null -> true
type.arguments.isNotEmpty() -> false
else -> hasNoArguments(type.outerType)
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.commonizer.core
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.FAILURE_MISSING_IN_SOME_TARGET
import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.SUCCESS_FROM_DEPENDENCY_LIBRARY
import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.create
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.commonizer.utils.isUnderKotlinNativeSyntheticPackages
import org.jetbrains.kotlin.descriptors.Visibility
@@ -203,16 +204,9 @@ private fun commonizeClass(classId: CirEntityId, classifiers: CirKnownClassifier
return true
}
return when (val node = classifiers.commonizedNodes.classNode(classId)) {
null -> {
// No node means that the type alias was not subject for commonization. It is missing in some target(s) => not commonized.
false
}
else -> {
// Common declaration in node is not null -> successfully commonized.
(node.commonDeclaration() != null)
}
}
// Looking for a a node that provides a non-null (successfully commonized) classifier declaration
return (classifiers.commonizedNodes.classNode(classId)?.commonDeclaration?.invoke()
?: classifiers.commonizedNodes.typeAliasNode(classId)?.commonDeclaration?.invoke()) != null
}
private fun commonizeTypeAlias(typeAliasId: CirEntityId, classifiers: CirKnownClassifiers): CommonizedTypeAliasAnswer {
@@ -221,16 +215,14 @@ private fun commonizeTypeAlias(typeAliasId: CirEntityId, classifiers: CirKnownCl
return SUCCESS_FROM_DEPENDENCY_LIBRARY
}
return when (val node = classifiers.commonizedNodes.typeAliasNode(typeAliasId)) {
null -> {
// No node means that the type alias was not subject for commonization. It is missing in some target(s) => not commonized.
FAILURE_MISSING_IN_SOME_TARGET
}
else -> {
// Common declaration in node is not null -> successfully commonized.
CommonizedTypeAliasAnswer.create(node.commonDeclaration())
}
val typeAliasNode = classifiers.commonizedNodes.typeAliasNode(typeAliasId)
val classNode = classifiers.commonizedNodes.classNode(typeAliasId)
if (typeAliasNode == null && classNode == null) {
return FAILURE_MISSING_IN_SOME_TARGET
}
return create(typeAliasNode?.commonDeclaration?.invoke() ?: classNode?.commonDeclaration?.invoke())
}
private class CommonizedTypeAliasAnswer(val commonized: Boolean, val commonClassifier: CirClassifier?) {
@@ -18,8 +18,7 @@ internal tailrec fun computeSuitableUnderlyingType(
return when (underlyingType) {
is CirClassType -> underlyingType.withCommonizedArguments(classifiers)
is CirTypeAliasType -> if (classifiers.commonDependencies.hasClassifier(underlyingType.classifierId))
underlyingType.withCommonizedArguments(classifiers)
else
underlyingType.withCommonizedArguments(classifiers) else
computeSuitableUnderlyingType(classifiers, underlyingType.underlyingType)
}
}
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.commonizer.mergedtree.CirNode.Companion.indexOfCommo
import org.jetbrains.kotlin.commonizer.mergedtree.CirNode.Companion.targetIndices
import org.jetbrains.kotlin.commonizer.mergedtree.CirRootNode
import org.jetbrains.kotlin.commonizer.metadata.CirTreeSerializer.serializeSingleTarget
import org.jetbrains.kotlin.commonizer.transformer.Checked.Companion.invoke
import org.jetbrains.kotlin.commonizer.transformer.InlineTypeAliasCirNodeTransformer
import org.jetbrains.kotlin.commonizer.tree.CirTreeRoot
import org.jetbrains.kotlin.commonizer.tree.assembleCirTree
import org.jetbrains.kotlin.commonizer.tree.deserializeCirTree
@@ -50,6 +52,7 @@ private fun commonize(
)
val mergedTree = merge(storageManager, classifiers, cirTrees) ?: return null
InlineTypeAliasCirNodeTransformer(storageManager, classifiers).invoke(mergedTree)
mergedTree.accept(CommonizationVisitor(classifiers, mergedTree), Unit)
parameters.logProgress("Commonized declarations for $target")
@@ -0,0 +1,37 @@
/*
* 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.CirDeclaration
import org.jetbrains.kotlin.storage.NullableLazyValue
internal fun interface CommonizerCondition {
fun allowCommonization(): Boolean
infix fun and(other: CommonizerCondition) = CommonizerCondition { allowCommonization() && other.allowCommonization() }
companion object Factory {
fun none(): CommonizerCondition = NoneCommonizerCondition
fun nodeIsNotCommonized(node: CirNode<*, *>) = CommonizerCondition { node.commonDeclaration() == null }
fun parent(parent: CirNode<*, *>): ParentNodeCommonizerCondition = ParentNodeCommonizerCondition(parent.commonDeclaration)
fun parent(parent: CirNode<*, *>?): CommonizerCondition = if (parent == null) none() else parent(parent)
}
}
private object NoneCommonizerCondition : CommonizerCondition {
override fun allowCommonization(): Boolean = true
}
internal class ParentNodeCommonizerCondition(
private val parentNodeCommonDeclaration: NullableLazyValue<CirDeclaration>
) : CommonizerCondition {
override fun allowCommonization(): Boolean = parentNodeCommonDeclaration() != null
}
@@ -20,6 +20,7 @@ internal fun buildRootNode(
): CirRootNode = buildNode(
storageManager = storageManager,
size = size,
commonizerCondition = CommonizerCondition.none(),
commonizerProducer = ::RootCommonizer,
nodeProducer = ::CirRootNode
)
@@ -30,6 +31,7 @@ internal fun buildModuleNode(
): CirModuleNode = buildNode(
storageManager = storageManager,
size = size,
commonizerCondition = CommonizerCondition.none(),
commonizerProducer = ::ModuleCommonizer,
nodeProducer = ::CirModuleNode
)
@@ -40,6 +42,7 @@ internal fun buildPackageNode(
): CirPackageNode = buildNode(
storageManager = storageManager,
size = size,
commonizerCondition = CommonizerCondition.none(),
commonizerProducer = ::PackageCommonizer,
nodeProducer = ::CirPackageNode
)
@@ -48,11 +51,11 @@ internal fun buildPropertyNode(
storageManager: StorageManager,
size: Int,
classifiers: CirKnownClassifiers,
parentCommonDeclaration: NullableLazyValue<*>?
condition: CommonizerCondition = CommonizerCondition.none(),
): CirPropertyNode = buildNode(
storageManager = storageManager,
size = size,
parentCommonDeclaration = parentCommonDeclaration,
commonizerCondition = condition,
commonizerProducer = { PropertyCommonizer(classifiers) },
nodeProducer = ::CirPropertyNode
)
@@ -61,11 +64,11 @@ internal fun buildFunctionNode(
storageManager: StorageManager,
size: Int,
classifiers: CirKnownClassifiers,
parentCommonDeclaration: NullableLazyValue<*>?
condition: CommonizerCondition = CommonizerCondition.none(),
): CirFunctionNode = buildNode(
storageManager = storageManager,
size = size,
parentCommonDeclaration = parentCommonDeclaration,
commonizerCondition = condition,
commonizerProducer = { FunctionCommonizer(classifiers) },
nodeProducer = ::CirFunctionNode
)
@@ -74,12 +77,12 @@ internal fun buildClassNode(
storageManager: StorageManager,
size: Int,
classifiers: CirKnownClassifiers,
parentCommonDeclaration: NullableLazyValue<*>?,
condition: CommonizerCondition = CommonizerCondition.none(),
classId: CirEntityId
): CirClassNode = buildNode(
storageManager = storageManager,
size = size,
parentCommonDeclaration = parentCommonDeclaration,
commonizerCondition = condition,
commonizerProducer = { ClassCommonizer(classifiers) },
recursionMarker = CirClassRecursionMarker,
nodeProducer = { targetDeclarations, commonDeclaration ->
@@ -89,15 +92,16 @@ internal fun buildClassNode(
}
)
internal fun buildClassConstructorNode(
storageManager: StorageManager,
size: Int,
classifiers: CirKnownClassifiers,
parentCommonDeclaration: NullableLazyValue<*>?
condition: ParentNodeCommonizerCondition,
): CirClassConstructorNode = buildNode(
storageManager = storageManager,
size = size,
parentCommonDeclaration = parentCommonDeclaration,
commonizerCondition = condition,
commonizerProducer = { ClassConstructorCommonizer(classifiers) },
nodeProducer = ::CirClassConstructorNode
)
@@ -110,6 +114,7 @@ internal fun buildTypeAliasNode(
): CirTypeAliasNode = buildNode(
storageManager = storageManager,
size = size,
commonizerCondition = CommonizerCondition.none(),
commonizerProducer = { TypeAliasCommonizer(classifiers) },
recursionMarker = CirClassifierRecursionMarker,
nodeProducer = { targetDeclarations, commonDeclaration ->
@@ -122,14 +127,14 @@ internal fun buildTypeAliasNode(
private fun <T : CirDeclaration, R : CirDeclaration, N : CirNode<T, R>> buildNode(
storageManager: StorageManager,
size: Int,
parentCommonDeclaration: NullableLazyValue<*>? = null,
commonizerCondition: CommonizerCondition,
commonizerProducer: () -> Commonizer<T, R>,
recursionMarker: R? = null,
nodeProducer: (CommonizedGroup<T>, NullableLazyValue<R>) -> N
): N {
val targetDeclarations = CommonizedGroup<T>(size)
val commonComputable = { commonize(parentCommonDeclaration, targetDeclarations, commonizerProducer()) }
val commonComputable = { commonize(commonizerCondition, targetDeclarations, commonizerProducer()) }
val commonLazyValue = if (recursionMarker != null)
storageManager.createRecursionTolerantNullableLazyValue(commonComputable, recursionMarker)
@@ -153,14 +158,13 @@ internal fun <T : Any, R> commonize(
@Suppress("NOTHING_TO_INLINE")
private inline fun <T : Any, R> commonize(
parentCommonDeclaration: NullableLazyValue<*>?,
commonizerCondition: CommonizerCondition,
targetDeclarations: CommonizedGroup<T>,
commonizer: Commonizer<T, R>
): R? {
if (parentCommonDeclaration != null && parentCommonDeclaration.invoke() == null) {
// don't commonize declaration if it's parent failed to commonize
return null
if (commonizerCondition.allowCommonization()) {
return commonize(targetDeclarations, commonizer)
}
return commonize(targetDeclarations, commonizer)
return null
}
@@ -143,6 +143,7 @@ private class CirTreeSerializationVisitor(
classContext: CirTreeSerializationContext
): KmClass? {
val cirClass = classContext.get<CirClass>(node) ?: return null
val classTypeParametersCount = cirClass.typeParameters.size
val fullClassName = classContext.currentPath.toString()
@@ -403,7 +404,8 @@ internal data class CirTreeSerializationContext(
}
inline fun <reified T : CirDeclaration> get(node: CirNode<*, *>): T? {
return (if (isCommon) node.commonDeclaration() else node.targetDeclarations[targetIndex]) as T?
return ((if (isCommon) node.commonDeclaration() else node.targetDeclarations[targetIndex]) as T?)
.takeIf { it !is ArtificialCirDeclaration }
}
inline fun <reified T : CirDeclaration> get(node: CirNodeWithLiftingUp<*, *>): T? {
@@ -411,7 +413,7 @@ internal data class CirTreeSerializationContext(
isCommon -> node.commonDeclaration() as T?
node.isLiftedUp -> null
else -> node.targetDeclarations[targetIndex] as T?
}
}.takeIf { it !is ArtificialCirDeclaration }
}
companion object {
@@ -0,0 +1,87 @@
/*
* 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.
*/
/*
* 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.cir.ArtificialCirDeclaration
import org.jetbrains.kotlin.commonizer.mergedtree.*
internal data class CirDeclarationCount(
val artificialNodeCount: Int,
val nonArtificialNodeCount: Int
)
internal data class MutableCirDeclarationCount(
var artificialNodeCount: Int = 0,
var nonArtificialNodeCount: Int = 0
) {
fun toCirDeclarationCount() = CirDeclarationCount(
artificialNodeCount = artificialNodeCount,
nonArtificialNodeCount = nonArtificialNodeCount
)
}
internal fun CirNode<*, *>.countCirDeclarations(): CirDeclarationCount {
val counter = MutableCirDeclarationCount()
accept(CirNodeCounterVisitor, counter)
return counter.toCirDeclarationCount()
}
private object CirNodeCounterVisitor : CirNodeVisitor<MutableCirDeclarationCount, Unit> {
private operator fun MutableCirDeclarationCount.plusAssign(node: CirNode<*, *>) {
node.targetDeclarations.forEach { declaration ->
if (declaration == null) return@forEach
if (declaration is ArtificialCirDeclaration) artificialNodeCount++ else nonArtificialNodeCount++
}
}
override fun visitRootNode(node: CirRootNode, data: MutableCirDeclarationCount) {
data += node
node.modules.forEach { (_, module) -> module.accept(this, data) }
}
override fun visitModuleNode(node: CirModuleNode, data: MutableCirDeclarationCount) {
data += node
node.packages.forEach { (_, pkg) -> pkg.accept(this, data) }
}
override fun visitPackageNode(node: CirPackageNode, data: MutableCirDeclarationCount) {
data += node
node.typeAliases.forEach { (_, typeAlias) -> typeAlias.accept(this, data) }
node.properties.forEach { (_, property) -> property.accept(this, data) }
node.functions.forEach { (_, function) -> function.accept(this, data) }
node.classes.forEach { (_, clazz) -> clazz.accept(this, data) }
}
override fun visitPropertyNode(node: CirPropertyNode, data: MutableCirDeclarationCount) {
data += node
}
override fun visitFunctionNode(node: CirFunctionNode, data: MutableCirDeclarationCount) {
data += node
}
override fun visitClassNode(node: CirClassNode, data: MutableCirDeclarationCount) {
data += node
node.properties.forEach { (_, property) -> property.accept(this, data) }
node.functions.forEach { (_, function) -> function.accept(this, data) }
node.constructors.forEach { (_, constructor) -> constructor.accept(this, data) }
node.classes.forEach { (_, clazz) -> clazz.accept(this, data) }
}
override fun visitClassConstructorNode(node: CirClassConstructorNode, data: MutableCirDeclarationCount) {
data += node
}
override fun visitTypeAliasNode(node: CirTypeAliasNode, data: MutableCirDeclarationCount) {
data += node
}
}
@@ -0,0 +1,43 @@
/*
* 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.CirRootNode
internal fun interface CirNodeTransformer {
sealed class Context {
object Unchecked : Context()
}
operator fun Context.invoke(root: CirRootNode)
}
internal class Checked private constructor() : CirNodeTransformer.Context() {
companion object {
private val areAssertionsEnabled = this::class.java.desiredAssertionStatus()
operator fun CirNodeTransformer.invoke(cirRootNode: CirRootNode) {
val context = if (areAssertionsEnabled) Checked() else Unchecked
val declarationCountBefore = if (areAssertionsEnabled) cirRootNode.countCirDeclarations() else null
with(this) {
context.invoke(cirRootNode)
}
if (declarationCountBefore != null) {
val declarationCountAfter = cirRootNode.countCirDeclarations()
if (declarationCountAfter.nonArtificialNodeCount > declarationCountBefore.nonArtificialNodeCount) {
throw AssertionError(
"$this attached declarations not marked as artificial\n" +
"Declaration Count before: $declarationCountBefore\n" +
"Declaration Count after: $declarationCountAfter"
)
}
}
}
}
}
@@ -0,0 +1,143 @@
/*
* 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.cir.*
import org.jetbrains.kotlin.commonizer.mergedtree.*
import org.jetbrains.kotlin.commonizer.transformer.CirNodeTransformer.Context
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.storage.StorageManager
internal class InlineTypeAliasCirNodeTransformer(
private val storageManager: StorageManager,
private val classifiers: CirKnownClassifiers
) : CirNodeTransformer {
override fun Context.invoke(root: CirRootNode) {
root.modules.values.forEach(::invoke)
}
private operator fun invoke(module: CirModuleNode) {
val classNodeIndex = ClassNodeIndex(module)
module.packages.values.forEach { packageNode ->
packageNode.typeAliases.values.forEach { typeAliasNode ->
val targetClassNode = classNodeIndex[typeAliasNode.id] ?: packageNode.createArtificialClassNode(typeAliasNode)
inlineTypeAliasIfPossible(classNodeIndex, typeAliasNode, targetClassNode)
}
}
}
private fun inlineTypeAliasIfPossible(classes: ClassNodeIndex, fromTypeAliasNode: CirTypeAliasNode, intoClassNode: CirClassNode) {
fromTypeAliasNode.targetDeclarations.forEachIndexed { targetIndex, typeAlias ->
if (typeAlias != null) {
inlineTypeAliasIfPossible(classes, typeAlias, intoClassNode, targetIndex)
}
}
}
private fun inlineTypeAliasIfPossible(
classes: ClassNodeIndex, fromTypeAlias: CirTypeAlias, intoClassNode: CirClassNode, targetIndex: Int
) {
if (fromTypeAlias.typeParameters.isNotEmpty()) {
// Inlining parameterized TAs is not supported yet
return
}
if (fromTypeAlias.underlyingType.arguments.isNotEmpty() ||
fromTypeAlias.underlyingType.run { this as? CirClassType }?.outerType?.arguments?.isNotEmpty() == true
) {
// Inlining TAs with parameterized underlying types is not supported yet
return
}
if (intoClassNode.targetDeclarations[targetIndex] != null) {
// No empty spot to inline the type-alias into
return
}
val fromAliasedClassNode = classes[fromTypeAlias.expandedType.classifierId]
val intoArtificialClass = ArtificialAliasedCirClass(
pointingTypeAlias = fromTypeAlias,
pointedClass = fromAliasedClassNode?.targetDeclarations?.get(targetIndex) ?: fromTypeAlias.toArtificialCirClass()
)
intoClassNode.targetDeclarations[targetIndex] = intoArtificialClass
if (fromAliasedClassNode != null && !fromTypeAlias.expandedType.isMarkedNullable) {
inlineArtificialMembers(fromAliasedClassNode, intoClassNode, intoArtificialClass, targetIndex)
}
}
private fun inlineArtificialMembers(
fromAliasedClassNode: CirClassNode,
intoClassNode: CirClassNode,
intoClass: CirClass,
targetIndex: Int
) {
val targetSize = intoClassNode.targetDeclarations.size
fromAliasedClassNode.constructors.forEach { (key, aliasedConstructorNode) ->
val aliasedConstructor = aliasedConstructorNode.targetDeclarations[targetIndex] ?: return@forEach
intoClassNode.constructors.getOrPut(key) {
buildClassConstructorNode(storageManager, targetSize, classifiers, CommonizerCondition.parent(intoClassNode))
}.targetDeclarations[targetIndex] = aliasedConstructor.withContainingClass(intoClass).markedArtificial()
}
fromAliasedClassNode.functions.forEach { (key, aliasedFunctionNode) ->
val aliasedFunction = aliasedFunctionNode.targetDeclarations[targetIndex] ?: return@forEach
intoClassNode.functions.getOrPut(key) {
buildFunctionNode(storageManager, targetSize, classifiers, CommonizerCondition.parent(intoClassNode))
}.targetDeclarations[targetIndex] = aliasedFunction.withContainingClass(intoClass).markedArtificial()
}
fromAliasedClassNode.properties.forEach { (key, aliasedPropertyNode) ->
val aliasedProperty = aliasedPropertyNode.targetDeclarations[targetIndex] ?: return@forEach
intoClassNode.properties.getOrPut(key) {
buildPropertyNode(storageManager, targetSize, classifiers, CommonizerCondition.parent(intoClassNode))
}.targetDeclarations[targetIndex] = aliasedProperty.withContainingClass(intoClass).markedArtificial()
}
}
private fun CirPackageNode.createArtificialClassNode(typeAliasNode: CirTypeAliasNode): CirClassNode {
val classNode = buildClassNode(
storageManager = storageManager,
size = typeAliasNode.targetDeclarations.size,
classifiers = classifiers,
// This artificial class node should only try to commonize if the package node is commonized
// and if the original typeAliasNode cannot be commonized.
// Therefore, this artificial class node acts as a fallback with the original type-alias being still the preferred
// option for commonization
condition = CommonizerCondition.parent(this) and CommonizerCondition.nodeIsNotCommonized(typeAliasNode),
classId = typeAliasNode.id
)
this.classes[typeAliasNode.classifierName] = classNode
return classNode
}
}
private typealias ClassNodeIndex = Map<CirEntityId, CirClassNode>
private fun ClassNodeIndex(module: CirModuleNode): ClassNodeIndex = module.packages.values
.flatMap { pkg -> pkg.classes.values }
.associateBy { clazz -> clazz.id }
private data class ArtificialAliasedCirClass(
val pointingTypeAlias: CirTypeAlias,
val pointedClass: CirClass
) : CirClass by pointedClass, ArtificialCirDeclaration {
override val name: CirName = pointingTypeAlias.name
override var companion: CirName?
get() = null
set(_) = throw UnsupportedOperationException("Can't set companion on artificial class (pointed by $pointingTypeAlias)")
}
private fun CirTypeAlias.toArtificialCirClass(): CirClass = CirClass.create(
annotations = emptyList(), name = name, typeParameters = typeParameters,
visibility = this.visibility, modality = Modality.FINAL, kind = ClassKind.CLASS,
companion = null, isCompanion = false, isData = false, isValue = false, isInner = false, isExternal = false
).also { it.supertypes = emptyList() }.markedArtificial()
@@ -52,7 +52,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, parent?.commonDeclaration, treeClass.id)
buildClassNode(context.storageManager, context.targets, context.classifiers, CommonizerCondition.parent(parent), treeClass.id)
}
classNode.targetDeclarations[context.targetIndex] = treeClass.clazz
treeClass.functions.forEach { function -> classNode.buildFunction(context, function, classNode) }
@@ -65,7 +65,7 @@ internal fun CirNodeWithMembers<*, *>.buildFunction(
context: TargetBuildingContext, treeFunction: CirTreeFunction, parent: CirNode<*, *>? = null
) {
val functionNode = functions.getOrPut(treeFunction.approximationKey) {
buildFunctionNode(context.storageManager, context.targets, context.classifiers, parent?.commonDeclaration)
buildFunctionNode(context.storageManager, context.targets, context.classifiers, CommonizerCondition.parent(parent))
}
functionNode.targetDeclarations[context.targetIndex] = treeFunction.function
}
@@ -74,16 +74,16 @@ internal fun CirNodeWithMembers<*, *>.buildProperty(
context: TargetBuildingContext, treeProperty: CirTreeProperty, parent: CirNode<*, *>? = null
) {
val propertyNode = properties.getOrPut(treeProperty.approximationKey) {
buildPropertyNode(context.storageManager, context.targets, context.classifiers, parent?.commonDeclaration)
buildPropertyNode(context.storageManager, context.targets, context.classifiers, CommonizerCondition.parent(parent))
}
propertyNode.targetDeclarations[context.targetIndex] = treeProperty.property
}
internal fun CirClassNode.buildConstructor(
context: TargetBuildingContext, treeConstructor: CirTreeClassConstructor, parent: CirNode<*, *>?
context: TargetBuildingContext, treeConstructor: CirTreeClassConstructor, parent: CirNode<*, *>
) {
val constructorNode = constructors.getOrPut(treeConstructor.approximationKey) {
buildClassConstructorNode(context.storageManager, context.targets, context.classifiers, parent?.commonDeclaration)
buildClassConstructorNode(context.storageManager, context.targets, context.classifiers, CommonizerCondition.parent(parent))
}
constructorNode.targetDeclarations[context.targetIndex] = treeConstructor.constructor
}
@@ -94,4 +94,3 @@ internal fun CirPackageNode.buildTypeAlias(context: TargetBuildingContext, treeT
}
typeAliasNode.targetDeclarations[context.targetIndex] = treeTypeAlias.typeAlias
}
@@ -1,5 +1,4 @@
expect class A()
// Lifted up type aliases:
typealias B = A // class at the RHS
typealias C = A // TA at the RHS, expanded to the same class
@@ -40,10 +39,11 @@ typealias U = A // same nullability of the RHS class
expect class V // different nullability of the RHS class
typealias W = A // same nullability of the RHS TA
expect class X // different nullability of the RHS TA
typealias Y = V // TA at the RHS with the different nullability of own RHS
// Supertypes:
expect class FILE : kotlinx.cinterop.CStructVar
expect class FILE expect constructor(): kotlinx.cinterop.CStructVar
typealias uuid_t = kotlinx.cinterop.CArrayPointer<kotlinx.cinterop.UByteVar>
typealias __darwin_uuid_t = kotlinx.cinterop.CArrayPointer<kotlinx.cinterop.UByteVar>
@@ -6,18 +6,18 @@ public typealias E1 = A1
protected typealias E2 = A1
internal typealias E3 = A1
public expect class E4
public expect class E4()
protected typealias F1 = A1
public expect class F2
public expect class F3
public expect class F2()
public expect class F3()
internal typealias G1 = A1
public expect class G2
public expect class G2()
public expect class H1
public expect class H1()
public typealias I1 = A1
public typealias I2 = B1
@@ -19,4 +19,4 @@ expect class AnnotatedClass(value: String) {
}
typealias AnnotatedLiftedUpTypeAlias = AnnotatedClass
expect class AnnotatedNonLiftedUpTypeAlias
expect class AnnotatedNonLiftedUpTypeAlias expect constructor(expect val value: String)
@@ -272,14 +272,6 @@ class TypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
shouldFailOnFirstVariant = true
)
@Test
// why success: expect class/actual TAs
fun taTypesInUserPackageWithDifferentClasses() = doTestSuccess(
expected = mockClassType("org/sample/FooAlias"),
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Foo") },
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Bar") }
)
@Test
// why success: short-circuiting & lifting up
fun multilevelTATypesInUserPackageWithSameNameAndRightHandSideClass1() = doTestSuccess(
@@ -338,28 +330,6 @@ class TypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
}
)
@Test
// why success: lifting up outer TA and expect class for inner TA
fun multilevelTATypesInUserPackageWithSameNameAndRightHandSideClass4() = doTestSuccess(
expected = mockTAType("org/sample/FooAlias") {
mockTAType("org/sample/FooAliasL2") {
mockClassType("org/sample/Foo")
}
},
mockTAType("org/sample/FooAlias") {
mockTAType("org/sample/FooAliasL2") {
mockClassType("org/sample/Bar")
}
},
mockTAType("org/sample/FooAlias") {
mockTAType("org/sample/FooAliasL2") {
mockClassType("org/sample/Baz")
}
}
)
@Test
// why success: types with the same nullability are treated as equal
fun taTypesInKotlinPackageWithSameNullability1() = doTestSuccess(
@@ -432,30 +402,16 @@ class TypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
fun taTypesInUserPackageWithDifferentNullability1() = doTestFailure(
mockTAType("org/sample/FooAlias", nullable = false) { mockClassType("org/sample/Foo") },
mockTAType("org/sample/FooAlias", nullable = false) { mockClassType("org/sample/Foo") },
mockTAType("org/sample/FooAlias", nullable = true) { mockClassType("org/sample/Foo") }
mockTAType("org/sample/FooAlias", nullable = true) { mockClassType("org/sample/Foo") },
shouldFailOnFirstVariant = true
)
@Test(expected = IllegalCommonizerStateException::class)
fun taTypesInUserPackageWithDifferentNullability2() = doTestFailure(
mockTAType("org/sample/FooAlias", nullable = true) { mockClassType("org/sample/Foo") },
mockTAType("org/sample/FooAlias", nullable = true) { mockClassType("org/sample/Foo") },
mockTAType("org/sample/FooAlias", nullable = false) { mockClassType("org/sample/Foo") }
)
@Test
// why success: nullability of underlying type does not matter if expect class/actual TAs created
fun taTypesInUserPackageWithDifferentNullability3() = doTestSuccess(
expected = mockClassType("org/sample/FooAlias"),
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Foo", nullable = false) },
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Foo", nullable = true) }
)
@Test
// why success: nullability of underlying type does not matter if expect class/actual TAs created
fun taTypesInUserPackageWithDifferentNullability4() = doTestSuccess(
expected = mockClassType("org/sample/FooAlias"),
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Foo", nullable = true) },
mockTAType("org/sample/FooAlias") { mockClassType("org/sample/Foo", nullable = false) }
mockTAType("org/sample/FooAlias", nullable = false) { mockClassType("org/sample/Foo") },
shouldFailOnFirstVariant = true
)
private fun prepareCache(variants: Array<out CirClassOrTypeAliasType>) {
@@ -469,7 +425,7 @@ class TypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
storageManager = LockBasedStorageManager.NO_LOCKS,
size = variants.size,
classifiers = classifiers,
parentCommonDeclaration = null,
condition = CommonizerCondition.none(),
classId = type.classifierId
)
}
@@ -104,4 +104,141 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm
""".trimIndent()
)
}
fun `test follow typeAlias on both platforms`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class A {
val x: Int = 42
}
typealias X = A
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class B {
val x: Int = 42
}
typealias X = B
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class X expect constructor() {
expect val x: Int
}
""".trimIndent()
)
}
fun `test follow exact same typeAlias on both platforms`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class AB {
val x: Int = 42
}
typealias X = AB
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class AB {
val x: Int = 42
}
typealias X = AB
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class AB expect constructor() {
expect val x: Int
}
typealias X = AB
""".trimIndent()
)
}
fun `test aliased class with companion`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class A {
companion object
}
typealias X = A
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class X {
companion object
}
""".trimIndent()
)
}
result.assertCommonized("(a, b)", """expect class X expect constructor()""")
}
fun `test typeAlias with nullability`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class A
typealias X = A?
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class B
typealias X = B
""".trimIndent()
)
}
result.assertCommonized("(a, b)", """expect class X""")
}
fun `test typeAlias chain with nullability`() {
val result = commonize {
outputTarget("(a, b)")
simpleSingleSourceTarget(
"a", """
class AB
typealias V = AB?
typealias Y = V
""".trimIndent()
)
simpleSingleSourceTarget(
"b", """
class AB
typealias V = AB
typealias Y = V
""".trimIndent()
)
}
result.assertCommonized(
"(a, b)", """
expect class AB expect constructor()
expect class V
typealias Y = V
""".trimIndent()
)
}
}
@@ -111,7 +111,7 @@ class HierarchicalTypeAliasCommonizationTest : AbstractInlineSourcesCommonizatio
"(e,f)", """expect class x expect constructor()"""
)
result.assertCommonized("((a,b), (c,d))", """expect class x""")
result.assertCommonized("(((a,b), (c,d)), (e,f))", """expect class x""")
result.assertCommonized("((a,b), (c,d))", """expect class x expect constructor()""")
result.assertCommonized("(((a,b), (c,d)), (e,f))", """expect class x expect constructor()""")
}
}
@@ -0,0 +1,51 @@
/*
* 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.cir.CirModule
import org.jetbrains.kotlin.commonizer.cir.CirName
import org.jetbrains.kotlin.commonizer.cir.markedArtificial
import org.jetbrains.kotlin.commonizer.mergedtree.buildModuleNode
import org.jetbrains.kotlin.commonizer.mergedtree.buildRootNode
import org.jetbrains.kotlin.commonizer.transformer.Checked.Companion.invoke
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
class RunTransformationTest {
private val storageManager = LockBasedStorageManager("test")
@Test
fun `fails when attaching non-artificial declarations`() {
val badTransformer = CirNodeTransformer { root ->
root.modules[CirName.create("bad-module")] = buildModuleNode(storageManager, 1).also { moduleNode ->
moduleNode.targetDeclarations[0] = CirModule.create(CirName.create("bad-module"))
}
}
val node = buildRootNode(storageManager, 0)
assertFailsWith<AssertionError> { badTransformer(node) }
}
@Test
fun `when attaching non-artificial declarations`() {
val goodTransformer = CirNodeTransformer { root ->
assertIs<Checked>(this, "Expected 'Checked' context")
root.modules[CirName.create("artificial-module")] = buildModuleNode(storageManager, 1).also { moduleNode ->
moduleNode.targetDeclarations[0] = CirModule.create(CirName.create("artificial-module")).markedArtificial()
}
}
val node = buildRootNode(storageManager, 0)
goodTransformer(node)
assertEquals(CirName.create("artificial-module"), node.modules.values.single().targetDeclarations.single()?.name)
}
}