diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt new file mode 100644 index 00000000000..335f46f6631 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt @@ -0,0 +1,14 @@ +package org.jetbrains.kotlin.objcexport + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer] + */ +internal object ObjCPropertyNames { + @Suppress("unused") + const val kotlinThrowableAsErrorMethodName: String = "asError" + + const val objectPropertyName: String = "shared" + + @Suppress("unused") + const val companionObjectPropertyName: String = "companion" +} \ No newline at end of file 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 new file mode 100644 index 00000000000..c1f94957c19 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt @@ -0,0 +1,48 @@ +package org.jetbrains.kotlin.objcexport.analysisApiUtils + +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 + +/** + * Traverses stubs and returns true if [objCErrorType] is used as a return, parameter or property type + */ +internal fun Iterable.hasErrorTypes(): Boolean { + return any { stub -> stub.hasErrorTypes() } +} + +internal fun ObjCExportStub.hasErrorTypes(): Boolean { + return when (val stub = this) { + is ObjCClass -> stub.members.hasErrorTypes() + is ObjCProperty -> stub.type == objCErrorType + is ObjCMethod -> { + if (stub.returnType == objCErrorType) true + else stub.parameters.any { parameter -> parameter.type == objCErrorType } + } + else -> false + } +} + +internal val KtType.isError + get() = this is KtClassErrorType + +internal const val errorClassName = "ERROR" + +context(KtObjCExportSession) +internal val errorInterface + get() = ObjCInterfaceImpl( + name = errorClassName, + comment = null, + origin = null, + attributes = emptyList(), + superProtocols = emptyList(), + members = emptyList(), + categoryName = null, + generics = emptyList(), + superClass = getDefaultSuperClassOrProtocolName().objCName, + superClassGenerics = emptyList() + ) + +internal val objCErrorType = ObjCClassType(errorClassName) +internal val errorForwardClass = ObjCClassForwardDeclaration(errorClassName) \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt index 0d0daade898..2978fb6a402 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.objcexport.analysisApiUtils import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.types.KtClassErrorType import org.jetbrains.kotlin.analysis.api.types.KtClassType import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier @@ -29,6 +30,10 @@ context(KtAnalysisSession) internal fun KtClassOrObjectSymbol.getSuperClassSymbolNotAny(): KtClassOrObjectSymbol? { return superTypes.firstNotNullOfOrNull find@{ superType -> if (superType.isAny) return@find null + if (superType.isError && superType is KtClassErrorType) { + //Header should have just a Base type in case unresolved super type + return@find null + } if (superType is KtClassType) { val classifier = superType.qualifiers.firstNotNullOfOrNull { qualifier -> (qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier)?.symbol diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt index 5f650893230..bf86e4ca706 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.* context(KtAnalysisSession) -internal fun KtClassOrObjectSymbol.members(): List { +internal fun KtClassOrObjectSymbol.getAllMembers(): List { return getMemberScope() .getAllSymbols() .sortedBy { sortMembers(it) } @@ -13,6 +13,37 @@ internal fun KtClassOrObjectSymbol.members(): List { .toList() } +/** + * Returns members explicitly defined in the symbol. All super methods are excluded. + * + * Also handles edge case with covariant method overwrite: + * + * ``` + * interface A { + * fun hello(): Any + * } + * + * interface B: A { + * override fun hello(): String + * } + * + * B.getBaseMembers().isEmpty() == true + * ``` + * + * More context is around [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isBaseMethod] + */ +context(KtAnalysisSession) +internal fun KtClassOrObjectSymbol.getDeclaredMembers(): List { + return getDeclaredMemberScope() + .getAllSymbols() + .sortedBy { sortMembers(it) } + .filterIsInstance() + .filter { member -> + member.getAllOverriddenSymbols().isEmpty() && member.isVisibleInObjC() + } + .toList() +} + /** * Temp workaround of [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorKt.makeMethodsOrderStable] */ diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt index 28e2ca461b2..1e5d8e4899e 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt @@ -6,10 +6,10 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllMembers import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC -import org.jetbrains.kotlin.objcexport.analysisApiUtils.members context(KtAnalysisSession, KtObjCExportSession) fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { @@ -27,7 +27,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { val comment: ObjCComment? = annotationsList.translateToObjCComment() val origin: ObjCExportStubOrigin = getObjCExportStubOrigin() val superProtocols: List = superProtocols() - val members: List = members().flatMap { it.translateToObjCExportStubs() } + val members: List = getAllMembers().flatMap { it.translateToObjCExportStubs() } val categoryName: String? = null val generics: List = emptyList() val superClassGenerics: List = emptyList() 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 bbabfbabc69..3e25a366063 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 @@ -7,30 +7,65 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.* -import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.CLASS -import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.INTERFACE -import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub -import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader -import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.* +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.objcexport.analysisApiUtils.errorForwardClass +import org.jetbrains.kotlin.objcexport.analysisApiUtils.errorInterface +import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasErrorTypes import org.jetbrains.kotlin.psi.KtFile - context(KtAnalysisSession, KtObjCExportSession) -fun translateToObjCHeader(files: List) : ObjCHeader { - val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() } +fun translateToObjCHeader(files: List): ObjCHeader { + val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() }.toMutableList() + val classForwardDeclarations = getClassForwardDeclarations(declarations).toMutableSet() + val protocolForwardDeclarations = getProtocolForwardDeclarations(declarations) + + if (declarations.hasErrorTypes()) { + declarations.add(errorInterface) + classForwardDeclarations.add(errorForwardClass) + } + return ObjCHeader( stubs = declarations, - classForwardDeclarations = emptySet(), - protocolForwardDeclarations = declarations - .filterIsInstance() - .flatMap { it.superProtocols } - .toSet(), + classForwardDeclarations = classForwardDeclarations, + protocolForwardDeclarations = protocolForwardDeclarations, additionalImports = emptyList(), - exportKDoc = configuration.exportKDoc ) } +/** + * Class which have static property must have forward declaration + * + * ``` + * @class Foo; + * + * @interface Foo + * @property (class) Foo + * @end + * ``` + */ +private fun getClassForwardDeclarations(declarations: List): Set { + return declarations + .filterIsInstance() + .filter { clazz -> + clazz.members + .filterIsInstance() + .any { property -> + val className = (property.type as? ObjCClassType)?.className == clazz.name + val static = property.propertyAttributes.contains("class") + className && static + } + }.map { clazz -> + ObjCClassForwardDeclaration(clazz.name) + }.toSet() +} + +private fun getProtocolForwardDeclarations(declarations: List) = declarations + .filterIsInstance() + .flatMap { it.superProtocols } + .toSet() + context(KtAnalysisSession, KtObjCExportSession) fun KtFile.translateToObjCExportStubs(): List { return this.getFileSymbol().translateToObjCExportStubs() @@ -49,6 +84,7 @@ internal fun KtSymbol.translateToObjCExportStubs(): List { this is KtFileSymbol -> translateToObjCExportStubs() this is KtClassOrObjectSymbol && classKind == INTERFACE -> listOfNotNull(translateToObjCProtocol()) this is KtClassOrObjectSymbol && classKind == CLASS -> listOfNotNull(translateToObjCClass()) + this is KtClassOrObjectSymbol && classKind == OBJECT -> listOfNotNull(translateToObjCObject()) this is KtConstructorSymbol -> translateToObjCConstructors() this is KtPropertySymbol -> listOfNotNull(translateToObjCProperty()) this is KtFunctionSymbol -> listOfNotNull(translateToObjCMethod()) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt index 608b61e129d..f66e8264006 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt @@ -134,7 +134,7 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri 1 -> "_" else -> "value" } - else -> symbol!!.getObjCName().name(true) + else -> symbol!!.name } MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler" @@ -208,7 +208,9 @@ fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String { 1 -> "" else -> "value" } - else -> typeParameterSymbol!!.getObjCName().name(false) + else -> { + typeParameterSymbol!!.name.toString() + } } MethodBridgeValueParameter.ErrorOutParameter -> "error" is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler" @@ -282,32 +284,35 @@ private fun String.mangleIfSpecialFamily(prefix: String): String { * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.startsWithWords] */ private fun String.startsWithWords(words: String) = this.startsWith(words) && - (this.length == words.length || !this[words.length].isLowerCase()) + (this.length == words.length || !this[words.length].isLowerCase()) +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated] + */ @InternalKotlinNativeApi fun MethodBridge.valueParametersAssociated( function: KtFunctionLikeSymbol, -): List> { +): List> { - val kotlinParameters = function.typeParameters.iterator() - if (!kotlinParameters.hasNext()) return emptyList() + val allParameters = function.valueParameters.iterator() + if (!allParameters.hasNext()) return emptyList() val skipFirstKotlinParameter = when (this.receiver) { MethodBridgeReceiver.Static -> false MethodBridgeReceiver.Factory, MethodBridgeReceiver.Instance -> true } - if (skipFirstKotlinParameter) { - kotlinParameters.next() + if (skipFirstKotlinParameter && allParameters.hasNext()) { + allParameters.next() } return this.valueParameters.map { when (it) { - is MethodBridgeValueParameter.Mapped -> it to kotlinParameters.next() + is MethodBridgeValueParameter.Mapped -> it to allParameters.next() is MethodBridgeValueParameter.SuspendCompletion, is MethodBridgeValueParameter.ErrorOutParameter, -> it to null } - }.also { assert(!kotlinParameters.hasNext()) } + } } @@ -329,7 +334,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue): if (!returnBridge.successMayBeZero) { check( successReturnType is ObjCNonNullReferenceType - || (successReturnType is ObjCPointerType && !successReturnType.nullable) + || (successReturnType is ObjCPointerType && !successReturnType.nullable) ) { "Unexpected return type: $successReturnType in $this" } 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 new file mode 100644 index 00000000000..72beaf28e02 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt @@ -0,0 +1,119 @@ +package org.jetbrains.kotlin.objcexport + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllMembers +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC + +context(KtAnalysisSession, KtObjCExportSession) +fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? { + require(classKind == KtClassKind.OBJECT) + if (!isVisibleInObjC()) return null + + val superClass = getSuperClassSymbolNotAny() + val kotlinAnyName = getDefaultSuperClassOrProtocolName() + val superName = if (superClass == null) kotlinAnyName else throw RuntimeException("Super class translation isn't implemented yet") + val enumKind = this.classKind == KtClassKind.ENUM_CLASS + val final = if (this is KtSymbolWithModality) this.modality == Modality.FINAL else false + val attributes = if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList() + + val name = getObjCClassOrProtocolName() + val comment: ObjCComment? = annotationsList.translateToObjCComment() + val origin: ObjCExportStubOrigin = getObjCExportStubOrigin() + val superProtocols: List = superProtocols() + val categoryName: String? = null + val generics: List = emptyList() + val superClassGenerics: List = emptyList() + val objectMembers = getDefaultMembers() + + getAllMembers().flatMap { it.translateToObjCExportStubs() }.forEach { + objectMembers.add(it) + } + + return ObjCInterfaceImpl( + name.objCName, + comment, + origin, + attributes, + superProtocols, + objectMembers, + categoryName, + generics, + superName.objCName, + superClassGenerics + ) +} + +context(KtAnalysisSession, KtObjCExportSession) +private fun KtClassOrObjectSymbol.getDefaultMembers(): MutableList { + + val result = mutableListOf() + val allocWithZoneParameter = ObjCParameter("zone", null, ObjCRawType("struct _NSZone *"), null) + + result.add( + ObjCMethod(null, null, false, ObjCInstanceType, listOf("alloc"), emptyList(), listOf("unavailable")) + ) + + result.add( + ObjCMethod(null, null, false, ObjCInstanceType, listOf("allocWithZone:"), listOf(allocWithZoneParameter), listOf("unavailable")) + ) + + result.add( + ObjCMethod( + null, + null, + false, + ObjCInstanceType, + listOf(getObjectInstanceSelector(this)), + emptyList(), + listOf(swiftNameAttribute("init()")) + ) + ) + + result.add( + ObjCProperty( + name = ObjCPropertyNames.objectPropertyName, + comment = null, + type = toPropertyType(), + propertyAttributes = listOf("class", "readonly"), + getterName = getObjectPropertySelector(this), + declarationAttributes = listOf(swiftNameAttribute(ObjCPropertyNames.objectPropertyName)), + origin = null + ) + ) + return result +} + +/** + * TODO: Temp implementation + * Use translateToObjCReferenceType() to make type + * See also: [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceType] + */ +private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType( + this.classIdIfNonLocal!!.shortClassName.asString(), + emptyList() +) + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getObjectInstanceSelector] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun getObjectInstanceSelector(objectSymbol: KtClassOrObjectSymbol): String { + return objectSymbol.getObjCClassOrProtocolName().objCName.lowercase() +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getObjectPropertySelector] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun getObjectPropertySelector(descriptor: KtClassOrObjectSymbol): String { + val collides = ObjCPropertyNames.objectPropertyName == getObjectInstanceSelector(descriptor) + return ObjCPropertyNames.objectPropertyName + (if (collides) "_" else "") +} + diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt index b6fe548fa2b..918c5d51415 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol import org.jetbrains.kotlin.backend.konan.cKeywords import org.jetbrains.kotlin.backend.konan.objcexport.* @@ -23,13 +24,13 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me val usedNames = mutableSetOf() - valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtTypeParameterSymbol?) -> + valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtValueParameterSymbol?) -> val candidateName: String = when (bridge) { is MethodBridgeValueParameter.Mapped -> { if (parameter == null) throw IllegalStateException("Parameter shouldn't be null") when { this is KtPropertySetterSymbol -> "value" - else -> parameter.getObjCName().name(false) + else -> parameter.name.toString() } } MethodBridgeValueParameter.ErrorOutParameter -> "error" @@ -40,7 +41,8 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me usedNames += uniqueName val type = when (bridge) { - is MethodBridgeValueParameter.Mapped -> TODO("Fetch KtType from KtTypeParameterSymbol: $parameter") + is MethodBridgeValueParameter.Mapped -> + parameter!!.returnType.translateToObjCReferenceType() MethodBridgeValueParameter.ErrorOutParameter -> ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true) @@ -48,9 +50,9 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me val resultType = if (bridge.useUnitCompletion) { null } else { - when (val it = this.returnType.translateToObjCReferenceType()) { - is ObjCNonNullReferenceType -> ObjCNullableReferenceType(it, isNullableResult = false) - is ObjCNullableReferenceType -> ObjCNullableReferenceType(it.nonNullType, isNullableResult = true) + when (val type = this.returnType.translateToObjCReferenceType()) { + is ObjCNonNullReferenceType -> ObjCNullableReferenceType(type, isNullableResult = false) + is ObjCNullableReferenceType -> ObjCNullableReferenceType(type.nonNullType, isNullableResult = true) } } ObjCBlockPointerType( diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt index b00fc6fbe72..1b01430f4ea 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt @@ -14,8 +14,8 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCComment import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocolImpl import org.jetbrains.kotlin.backend.konan.objcexport.toNameAttributes +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredMembers import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC -import org.jetbrains.kotlin.objcexport.analysisApiUtils.members context(KtAnalysisSession, KtObjCExportSession) fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { @@ -26,7 +26,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { // TODO: Check error type! val name = getObjCClassOrProtocolName() - val members = members().flatMap { it.translateToObjCExportStubs() } + val members = getDeclaredMembers().flatMap { it.translateToObjCExportStubs() } val comment: ObjCComment? = annotationsList.translateToObjCComment() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt index 1c52f4baae9..25e43d496cc 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt @@ -5,6 +5,9 @@ import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isError +import org.jetbrains.kotlin.objcexport.analysisApiUtils.objCErrorType /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceType] @@ -20,30 +23,41 @@ internal fun KtType.translateToObjCReferenceType(): ObjCReferenceType { context(KtAnalysisSession, KtObjCExportSession) private fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenceType { - /** - * Simplified version of [org.jetbrains.kotlin.backend.konan.objcexport.CustomTypeMapper] - */ - val typesMap = mutableMapOf().apply { - this[ClassId.topLevel(StandardNames.FqNames.list)] = "NSArray" - this[ClassId.topLevel(StandardNames.FqNames.mutableList)] = "NSMutableArray" - this[ClassId.topLevel(StandardNames.FqNames.set)] = "NSSet" - this[ClassId.topLevel(StandardNames.FqNames.mutableSet)] = "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName - this[ClassId.topLevel(StandardNames.FqNames.map)] = "NSDictionary" - this[ClassId.topLevel(StandardNames.FqNames.mutableMap)] = "MutableDictionary".getObjCKotlinStdlibClassOrProtocolName().objCName - this[ClassId.topLevel(StandardNames.FqNames.string.toSafe())] = "NSString" - } + val classId = this.expandedClassSymbol?.classIdIfNonLocal + val isInlined = false //TODO: replace when KT-65176 is implemented + val isHidden = classId in hiddenTypes - NSNumberKind.entries.forEach { - val classId = it.mappedKotlinClassId - if (classId != null) { - typesMap[classId] = classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName + return if (isError) { + objCErrorType + } else if (isAny || isHidden || isInlined) { + ObjCIdType + } else { + /** + * Simplified version of [org.jetbrains.kotlin.backend.konan.objcexport.CustomTypeMapper] + */ + val typesMap = mutableMapOf().apply { + this[ClassId.topLevel(StandardNames.FqNames.list)] = "NSArray" + this[ClassId.topLevel(StandardNames.FqNames.mutableList)] = "NSMutableArray" + this[ClassId.topLevel(StandardNames.FqNames.set)] = "NSSet" + this[ClassId.topLevel(StandardNames.FqNames.mutableSet)] = "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName + this[ClassId.topLevel(StandardNames.FqNames.map)] = "NSDictionary" + this[ClassId.topLevel(StandardNames.FqNames.mutableMap)] = "MutableDictionary".getObjCKotlinStdlibClassOrProtocolName().objCName + this[ClassId.topLevel(StandardNames.FqNames.string.toSafe())] = "NSString" } + + NSNumberKind.entries.forEach { number -> + val numberClassId = number.mappedKotlinClassId + if (numberClassId != null) { + typesMap[numberClassId] = numberClassId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName + } + } + + val typeName = typesMap[classId] + ?: classId!!.shortClassName.asString() + .getObjCKotlinStdlibClassOrProtocolName().objCName //throw IllegalStateException("Unsupported mapping type for $this") + + ObjCClassType(typeName) } - - val typeName = typesMap[this.expandedClassSymbol?.classIdIfNonLocal] - ?: throw IllegalStateException("Unsupported mapping type for $this") - - return ObjCClassType(typeName) } private fun ObjCNonNullReferenceType.withNullabilityOf(kotlinType: KtType): ObjCReferenceType { @@ -53,3 +67,21 @@ private fun ObjCNonNullReferenceType.withNullabilityOf(kotlinType: KtType): ObjC this } } + +/** + * Types to be "hidden" during mapping, i.e. represented as `id`. + * + * Currently contains super types of classes handled by custom type mappers. + * Note: can be generated programmatically, but requires stdlib in this case. + */ +private val hiddenTypes: Set = listOf( + "kotlin.Any", + "kotlin.CharSequence", + "kotlin.Comparable", + "kotlin.Function", + "kotlin.Number", + "kotlin.collections.Collection", + "kotlin.collections.Iterable", + "kotlin.collections.MutableCollection", + "kotlin.collections.MutableIterable" +).map { ClassId.topLevel(FqName(it)) }.toSet() \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt index 469bafc315f..7b9ea47acf7 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt @@ -6,7 +6,8 @@ package org.jetbrains.kotlin.objcexport.testUtils import org.jetbrains.kotlin.analysis.api.analyze -import org.jetbrains.kotlin.backend.konan.tests.ObjCExportHeaderGeneratorTest.HeaderGenerator +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader +import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator import org.jetbrains.kotlin.objcexport.KtObjCExportConfiguration import org.jetbrains.kotlin.objcexport.KtObjCExportSession import org.jetbrains.kotlin.objcexport.translateToObjCHeader @@ -27,12 +28,12 @@ class AnalysisApiHeaderGeneratorExtension : ParameterResolver { } object AnalysisApiHeaderGenerator : HeaderGenerator { - override fun generateHeaders(root: File): String { + override fun generateHeaders(root: File, configuration: HeaderGenerator.Configuration): ObjCHeader { val session = createStandaloneAnalysisApiSession(root.listFiles().orEmpty().filter { it.extension == "kt" }) val (module, files) = session.modulesWithFiles.entries.single() return analyze(module) { - KtObjCExportSession(KtObjCExportConfiguration()) { - translateToObjCHeader(files.map { it as KtFile }).toString() + KtObjCExportSession(KtObjCExportConfiguration(frameworkName = configuration.frameworkName)) { + translateToObjCHeader(files.map { it as KtFile }) } } } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/HasErrorTypesTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/HasErrorTypesTest.kt new file mode 100644 index 00000000000..b38beae1cb1 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/HasErrorTypesTest.kt @@ -0,0 +1,104 @@ +package org.jetbrains.kotlin.objcexport.tests + +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub +import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator +import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasErrorTypes +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import kotlin.io.path.writeText +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class HasErrorTypesTest( + private val headerGenerator: HeaderGenerator, +) { + + @TempDir + private lateinit var tempDir: Path + + @Test + fun `test - no errors`() { + val stubs = stubs( + """ + fun foo() = Unit + class Foo { + val myProperty: Int = 42 + fun myFunction(): Int = 42 + } + """.trimIndent() + ) + + assertFalse(stubs.hasErrorTypes()) + } + + @Test + fun `test - property return type error`() { + val stubs = stubs( + """ + val foo: Unresolved get() = error("stub") + """.trimIndent() + ) + + assertTrue(stubs.hasErrorTypes()) + } + + @Test + fun `test - function return type error`() { + val stubs = stubs( + """ + fun foo(): Unresolved = error("stub") + """.trimIndent() + ) + + assertTrue(stubs.hasErrorTypes()) + } + + @Test + fun `test - nested member function return type error`() { + val stubs = stubs( + """ + class Foo { + fun foo(): Unresolved = error("stub") + } + """.trimIndent() + ) + + assertTrue(stubs.hasErrorTypes()) + } + + @Test + fun `test - nested member property return type error`() { + val stubs = stubs( + """ + class Foo { + val foo: Unresolved get() = error("stub") + } + """.trimIndent() + ) + + assertTrue(stubs.hasErrorTypes()) + } + + @Test + fun `test - nested error class property`() { + val stubs = stubs( + """ + class A { + class B { + val e = error("error") + } + } + """.trimIndent() + ) + + assertTrue(stubs.hasErrorTypes()) + } + + private fun stubs(@Language("kotlin") sourceCode: String): List { + val sourceFile = tempDir.resolve("sources.kt") + sourceFile.writeText(sourceCode) + return headerGenerator.generateHeaders(tempDir.toFile()).stubs + } +} diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 6aba826d082..3a97bff1aa2 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -36,13 +36,15 @@ abstract class ObjCExportHeaderGenerator @InternalKotlinNativeApi constructor( open val shouldExportKDoc = false - fun build(): List = ObjCHeader( + @InternalKotlinNativeApi + fun buildHeader(): ObjCHeader = ObjCHeader( stubs = stubs, classForwardDeclarations = classForwardDeclarations, protocolForwardDeclarations = protocolForwardDeclarations, additionalImports = getAdditionalImports(), - exportKDoc = shouldExportKDoc - ).lines + ) + + fun build(): List = buildHeader().render(shouldExportKDoc) @InternalKotlinNativeApi fun buildInterface(): ObjCExportedInterface { diff --git a/native/objcexport-header-generator/impl/k1/test/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/test/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt index af92aa26ab7..8f04a626106 100644 --- a/native/objcexport-header-generator/impl/k1/test/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/test/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport import org.jetbrains.kotlin.backend.konan.objcexport.* -import org.jetbrains.kotlin.backend.konan.tests.ObjCExportHeaderGeneratorTest import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -28,7 +27,7 @@ class Fe10HeaderGeneratorExtension : ParameterResolver, AfterEachCallback { } override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { - return parameterContext.parameter.type == ObjCExportHeaderGeneratorTest.HeaderGenerator::class.java + return parameterContext.parameter.type == HeaderGenerator::class.java } override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { @@ -43,15 +42,16 @@ class Fe10HeaderGeneratorExtension : ParameterResolver, AfterEachCallback { } } -private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : - ObjCExportHeaderGeneratorTest.HeaderGenerator { - override fun generateHeaders(root: File): String { - val headerGenerator = createObjCExportHeaderGenerator(disposable, root) +private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : HeaderGenerator { + override fun generateHeaders(root: File, configuration: HeaderGenerator.Configuration): ObjCHeader { + val headerGenerator = createObjCExportHeaderGenerator(disposable, root, configuration) headerGenerator.translateModuleDeclarations() - return headerGenerator.build().joinToString(System.lineSeparator()) + return headerGenerator.buildHeader() } - private fun createObjCExportHeaderGenerator(disposable: Disposable, root: File): ObjCExportHeaderGenerator { + private fun createObjCExportHeaderGenerator( + disposable: Disposable, root: File, configuration: HeaderGenerator.Configuration, + ): ObjCExportHeaderGenerator { val mapper = ObjCExportMapper( unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT ) @@ -62,7 +62,7 @@ private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : local = false, problemCollector = ObjCExportProblemCollector.SILENT, configuration = object : ObjCExportNamer.Configuration { - override val topLevelNamePrefix: String get() = "" + override val topLevelNamePrefix: String get() = configuration.frameworkName override fun getAdditionalPrefix(module: ModuleDescriptor): String? = null override val objcGenerics: Boolean = true } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCHeader.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCHeader.kt index eb8682ba204..1b8bfc9350f 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCHeader.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCHeader.kt @@ -5,74 +5,85 @@ package org.jetbrains.kotlin.backend.konan.objcexport -data class ObjCHeader(val lines: List) { +import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi + +data class ObjCHeader( + @property:InternalKotlinNativeApi val stubs: List, + @property:InternalKotlinNativeApi val classForwardDeclarations: Set, + @property:InternalKotlinNativeApi val protocolForwardDeclarations: Set, + @property:InternalKotlinNativeApi val additionalImports: List, +) { + + fun renderClassForwardDeclarations(): List = buildList { + if (classForwardDeclarations.isNotEmpty()) { + add("@class ${ + classForwardDeclarations.joinToString { + buildString { + append(it.className) + formatGenerics(this, it.typeDeclarations) + } + } + };") + add("") + } + } + + fun renderProtocolForwardDeclarations(): List = buildList { + if (protocolForwardDeclarations.isNotEmpty()) { + add("@protocol ${protocolForwardDeclarations.joinToString()};") + add("") + } + } + + fun render(exportKDoc: Boolean = true): List { + return buildList { + addImports(foundationImports) + addImports(additionalImports) + add("") + + addAll(renderClassForwardDeclarations()) + addAll(renderProtocolForwardDeclarations()) + + add("NS_ASSUME_NONNULL_BEGIN") + add("#pragma clang diagnostic push") + listOf( + "-Wunknown-warning-option", + + // Protocols don't have generics, classes do. So generated header may contain + // overriding property with "incompatible" type, e.g. `Generic`-typed property + // overriding `Generic`. Suppress these warnings: + "-Wincompatible-property-type", + + "-Wnullability" + ).forEach { + add("#pragma clang diagnostic ignored \"$it\"") + } + add("") + + // If _Nullable_result is not supported, then use _Nullable: + add("#pragma push_macro(\"$objcNullableResultAttribute\")") + add("#if !__has_feature(nullability_nullable_result)") + add("#undef $objcNullableResultAttribute") + add("#define $objcNullableResultAttribute $objcNullableAttribute") + add("#endif") + add("") + + stubs.forEach { + addAll(StubRenderer.render(it, exportKDoc)) + add("") + } + + add("#pragma pop_macro(\"$objcNullableResultAttribute\")") + add("#pragma clang diagnostic pop") + add("NS_ASSUME_NONNULL_END") + } + } + override fun toString(): String { - return lines.joinToString(System.lineSeparator()) + return render().joinToString(System.lineSeparator()) } } -fun ObjCHeader( - stubs: List, - classForwardDeclarations: Set, - protocolForwardDeclarations: Set, - additionalImports: List = emptyList(), - exportKDoc: Boolean = false, -): ObjCHeader = ObjCHeader(buildList { - addImports(foundationImports) - addImports(additionalImports) - add("") - - if (classForwardDeclarations.isNotEmpty()) { - add("@class ${ - classForwardDeclarations.joinToString { - buildString { - append(it.className) - formatGenerics(this, it.typeDeclarations) - } - } - };") - add("") - } - - if (protocolForwardDeclarations.isNotEmpty()) { - add("@protocol ${protocolForwardDeclarations.joinToString()};") - add("") - } - - add("NS_ASSUME_NONNULL_BEGIN") - add("#pragma clang diagnostic push") - listOf( - "-Wunknown-warning-option", - - // Protocols don't have generics, classes do. So generated header may contain - // overriding property with "incompatible" type, e.g. `Generic`-typed property - // overriding `Generic`. Suppress these warnings: - "-Wincompatible-property-type", - - "-Wnullability" - ).forEach { - add("#pragma clang diagnostic ignored \"$it\"") - } - add("") - - // If _Nullable_result is not supported, then use _Nullable: - add("#pragma push_macro(\"$objcNullableResultAttribute\")") - add("#if !__has_feature(nullability_nullable_result)") - add("#undef $objcNullableResultAttribute") - add("#define $objcNullableResultAttribute $objcNullableAttribute") - add("#endif") - add("") - - stubs.forEach { - addAll(StubRenderer.render(it, exportKDoc)) - add("") - } - - add("#pragma pop_macro(\"$objcNullableResultAttribute\")") - add("#pragma clang diagnostic pop") - add("NS_ASSUME_NONNULL_END") -}) - private fun MutableList.addImports(imports: Iterable) { imports.forEach { add("#import <$it>") diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/HeaderGenerator.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/HeaderGenerator.kt new file mode 100644 index 00000000000..c479f739bb4 --- /dev/null +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/HeaderGenerator.kt @@ -0,0 +1,18 @@ +/* + * 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.backend.konan.testUtils + +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader +import java.io.File + +interface HeaderGenerator { + + data class Configuration( + val frameworkName: String = "", + ) + + fun generateHeaders(root: File, configuration: Configuration = Configuration()): ObjCHeader +} \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/testDataDir.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/testDataDir.kt index 782732c4a31..5bed39f7169 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/testDataDir.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/testDataDir.kt @@ -9,4 +9,5 @@ import java.io.File val testDataDir = File("native/objcexport-header-generator/testData") val headersTestDataDir = testDataDir.resolve("headers") -val baseDeclarationsDir = testDataDir.resolve("baseDeclarations") \ No newline at end of file +val baseDeclarationsDir = testDataDir.resolve("baseDeclarations") +val forwardDeclarationsDir = testDataDir.resolve("forwardDeclarations") \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt new file mode 100644 index 00000000000..e8d4943d677 --- /dev/null +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt @@ -0,0 +1,48 @@ +/* + * 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.backend.konan.tests + +import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator +import org.jetbrains.kotlin.backend.konan.testUtils.forwardDeclarationsDir +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.junit.jupiter.api.Test +import java.io.File +import kotlin.test.fail + +class ObjCExportForwardDeclarationsTest( + private val generator: HeaderGenerator, +) { + + @Test + fun `test - function returning interface`() { + doTest(forwardDeclarationsDir.resolve("functionReturningInterface")) + } + + @Test + fun `test - function returning class`() { + doTest(forwardDeclarationsDir.resolve("functionReturningClass")) + } + + @Test + fun `test - property returning interface`() { + doTest(forwardDeclarationsDir.resolve("propertyReturningInterface")) + } + + @Test + fun `test - property returning class`() { + doTest(forwardDeclarationsDir.resolve("propertyReturningClass")) + } + + private fun doTest(root: File) { + if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory") + val generatedHeaders = generator.generateHeaders(root) + val renderedForwardDeclarations = buildString { + generatedHeaders.renderClassForwardDeclarations().forEach(this::appendLine) + generatedHeaders.renderProtocolForwardDeclarations().forEach(this::appendLine) + } + KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), renderedForwardDeclarations) + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt index 9118f9b7161..28388b4bf24 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.backend.konan.tests +import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator +import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator.Configuration import org.jetbrains.kotlin.backend.konan.testUtils.headersTestDataDir import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.jupiter.api.Test @@ -110,6 +112,11 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { doTest(headersTestDataDir.resolve("functionWithErrorType")) } + @Test + fun `test - functionWithErrorTypeAndFrameworkName`() { + doTest(headersTestDataDir.resolve("functionWithErrorTypeAndFrameworkName"), Configuration(frameworkName = "shared")) + } + @Test fun `test - kdocWithBlockTags`() { doTest(headersTestDataDir.resolve("kdocWithBlockTags")) @@ -145,13 +152,19 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { doTest(headersTestDataDir.resolve("dispatchAndExtensionReceiverWithMustBeDocumentedAnnotation")) } - fun interface HeaderGenerator { - fun generateHeaders(root: File): String + @Test + fun `test - classWithUnresolvedSuperType`() { + doTest(headersTestDataDir.resolve("classWithUnresolvedSuperType")) } - private fun doTest(root: File) { + @Test + fun `test - classWithUnresolvedSuperTypeGenerics`() { + doTest(headersTestDataDir.resolve("classWithUnresolvedSuperTypeGenerics")) + } + + private fun doTest(root: File, configuration: Configuration = Configuration()) { if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory") - val generatedHeaders = generator.generateHeaders(root) + val generatedHeaders = generator.generateHeaders(root, configuration).toString() KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), generatedHeaders) } } diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/!functionReturningClass.h b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/!functionReturningClass.h new file mode 100644 index 00000000000..bb70b047fca --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/!functionReturningClass.h @@ -0,0 +1 @@ +@class Foo; diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/Foo.kt b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/Foo.kt new file mode 100644 index 00000000000..3a7c7bdbdf0 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningClass/Foo.kt @@ -0,0 +1,3 @@ +fun foo(): Foo + +class Foo diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/!functionReturningInterface.h b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/!functionReturningInterface.h new file mode 100644 index 00000000000..c11e3764c03 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/!functionReturningInterface.h @@ -0,0 +1 @@ +@protocol Foo; diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/Foo.kt b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/Foo.kt new file mode 100644 index 00000000000..36e0d3dc837 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/functionReturningInterface/Foo.kt @@ -0,0 +1,3 @@ +fun foo(): Foo + +interface Foo diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/!propertyReturningClass.h b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/!propertyReturningClass.h new file mode 100644 index 00000000000..bb70b047fca --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/!propertyReturningClass.h @@ -0,0 +1 @@ +@class Foo; diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/Foo.kt b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/Foo.kt new file mode 100644 index 00000000000..bf7ba8dac00 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningClass/Foo.kt @@ -0,0 +1,3 @@ +val foo: Foo get() = TODO() + +class Foo diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/!propertyReturningInterface.h b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/!propertyReturningInterface.h new file mode 100644 index 00000000000..c11e3764c03 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/!propertyReturningInterface.h @@ -0,0 +1 @@ +@protocol Foo; diff --git a/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/Foo.kt b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/Foo.kt new file mode 100644 index 00000000000..299052cc208 --- /dev/null +++ b/native/objcexport-header-generator/testData/forwardDeclarations/propertyReturningInterface/Foo.kt @@ -0,0 +1,3 @@ +val foo: Foo get() = TODO() + +interface Foo diff --git a/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/!classWithUnresolvedSuperType.h b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/!classWithUnresolvedSuperType.h new file mode 100644 index 00000000000..bbd846afbc9 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/!classWithUnresolvedSuperType.h @@ -0,0 +1,29 @@ +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wincompatible-property-type" +#pragma clang diagnostic ignored "-Wnullability" + +#pragma push_macro("_Nullable_result") +#if !__has_feature(nullability_nullable_result) +#undef _Nullable_result +#define _Nullable_result _Nullable +#endif + +__attribute__((objc_subclassing_restricted)) +@interface Foo : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/Foo.kt b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/Foo.kt new file mode 100644 index 00000000000..6e49f1d2a8c --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperType/Foo.kt @@ -0,0 +1 @@ +class Foo : Unresolved() \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/!classWithUnresolvedSuperTypeGenerics.h b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/!classWithUnresolvedSuperTypeGenerics.h new file mode 100644 index 00000000000..82963739ef1 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/!classWithUnresolvedSuperTypeGenerics.h @@ -0,0 +1,35 @@ +#import +#import +#import +#import +#import +#import +#import + +@protocol Foo; + +NS_ASSUME_NONNULL_BEGIN +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wincompatible-property-type" +#pragma clang diagnostic ignored "-Wnullability" + +#pragma push_macro("_Nullable_result") +#if !__has_feature(nullability_nullable_result) +#undef _Nullable_result +#define _Nullable_result _Nullable +#endif + +@protocol Foo +@required +@end + +__attribute__((objc_subclassing_restricted)) +@interface Bar : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/Foo.kt b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/Foo.kt new file mode 100644 index 00000000000..4dd22601f0b --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithUnresolvedSuperTypeGenerics/Foo.kt @@ -0,0 +1,3 @@ +interface Foo + +class Bar : Foo \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/!functionWithErrorTypeAndFrameworkName.h b/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/!functionWithErrorTypeAndFrameworkName.h new file mode 100644 index 00000000000..1cb4c7900d2 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/!functionWithErrorTypeAndFrameworkName.h @@ -0,0 +1,34 @@ +#import +#import +#import +#import +#import +#import +#import + +@class ERROR; + +NS_ASSUME_NONNULL_BEGIN +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wincompatible-property-type" +#pragma clang diagnostic ignored "-Wnullability" + +#pragma push_macro("_Nullable_result") +#if !__has_feature(nullability_nullable_result) +#undef _Nullable_result +#define _Nullable_result _Nullable +#endif + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("FooKt"))) +@interface sharedFooKt : sharedBase ++ (ERROR *)foo __attribute__((swift_name("foo()"))); +@end + +@interface ERROR : sharedBase +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/Foo.kt b/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/Foo.kt new file mode 100644 index 00000000000..dae719d5091 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/functionWithErrorTypeAndFrameworkName/Foo.kt @@ -0,0 +1 @@ +fun foo() = Unresolved \ No newline at end of file