diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformer.kt index 4f56ded1589..5cee3345143 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformer.kt @@ -10,8 +10,13 @@ import org.jetbrains.kotlin.commonizer.mergedtree.* import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.Composite.Companion.plus import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.ParentNode import org.jetbrains.kotlin.commonizer.mergedtree.CirNodeRelationship.PreferredNode +import org.jetbrains.kotlin.commonizer.transformer.ArtificialSupertypes.artificialSupertypes +import org.jetbrains.kotlin.commonizer.utils.CNAMES_STRUCTS_PACKAGE +import org.jetbrains.kotlin.commonizer.utils.OBJCNAMES_CLASSES_PACKAGE +import org.jetbrains.kotlin.commonizer.utils.OBJCNAMES_PROTOCOLS_PACKAGE import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.storage.StorageManager internal class InlineTypeAliasCirNodeTransformer( @@ -142,4 +147,35 @@ 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() } +).also { it.supertypes = artificialSupertypes() } + + +/** + * Analog to "KlibResolvedModuleDescriptorsFactoryImpl.createForwardDeclarationsModule" which also + * automatically assumes relevant supertypes for forward declarations based upon the package they are in. + */ +private object ArtificialSupertypes { + private fun createType(classId: String): CirClassType { + return CirClassType.createInterned( + classId = CirEntityId.create(classId), + outerType = null, visibility = Visibilities.Public, arguments = emptyList(), isMarkedNullable = false + ) + } + + private val cOpaqueType = listOf(createType("kotlinx/cinterop/COpaque")) + private val objcObjectBase = listOf(createType("kotlinx/cinterop/ObjCObjectBase")) + private val objcCObject = listOf(createType("kotlinx/cinterop/ObjCObject")) + + fun CirTypeAlias.artificialSupertypes(): List { + /* Not supported (yet). No real life examples known (yet), that would benefit */ + if (this.expandedType.isMarkedNullable) return emptyList() + + return when (underlyingType.classifierId.packageName) { + CNAMES_STRUCTS_PACKAGE -> cOpaqueType + OBJCNAMES_CLASSES_PACKAGE -> objcObjectBase + OBJCNAMES_PROTOCOLS_PACKAGE -> objcCObject + else -> emptyList() + } + } +} + diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/utils/names.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/utils/names.kt index 5cbfbffa259..6e65d187479 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/utils/names.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/utils/names.kt @@ -43,6 +43,12 @@ private val KOTLIN_NATIVE_SYNTHETIC_PACKAGES: List = ForwardDecl CirPackageName.create(packageFqName) } +internal val CNAMES_STRUCTS_PACKAGE = CirPackageName.create("cnames.structs") + +internal val OBJCNAMES_CLASSES_PACKAGE = CirPackageName.create("objcnames.classes") + +internal val OBJCNAMES_PROTOCOLS_PACKAGE = CirPackageName.create("objcnames.protocols") + private val CINTEROP_PACKAGE: CirPackageName = CirPackageName.create("kotlinx.cinterop") private val OBJC_INTEROP_CALLABLE_ANNOTATIONS: List = listOf( diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt index 2a1f5eb1edd..fa5501a704d 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt @@ -572,4 +572,58 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm """.trimIndent() ) } + + fun `test supertypes being retained`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget( + "a", """ + interface SuperInterface + class X: SuperInterface + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + interface SuperInterface + class B: SuperInterface + typealias X = B + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + expect interface SuperInterface + expect class X(): SuperInterface + """.trimIndent() + ) + } + + fun `test supertypes being retained from dependencies`() { + val result = commonize { + outputTarget("(a, b)") + + registerDependency("a", "b", "(a, b)") { + source("""interface SuperInterface""") + } + + simpleSingleSourceTarget( + "a", """ + class X: SuperInterface + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + class B: SuperInterface + typealias X = B + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + expect class X(): SuperInterface + """.trimIndent() + ) + } } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformerTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformerTest.kt new file mode 100644 index 00000000000..96462b830d8 --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/transformer/InlineTypeAliasCirNodeTransformerTest.kt @@ -0,0 +1,119 @@ +package org.jetbrains.kotlin.commonizer.transformer + +import org.jetbrains.kotlin.commonizer.cir.* +import org.jetbrains.kotlin.commonizer.mergedtree.* +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class InlineTypeAliasCirNodeTransformerTest { + + private val storageManager = LockBasedStorageManager("test") + + private val classifiers = CirKnownClassifiers( + commonizedNodes = CirCommonizedClassifierNodes.default(), + commonDependencies = CirProvidedClassifiers.EMPTY + ) + + @Test + fun `test artificial supertypes - regular package`() { + val root = setup(CirEntityId.create("regular/package/__X")) + InlineTypeAliasCirNodeTransformer(storageManager, classifiers).invoke(root) + val artificialXClass = root.assertInlinedXClass + assertEquals( + emptyList(), artificialXClass.supertypes, + "Expected no artificial supertype for artificial class X" + ) + } + + @Test + fun `test artificial supertypes - cnames structs`() { + val root = setup(CirEntityId.create("cnames/structs/__X")) + InlineTypeAliasCirNodeTransformer(storageManager, classifiers).invoke(root) + val artificialXClass = root.assertInlinedXClass + + assertEquals( + setOf(CirEntityId.create("kotlinx/cinterop/COpaque")), + artificialXClass.supertypes.map { it as CirClassType }.map { it.classifierId }.toSet(), + "Expected 'COpaque' supertype being attached automatically" + ) + } + + @Test + fun `test artificial supertypes - objcnames classes`() { + val root = setup(CirEntityId.create("objcnames/classes/__X")) + InlineTypeAliasCirNodeTransformer(storageManager, classifiers).invoke(root) + val artificialXClass = root.assertInlinedXClass + + assertEquals( + setOf(CirEntityId.create("kotlinx/cinterop/ObjCObjectBase")), + artificialXClass.supertypes.map { it as CirClassType }.map { it.classifierId }.toSet(), + "Expected 'ObjCObjectBase' supertype being attached automatically" + ) + } + + @Test + fun `test artificial supertypes - objcnames protocols`() { + val root = setup(CirEntityId.create("objcnames/protocols/__X")) + InlineTypeAliasCirNodeTransformer(storageManager, classifiers).invoke(root) + val artificialXClass = root.assertInlinedXClass + + assertEquals( + setOf(CirEntityId.create("kotlinx/cinterop/ObjCObject")), + artificialXClass.supertypes.map { it as CirClassType }.map { it.classifierId }.toSet(), + "Expected 'ObjCObject' supertype being attached automatically" + ) + } + + private fun setup(typeAliasPointingTo: CirEntityId): CirRootNode { + val root = buildRootNode(storageManager, 1) + root.modules[CirName.create("test-module")] = buildModuleNode(storageManager, 1).apply { + packages[CirPackageName.create("under.test")] = buildPackageNode(storageManager, 2).apply { + + typeAliases[CirName.create("X")] = buildTypeAliasNode( + storageManager, 2, classifiers, CirEntityId.create("under/test/X") + ).apply { + val underlyingType = CirClassType.createInterned( + classId = typeAliasPointingTo, + outerType = null, visibility = Visibilities.Public, + arguments = emptyList(), isMarkedNullable = false + ) + targetDeclarations[0] = CirTypeAlias.create( + annotations = emptyList(), + name = CirName.create("X"), + typeParameters = emptyList(), + visibility = Visibilities.Public, + underlyingType = underlyingType, + expandedType = underlyingType + ) + } + + classes[CirName.create("X")] = buildClassNode( + storageManager, 2, classifiers, null, CirEntityId.create("under/test/X") + ).apply { + targetDeclarations[1] = CirClass.create( + name = CirName.create("X"), typeParameters = emptyList(), visibility = Visibilities.Public, + companion = null, isCompanion = false, isData = false, isExternal = false, + isInner = false, isValue = false, kind = ClassKind.CLASS, + modality = Modality.FINAL, annotations = emptyList(), + ) + } + } + } + + return root + } + + private val CirRootNode.xClassNode: CirClassNode + get() = modules.getValue(CirName.create("test-module")) + .packages.getValue(CirPackageName.create("under.test")) + .classes.getValue(CirName.create("X")) + + private val CirRootNode.assertInlinedXClass: CirClass + get() = assertNotNull(xClassNode.targetDeclarations[0], "Missing inlined class 'X' at index 0") + +} \ No newline at end of file