From 80d9933543417eea0981adafdd8f8b3cc7bfe983 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 11 Jan 2024 08:47:27 +0100 Subject: [PATCH] [ObjCExport] Analysis Api: Implement initial support for exporting 'MustBeDocumented' annotations ^KT-64869 Fixed --- .../getObjCDocumentedAnnotations.kt | 46 +++++++ .../kotlin/objcexport/translateToObjCClass.kt | 18 +-- .../objcexport/translateToObjCComment.kt | 122 ++++++++++++++++++ .../objcexport/translateToObjCMethod.kt | 107 +-------------- .../objcexport/translateToObjCProtocol.kt | 3 +- .../tests/GetObjCDocumentedAnnotationsTest.kt | 79 ++++++++++++ .../konan/objcexport/ObjCCommentUtils.kt | 22 ++++ .../tests/ObjCExportHeaderGeneratorTest.kt | 5 + ...!interfaceWithMustBeDocumentedAnnotation.h | 33 +++++ .../Foo.kt | 8 ++ 10 files changed, 318 insertions(+), 125 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetObjCDocumentedAnnotationsTest.kt create mode 100644 native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCCommentUtils.kt create mode 100644 native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/!interfaceWithMustBeDocumentedAnnotation.h create mode 100644 native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/Foo.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt new file mode 100644 index 00000000000..8a2a724dc27 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt @@ -0,0 +1,46 @@ +/* + * 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.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList +import org.jetbrains.kotlin.analysis.api.annotations.annotationClassIds +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotatedSymbol +import org.jetbrains.kotlin.backend.konan.KonanFqNames +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.name.StandardClassIds + +/** + * Returns the list of annotations which shall be documented for ObjC export. + * Usually annotations that are marked with `@MustBeDocumented` are important and shall be retained for users. + * However, some annotations e.g. [kotlin.native.ObjCName], whilst annotated with `@MustBeDocumented` shall not be exported for ObjC + * documentation. + */ +context(KtAnalysisSession) +internal fun KtAnnotatedSymbol.getObjCDocumentedAnnotations(): List { + return annotationsList.getObjCDocumentedAnnotations() +} + +/** + * See [getObjCDocumentedAnnotations] + */ +context(KtAnalysisSession) +internal fun KtAnnotationsList.getObjCDocumentedAnnotations(): List { + return annotations + .filter { annotation -> + val annotationClassId = annotation.classId ?: return@filter false + if (annotationClassId.asSingleFqName() in mustBeDocumentedAnnotationsStopList) return@filter false + val annotationClassSymbol = getClassOrObjectSymbolByClassId(annotationClassId) ?: return@filter false + StandardClassIds.Annotations.MustBeDocumented in annotationClassSymbol.annotationClassIds + } +} + +private val mustBeDocumentedAnnotationsStopList = setOf( + StandardNames.FqNames.deprecated, + KonanFqNames.objCName, + KonanFqNames.shouldRefineInSwift +) \ No newline at end of file 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 bcc77fe32f7..07d81c58f9e 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 @@ -1,13 +1,10 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList 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.KonanFqNames import org.jetbrains.kotlin.backend.konan.objcexport.* -import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny @@ -27,7 +24,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { val attributes = if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList() val name = getObjCClassOrProtocolName() - val comment: ObjCComment? = annotationsList.toComment() + val comment: ObjCComment? = annotationsList.translateToObjCComment() val origin: ObjCExportStubOrigin = getObjCStubOrigin() val superProtocols: List = superProtocols() val members: List = members().flatMap { it.translateToObjCExportStubs() } @@ -59,16 +56,3 @@ private fun abbreviate(name: String): String { return normalizedName } - -/** - * Not implemented - */ -private fun KtAnnotationsList.toComment(): ObjCComment? { - return null -} - -private val mustBeDocumentedAnnotationsStopList = setOf( - StandardNames.FqNames.deprecated, - KonanFqNames.objCName, - KonanFqNames.shouldRefineInSwift -) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt new file mode 100644 index 00000000000..b2d785b2eea --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt @@ -0,0 +1,122 @@ +/* + * 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 + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList +import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility +import org.jetbrains.kotlin.backend.konan.objcexport.MethodBridge +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCComment +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCParameter +import org.jetbrains.kotlin.backend.konan.objcexport.plus +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getObjCDocumentedAnnotations + + +context(KtAnalysisSession) +internal fun KtAnnotationsList.translateToObjCComment(): ObjCComment? { + val annotations = getObjCDocumentedAnnotations() + .mapNotNull { annotation -> renderAnnotation(annotation) } + + if (annotations.isEmpty()) return null + return ObjCComment(listOf("@note annotations") + annotations.map { " $it" }) +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildComment] + */ +context(KtAnalysisSession) +internal fun KtFunctionLikeSymbol.translateToObjCComment(bridge: MethodBridge, parameters: List): ObjCComment? { + val isSuspend: Boolean = if (this is KtFunctionSymbol) this.isSuspend else false + + val throwsComments = if (isSuspend || bridge.returnsError) { + val effectiveThrows = getEffectiveThrows(this).toSet() + when { + effectiveThrows.contains(StandardClassIds.Throwable) -> { + listOf("@note This method converts all Kotlin exceptions to errors.") + } + + effectiveThrows.isNotEmpty() -> { + listOf( + buildString { + append("@note This method converts instances of ") + effectiveThrows.joinTo(this) { it.relativeClassName.asString() } + append(" to errors.") + }, + "Other uncaught Kotlin exceptions are fatal." + ) + } + + else -> { + // Shouldn't happen though. + listOf("@warning All uncaught Kotlin exceptions are fatal.") + } + } + } else emptyList() + + val visibilityComments = buildObjCVisibilityComment("method") + + val paramComments = valueParameters.mapNotNull { parameterSymbol -> + parameters.find { parameter -> parameter.origin?.name == parameterSymbol.name } + ?.renderedObjCDocumentedParamAnnotations(parameterSymbol) + } + val annotationsComments = annotationsList.translateToObjCComment() + return annotationsComments + paramComments + throwsComments + visibilityComments +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mustBeDocumentedParamAttributeList] + */ +context(KtAnalysisSession) +private fun ObjCParameter.renderedObjCDocumentedParamAnnotations(parameterSymbol: KtValueParameterSymbol): String? { + val renderedAnnotationsString = parameterSymbol.getObjCDocumentedAnnotations() + .mapNotNull { annotation -> renderAnnotation(annotation) } + .ifEmpty { return null } + .joinToString(" ") + return "@param $name annotations $renderedAnnotationsString" +} + + +private fun renderAnnotation(annotation: KtAnnotationApplicationWithArgumentsInfo): String? { + return renderAnnotation(annotation.classId ?: return null, annotation.arguments) +} + +private fun renderAnnotation(clazz: ClassId, arguments: List): String { + return buildString { + append(clazz.asSingleFqName()) + if (arguments.isNotEmpty()) { + append('(') + arguments.joinTo(this) + append(')') + } + } +} + +/** + * Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows] + */ +@Suppress("UNUSED_PARAMETER") +private fun getEffectiveThrows(method: KtFunctionLikeSymbol): Sequence { + return emptySequence() +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.visibilityComments] + */ +private fun KtSymbol.buildObjCVisibilityComment(kind: String): ObjCComment? { + return when ((this as? KtSymbolWithVisibility)?.visibility ?: return null) { + Visibilities.Protected -> ObjCComment("@note This $kind has protected visibility in Kotlin source and is intended only for use by subclasses.") + else -> null + } +} \ No newline at end of file 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 7ad47a51d1e..ee7b0b45dd9 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 @@ -4,18 +4,12 @@ package org.jetbrains.kotlin.objcexport import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos -import org.jetbrains.kotlin.analysis.api.annotations.annotations import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.backend.konan.objcexport.* -import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.descriptors.Visibility -import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors @@ -62,7 +56,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod( val swiftName = getSwiftName(bridge) val attributes = mutableListOf() val returnBridge = bridge.returnBridge - val comment = buildComment(this, bridge, parameters) + val comment = this.translateToObjCComment(bridge, parameters) attributes += getSwiftPrivateAttribute() ?: swiftNameAttribute(swiftName) @@ -109,105 +103,6 @@ internal fun String.toValidObjCSwiftIdentifier(): String { .let { if (it == "_") "__" else it } } -private val throwableClassId = ClassId.topLevel(StandardNames.FqNames.throwable) - -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildComment] - */ -private fun buildComment(method: KtFunctionLikeSymbol, bridge: MethodBridge, parameters: List): ObjCComment? { - - val visibility: Visibility - val isSuspend: Boolean - - if (method is KtFunctionSymbol) { - isSuspend = method.isSuspend - visibility = method.visibility - } else if (method is KtConstructorSymbol) { - isSuspend = false - visibility = Visibilities.Public //check constructor - } else { - TODO("Unsupported type for comment building: $method") - } - - val throwsComments = if (isSuspend || bridge.returnsError) { - val effectiveThrows = getEffectiveThrows(method).toSet() - when { - effectiveThrows.contains(throwableClassId) -> { - listOf("@note This method converts all Kotlin exceptions to errors.") - } - - effectiveThrows.isNotEmpty() -> { - listOf( - buildString { - append("@note This method converts instances of ") - effectiveThrows.joinTo(this) { it.relativeClassName.asString() } - append(" to errors.") - }, - "Other uncaught Kotlin exceptions are fatal." - ) - } - - else -> { - // Shouldn't happen though. - listOf("@warning All uncaught Kotlin exceptions are fatal.") - } - } - } else emptyList() - - val visibilityComments = visibilityComments(visibility, "method") - - val paramComments = method.valueParameters.flatMap { parameterDescriptor -> - parameters.find { parameter -> parameter.origin?.name == parameterDescriptor.name }?.let { parameter -> - mustBeDocumentedParamAttributeList(parameter, descriptor = parameterDescriptor) - } ?: emptyList() - } - val annotationsComments = mustBeDocumentedAttributeList(method.annotations) - val commentLines = annotationsComments + paramComments + throwsComments + visibilityComments - return if (commentLines.isNotEmpty()) ObjCComment(commentLines) else null -} - -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mustBeDocumentedParamAttributeList] - */ -private fun mustBeDocumentedParamAttributeList(parameter: ObjCParameter, descriptor: KtValueParameterSymbol): List { - - descriptor.annotationsList.annotations - - val mbdAnnotations = mustBeDocumentedAnnotations(descriptor.annotations).joinToString(" ") - return if (mbdAnnotations.isNotEmpty()) listOf("@param ${parameter.name} annotations $mbdAnnotations") else emptyList() -} - -/** - * Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mustBeDocumentedAnnotations] - */ -private fun mustBeDocumentedAnnotations(annotations: List): List { - return emptyList() -} - -private fun mustBeDocumentedAttributeList(annotations: List): List { - val mustBeDocumentedAnnotations = mustBeDocumentedAnnotations(annotations) - return if (mustBeDocumentedAnnotations.isNotEmpty()) { - listOf("@note annotations") + mustBeDocumentedAnnotations.map { " $it" } - } else emptyList() -} - -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.visibilityComments] - */ -private fun visibilityComments(visibility: Visibility, kind: String): List { - return when (visibility) { - Visibilities.Protected -> listOf("@note This $kind has protected visibility in Kotlin source and is intended only for use by subclasses.") - else -> emptyList() - } -} - -/** - * Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows] - */ -private fun getEffectiveThrows(method: KtFunctionLikeSymbol): Sequence { - return emptySequence() -} - internal fun KtCallableSymbol.getSwiftPrivateAttribute(): String? = if (isRefinedInSwift()) "swift_private" else null 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 1d43d9ed8f6..3b04f51b48b 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 @@ -28,8 +28,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { val members = members().flatMap { it.translateToObjCExportStubs() } - // TODO: Resolve comment - val comment: ObjCComment? = null + val comment: ObjCComment? = annotationsList.translateToObjCComment() return ObjCProtocolImpl( name = name.objCName, diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetObjCDocumentedAnnotationsTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetObjCDocumentedAnnotationsTest.kt new file mode 100644 index 00000000000..194ce43724f --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetObjCDocumentedAnnotationsTest.kt @@ -0,0 +1,79 @@ +/* + * 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.tests + +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getObjCDocumentedAnnotations +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +class GetObjCDocumentedAnnotationsTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + @Test + fun `test - no annotation present`() { + val file = inlineSourceCodeAnalysis.createKtFile("class Foo") + analyze(file) { + val foo = file.getClassOrFail("Foo") + val objCDocumentedAnnotations = foo.getObjCDocumentedAnnotations() + if (objCDocumentedAnnotations.isNotEmpty()) + fail("Expected no 'ObjC Documented Annotation present, Found. $objCDocumentedAnnotations") + } + } + + @Test + fun `test - simple Important annotation`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + @MustBeDocumented + annotation class ImportantAnnotation + annotation class OtherAnnotation + + @ImportantAnnotation @OtherAnnotation + class Foo + """.trimIndent() + ) + + analyze(file) { + val foo = file.getClassOrFail("Foo") + val objCDocumentedAnnotations = foo.getObjCDocumentedAnnotations() + if (objCDocumentedAnnotations.size != 1) + fail("Expected single documented annotation. Found: $objCDocumentedAnnotations") + + val objCDocumentedAnnotation = objCDocumentedAnnotations.single() + assertEquals("ImportantAnnotation", objCDocumentedAnnotation.classId?.shortClassName?.asString()) + } + } + + @Test + fun `test - special ObjC annotations are not documented for export`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + @MustBeDocumented + annotation class ImportantAnnotation + + @kotlin.native.ObjCName("ObjCFoo") + @kotlin.native.ShouldRefineInSwift + @Deprecated("Deprecations shall also not be shown") + @ImportantAnnotation + class Foo + """.trimIndent() + ) + + analyze(file) { + val foo = file.getClassOrFail("Foo") + val objCDocumentedAnnotations = foo.getObjCDocumentedAnnotations() + if (objCDocumentedAnnotations.size != 1) + fail("Expected single documented annotation. Found: $objCDocumentedAnnotations") + + val objCDocumentedAnnotation = objCDocumentedAnnotations.single() + assertEquals("ImportantAnnotation", objCDocumentedAnnotation.classId?.shortClassName?.asString()) + } + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCCommentUtils.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCCommentUtils.kt new file mode 100644 index 00000000000..a9d294694d2 --- /dev/null +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCCommentUtils.kt @@ -0,0 +1,22 @@ +/* + * 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.objcexport + +import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi + +@InternalKotlinNativeApi +operator fun ObjCComment?.plus(other: ObjCComment?): ObjCComment? { + val lines = this?.contentLines.orEmpty() + other?.contentLines.orEmpty() + if (lines.isEmpty()) return null + return ObjCComment(lines) +} + +@InternalKotlinNativeApi +operator fun ObjCComment?.plus(lines: Iterable): ObjCComment? { + val newLines = this?.contentLines.orEmpty() + lines + if (newLines.isEmpty()) return null + return ObjCComment(newLines) +} 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 b5810c21662..9118f9b7161 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 @@ -120,6 +120,11 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { doTest(headersTestDataDir.resolve("classWithMustBeDocumentedAnnotation")) } + @Test + fun `test - interfaceWithMustBeDocumentedAnnotation`() { + doTest(headersTestDataDir.resolve("interfaceWithMustBeDocumentedAnnotation")) + } + @Test fun `test - functionWithMustBeDocumentedAnnotation`() { doTest(headersTestDataDir.resolve("functionWithMustBeDocumentedAnnotation")) diff --git a/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/!interfaceWithMustBeDocumentedAnnotation.h b/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/!interfaceWithMustBeDocumentedAnnotation.h new file mode 100644 index 00000000000..d4fcc67f075 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/!interfaceWithMustBeDocumentedAnnotation.h @@ -0,0 +1,33 @@ +#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 + + +/** + * @note annotations + * ImportantAnnotation + * AnotherImportantAnnotation +*/ +@protocol Foo +@required +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/Foo.kt b/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/Foo.kt new file mode 100644 index 00000000000..54d11b4d371 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/interfaceWithMustBeDocumentedAnnotation/Foo.kt @@ -0,0 +1,8 @@ +@kotlin.annotation.MustBeDocumented +annotation class ImportantAnnotation + +@kotlin.annotation.MustBeDocumented +annotation class AnotherImportantAnnotation + +@ImportantAnnotation @AnotherImportantAnnotation +interface Foo \ No newline at end of file