[ObjCExport] KtObjCExportHeaderGenerator: Separate 'requiresClassForwardDeclaration' from ObjCType.classId

There are two different objectives associated with this newly
introduced extras:

- originClassId: Used to track where a type came from; Will
ensure that the translation queue will be populated to also
translate potential dependency classes

- requiresForwardDeclaration: Used to track if a certain
type requires rendering as forward declaration

KT-65891
This commit is contained in:
Sebastian Sellmair
2024-02-20 11:38:18 +01:00
committed by Space Team
parent 85854a6b8d
commit a5baf42422
9 changed files with 175 additions and 63 deletions
@@ -11,8 +11,9 @@ sourceSets {
dependencies {
api(intellijCore())
api(project(":native:base"))
api(project(":core:compiler.common"))
api(project(":kotlin-tooling-core"))
api(project(":native:base"))
testApi(libs.junit.jupiter.api)
testApi(libs.junit.jupiter.engine)
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.analysis.api.types.KtClassErrorType
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
import org.jetbrains.kotlin.objcexport.extras.withRequiresForwardDeclaration
/**
* Traverses stubs and returns true if [objCErrorType] is used as a return, parameter or property type
@@ -44,5 +45,4 @@ internal val errorInterface
superClassGenerics = emptyList()
)
internal val objCErrorType = ObjCClassType(errorClassName, classId = null)
internal val errorForwardClass = ObjCClassForwardDeclaration(errorClassName)
internal val objCErrorType = ObjCClassType(errorClassName).withRequiresForwardDeclaration()
@@ -6,6 +6,8 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClassType
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
import org.jetbrains.kotlin.objcexport.extras.withOriginClassId
import org.jetbrains.kotlin.objcexport.extras.withRequiresForwardDeclaration
/**
* If object class has companion object it needs to have property which returns this companion.
@@ -13,18 +15,20 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.buildCompanionProperty(): ObjCProperty {
val companion = this.getStaticMemberScope().getClassifierSymbols().toList()
.firstOrNull { (it as? KtClassOrObjectSymbol)?.isCompanion == true }
val typeName = (companion as KtClassOrObjectSymbol).getObjCClassOrProtocolName()
val propertyName = ObjCPropertyNames.companionObjectPropertyName
return ObjCProperty(
propertyName,
null,
null,
ObjCClassType(typeName.objCName, classId = companion.classIdIfNonLocal),
listOf("class", "readonly"),
name = propertyName,
comment = null,
origin = null,
type = ObjCClassType(typeName.objCName)
.withRequiresForwardDeclaration()
.withOriginClassId(companion.classIdIfNonLocal),
propertyAttributes = listOf("class", "readonly"),
getterName = propertyName,
declarationAttributes = listOf(swiftNameAttribute(propertyName))
)
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2024 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.objcexport.extras
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCNullableReferenceType
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCReferenceType
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.tooling.core.extrasKeyOf
private val originClassIdKey = extrasKeyOf<ClassId>()
/**
* Tracks the Kotlin origin associated with [this] [ObjCType].
* Providing the [originClassId] can signal a header generation that the class associated with the [ClassId] should
* also be translated for the header.
*/
internal val ObjCType.originClassId: ClassId?
get() {
extras[originClassIdKey]?.let { return it }
if (this is ObjCNullableReferenceType) {
return this.nonNullType.extras[originClassIdKey]?.let { return it }
}
return null
}
/**
* See [originClassId]
*/
internal fun <T : ObjCReferenceType> T.withOriginClassId(classId: ClassId?): T = also { type ->
if (classId != null) type.extras[originClassIdKey] = classId
else type.extras.remove(originClassIdKey)
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2024 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.objcexport.extras
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCReferenceType
import org.jetbrains.kotlin.tooling.core.extrasKeyOf
import org.jetbrains.kotlin.tooling.core.readWriteProperty
private val requiresForwardDeclarationKey = extrasKeyOf<Boolean>("isForwardDeclaration")
/**
* Marks a type such that the generated header renders a forward declaration to this type when used.
* - Default value: `false`.
* - Example: All types used in function and method signature are expected to render forward declarations
*/
internal val ObjCReferenceType.requiresForwardDeclaration by requiresForwardDeclarationKey.readWriteProperty.notNull(false)
/**
* ⚠️ Marks [this] [ObjCReferenceType] as 'requires forward declaration' and returns the same instance.
* This method shall be used during the construction of a new type.
* @see ObjCReferenceType.requiresForwardDeclaration
*/
internal fun <T : ObjCReferenceType> T.withRequiresForwardDeclaration(): T = also { type ->
type.extras[requiresForwardDeclarationKey] = true
}
@@ -12,8 +12,9 @@ import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.objcexport.KtObjCExportHeaderGenerator.QueueElement
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
import org.jetbrains.kotlin.objcexport.extras.originClassId
import org.jetbrains.kotlin.objcexport.extras.requiresForwardDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.addIfNotNull
context(KtAnalysisSession, KtObjCExportSession)
@@ -52,9 +53,19 @@ private class KtObjCExportHeaderGenerator {
private val objCStubsByClassId = hashMapOf<ClassId, ObjCClass?>()
/**
* The mutable aggregate of all entities that shall later be rendered as forward declarations
* An index of already translated classes (by ObjC name)
*/
private val objCForwardDeclarations = mutableSetOf<ClassId>()
private val objCStubsByClassName = hashMapOf<String, ObjCClass>()
/**
* The mutable aggregate of all protocol names that shall later be rendered as forward declarations
*/
private val objCProtocolForwardDeclarations = mutableSetOf<String>()
/**
* The mutable aggregate of all class names that shall later be rendered as forward declarations
*/
private val objCClassForwardDeclarations = mutableSetOf<String>()
/**
* See [symbolDeque]:
@@ -98,18 +109,25 @@ private class KtObjCExportHeaderGenerator {
context(KtAnalysisSession, KtObjCExportSession)
private fun translateFileSymbol(symbol: KtFileSymbol) {
val objCFacades = listOfNotNull(symbol.getExtensionsFacade(), symbol.getTopLevelFacade()).ifEmpty { return }
objCStubs += objCFacades
objCFacades.forEach { enqueueDependencyClasses(it) }
symbol.getExtensionsFacade()?.let { extensionFacade ->
objCStubs += extensionFacade
enqueueDependencyClasses(extensionFacade)
objCClassForwardDeclarations += extensionFacade.name
}
symbol.getTopLevelFacade()?.let { topLevelFacade ->
objCStubs += topLevelFacade
enqueueDependencyClasses(topLevelFacade)
}
}
context(KtAnalysisSession, KtObjCExportSession)
private fun translateClassOrObjectSymbol(symbol: KtClassOrObjectSymbol) {
private fun translateClassOrObjectSymbol(symbol: KtClassOrObjectSymbol): ObjCClass? {
/* No classId, no stubs ¯\_(ツ)_/¯ */
val classId = symbol.classIdIfNonLocal ?: return
val classId = symbol.classIdIfNonLocal ?: return null
/* Already processed this class, therefore nothing to do! */
if (classId in objCStubsByClassId) return
if (classId in objCStubsByClassId) return objCStubsByClassId[classId]
/**
* Translate: Note: Even if the result was 'null', the classId will still be marked as 'handled' by adding it
@@ -117,7 +135,7 @@ private class KtObjCExportHeaderGenerator {
*/
val objCClass = symbol.translateToObjCExportStub()
objCStubsByClassId[classId] = objCClass
objCClass ?: return
objCClass ?: return null
/*
To replicate the translation (and result stub order) of the K1 implementation:
@@ -125,19 +143,24 @@ private class KtObjCExportHeaderGenerator {
2) Super interface / superclass symbol export stubs (result of translation) have to be present in the stubs list before the
original stub
*/
val superInterfaceOrClassSymbols = buildList {
addAll(symbol.getDeclaredSuperInterfaceSymbols())
addIfNotNull(symbol.getSuperClassSymbolNotAny())
symbol.getDeclaredSuperInterfaceSymbols().forEach { superInterfaceSymbol ->
translateClassOrObjectSymbol(superInterfaceSymbol)?.let {
objCProtocolForwardDeclarations += it.name
}
}
superInterfaceOrClassSymbols.forEach { superInterfaceOrClassSymbol ->
translateClassOrObjectSymbol(superInterfaceOrClassSymbol)
symbol.getSuperClassSymbolNotAny()?.let { superClassSymbol ->
translateClassOrObjectSymbol(superClassSymbol)?.let {
objCClassForwardDeclarations += it.name
}
}
/* Note: It is important to add *this* stub to the result list only after translating/processing the superclass symbols */
objCStubs += objCClass
objCForwardDeclarations += superInterfaceOrClassSymbols.mapNotNull { it.classIdIfNonLocal }
objCStubsByClassName[objCClass.name] = objCClass
enqueueDependencyClasses(objCClass)
return objCClass
}
/**
@@ -154,32 +177,54 @@ private class KtObjCExportHeaderGenerator {
* and `Array` has to be registered as forward declaration.
*/
private fun enqueueDependencyClasses(stub: ObjCExportStub) {
symbolDeque += stub.closureSequence().mapNotNull { child ->
symbolDeque += stub.closureSequence()
.mapNotNull { child ->
when (child) {
is ObjCMethod -> child.returnType
is ObjCParameter -> child.type
is ObjCProperty -> child.type
is ObjCTopLevel -> null
}
}.flatMap { type ->
}
.flatMap { type ->
if (type is ObjCClassType) type.typeArguments + type
else listOf(type)
}.mapNotNull { if (it is ObjCReferenceType) it.classId else null }.onEach { objCForwardDeclarations += it }
.map { QueueElement.Class(it) }.toList()
}
.filterIsInstance<ObjCReferenceType>()
.onEach { type ->
if (!type.requiresForwardDeclaration) return@onEach
if (type is ObjCClassType) objCClassForwardDeclarations += type.className
if (type is ObjCProtocolType) objCProtocolForwardDeclarations += type.protocolName
}
.mapNotNull { it.originClassId }
.map(QueueElement::Class).toList()
}
/**
* [objCClassForwardDeclarations] are recorded by their respective class name:
* This method will resolve the objc interface that was translated, which is associated with the [className] and
* build the respective [ObjCClassForwardDeclaration] from it.
*
* If no such class was explicitly translated a simple [ObjCClassForwardDeclaration] will be emitted that does not
* carry any generics.
*/
private fun resolveObjCClassForwardDeclaration(className: String): ObjCClassForwardDeclaration {
objCStubsByClassName[className]
.let { it as? ObjCInterface }
?.let { objCClass -> return ObjCClassForwardDeclaration(objCClass.name, objCClass.generics) }
return ObjCClassForwardDeclaration(className)
}
context(KtAnalysisSession, KtObjCExportSession)
fun buildObjCHeader(): ObjCHeader {
val hasErrorTypes = objCStubs.hasErrorTypes()
val resolvedObjCForwardDeclarations = objCForwardDeclarations.mapNotNull { classId -> objCStubsByClassId[classId] }.asSequence()
val protocolForwardDeclarations = objCProtocolForwardDeclarations.toSet()
val protocolForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCProtocol>().map { it.name }.toSet()
val classForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCInterface>()
.map { stub -> ObjCClassForwardDeclaration(stub.name, stub.generics) }
.plus(listOfNotNull(errorForwardClass.takeIf { hasErrorTypes }))
.plus(objCStubs.filterIsInstance<ObjCInterface>().filter { it.isExtensionsFacade }.map { ObjCClassForwardDeclaration(it.name) })
val classForwardDeclarations = objCClassForwardDeclarations
.map { className -> resolveObjCClassForwardDeclaration(className) }
.toSet()
val stubs = (if (configuration.generateBaseDeclarationStubs) objCBaseDeclarations() else emptyList()).plus(objCStubs)
@@ -8,6 +8,8 @@ import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
import org.jetbrains.kotlin.objcexport.extras.withOriginClassId
import org.jetbrains.kotlin.objcexport.extras.withRequiresForwardDeclaration
context(KtAnalysisSession, KtObjCExportSession)
fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
@@ -83,11 +85,8 @@ private fun KtClassOrObjectSymbol.getDefaultMembers(): List<ObjCExportStub> {
* See also: [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceType]
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType(
getObjCClassOrProtocolName().objCName,
emptyList(),
classIdIfNonLocal!!
)
private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType(getObjCClassOrProtocolName().objCName, emptyList(),)
.withRequiresForwardDeclaration().withOriginClassId(classIdIfNonLocal)
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getObjectInstanceSelector]
@@ -19,6 +19,8 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.getInlineTargetTypeOrNul
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isError
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isObjCObjectType
import org.jetbrains.kotlin.objcexport.analysisApiUtils.objCErrorType
import org.jetbrains.kotlin.objcexport.extras.withOriginClassId
import org.jetbrains.kotlin.objcexport.extras.withRequiresForwardDeclaration
/**
@@ -107,10 +109,10 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
// TODO NOW: create type translation test
return if (classSymbol?.classKind == KtClassKind.INTERFACE) {
ObjCProtocolType(fullyExpandedType.objCTypeName, classId)
ObjCProtocolType(fullyExpandedType.objCTypeName)
} else {
ObjCClassType(fullyExpandedType.objCTypeName, translateTypeArgumentsToObjC(), classId)
}
ObjCClassType(fullyExpandedType.objCTypeName, translateTypeArgumentsToObjC())
}.withRequiresForwardDeclaration().withOriginClassId(classId)
}
if (fullyExpandedType is KtTypeParameterType) {
@@ -6,10 +6,14 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
import org.jetbrains.kotlin.types.Variance
sealed class ObjCType {
sealed class ObjCType : HasMutableExtras {
override val extras: MutableExtras = mutableExtrasOf()
final override fun toString(): String = this.render()
abstract fun render(attrsAndName: String): String
@@ -26,20 +30,13 @@ data class ObjCRawType(
override fun render(attrsAndName: String): String = rawText.withAttrsAndName(attrsAndName)
}
sealed class ObjCReferenceType : ObjCType() {
@InternalKotlinNativeApi
open val classId: ClassId? = null
}
sealed class ObjCReferenceType : ObjCType()
sealed class ObjCNonNullReferenceType : ObjCReferenceType()
data class ObjCNullableReferenceType(
val nonNullType: ObjCNonNullReferenceType,
val isNullableResult: Boolean = false,
) : ObjCReferenceType() {
override val classId: ClassId? get() = nonNullType.classId
override fun render(attrsAndName: String): String {
val attribute = if (isNullableResult) objcNullableResultAttribute else objcNullableAttribute
return nonNullType.render(" $attribute".withAttrsAndName(attrsAndName))
@@ -49,7 +46,6 @@ data class ObjCNullableReferenceType(
data class ObjCClassType(
val className: String,
val typeArguments: List<ObjCNonNullReferenceType> = emptyList(),
override val classId: ClassId? = null,
) : ObjCNonNullReferenceType() {
@@ -80,7 +76,6 @@ data class ObjCGenericTypeParameterUsage(
data class ObjCProtocolType(
val protocolName: String,
override val classId: ClassId? = null,
) : ObjCNonNullReferenceType() {
override fun render(attrsAndName: String) = "id<$protocolName>".withAttrsAndName(attrsAndName)
}