From a5baf42422231acedc5a29d8fe23376bdfd0c32e Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Tue, 20 Feb 2024 11:38:18 +0100 Subject: [PATCH] [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 --- .../build.gradle.kts | 3 +- .../objcexport/analysisApiUtils/errors.kt | 4 +- .../objcexport/buildCompanionProperty.kt | 16 ++- .../kotlin/objcexport/extras/originClassId.kt | 38 ++++++ .../extras/requiresForwardDeclaration.kt | 28 +++++ .../objcexport/translateToObjCHeader.kt | 113 ++++++++++++------ .../objcexport/translateToObjCObject.kt | 9 +- .../kotlin/objcexport/translateToObjCType.kt | 8 +- .../backend/konan/objcexport/objcTypes.kt | 19 ++- 9 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/originClassId.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/requiresForwardDeclaration.kt diff --git a/native/objcexport-header-generator/build.gradle.kts b/native/objcexport-header-generator/build.gradle.kts index c08ee55e751..d56156e195f 100644 --- a/native/objcexport-header-generator/build.gradle.kts +++ b/native/objcexport-header-generator/build.gradle.kts @@ -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) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt index 69788cfb896..e62021b80d1 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt @@ -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) \ No newline at end of file +internal val objCErrorType = ObjCClassType(errorClassName).withRequiresForwardDeclaration() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/buildCompanionProperty.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/buildCompanionProperty.kt index 37b3861a64e..8e35a39d87d 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/buildCompanionProperty.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/buildCompanionProperty.kt @@ -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)) ) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/originClassId.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/originClassId.kt new file mode 100644 index 00000000000..c78771efc6a --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/originClassId.kt @@ -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() + +/** + * 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.withOriginClassId(classId: ClassId?): T = also { type -> + if (classId != null) type.extras[originClassIdKey] = classId + else type.extras.remove(originClassIdKey) +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/requiresForwardDeclaration.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/requiresForwardDeclaration.kt new file mode 100644 index 00000000000..8daea935e3c --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/extras/requiresForwardDeclaration.kt @@ -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("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.withRequiresForwardDeclaration(): T = also { type -> + type.extras[requiresForwardDeclarationKey] = true +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt index 758edcc5539..bb3beaa7fc6 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt @@ -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() /** - * 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() + private val objCStubsByClassName = hashMapOf() + + /** + * The mutable aggregate of all protocol names that shall later be rendered as forward declarations + */ + private val objCProtocolForwardDeclarations = mutableSetOf() + + /** + * The mutable aggregate of all class names that shall later be rendered as forward declarations + */ + private val objCClassForwardDeclarations = mutableSetOf() /** * 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 -> - when (child) { - is ObjCMethod -> child.returnType - is ObjCParameter -> child.type - is ObjCProperty -> child.type - is ObjCTopLevel -> null + symbolDeque += stub.closureSequence() + .mapNotNull { child -> + when (child) { + is ObjCMethod -> child.returnType + is ObjCParameter -> child.type + is ObjCProperty -> child.type + is ObjCTopLevel -> null + } } - }.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() + .flatMap { type -> + if (type is ObjCClassType) type.typeArguments + type + else listOf(type) + } + .filterIsInstance() + .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().map { it.name }.toSet() - - val classForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance() - .map { stub -> ObjCClassForwardDeclaration(stub.name, stub.generics) } - .plus(listOfNotNull(errorForwardClass.takeIf { hasErrorTypes })) - .plus(objCStubs.filterIsInstance().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) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt index 0a0f62e99cc..9fac38f3e2a 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt @@ -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 { * 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] diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt index 8a0b6a07004..5f37706669e 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt @@ -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) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt index 49574216cd6..df749dfa7e9 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt @@ -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 = 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) }