[Commonizer] InlineTypeAliasCirNodeTransformer: add artificial supertypes

When inlining any TypeAlias pointing towards forward declarations,
the inlined class will respect implicit supertypes.

This logic is analog to
KlibResolvedModuleDescriptorsFactoryImpl.createForwardDeclarationsModule

^KT-47430 In Progress
This commit is contained in:
sebastian.sellmair
2021-08-10 12:01:48 +02:00
committed by Space
parent 0c35a3b699
commit cdfe8d60a4
4 changed files with 216 additions and 1 deletions

View File

@@ -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<CirType> {
/* 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()
}
}
}

View File

@@ -43,6 +43,12 @@ private val KOTLIN_NATIVE_SYNTHETIC_PACKAGES: List<CirPackageName> = 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<CirName> = listOf(

View File

@@ -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()
)
}
}

View File

@@ -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")
}