diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt new file mode 100644 index 00000000000..debeaf94f53 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2023 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 + +data class KtObjCExportConfiguration( + /** + * Also used as top level prefix for declarations if present + */ + val frameworkName: String? = null, + + /** + * Should kdoc written by the user be available in the generated headers and stubs + */ + val exportKDoc: Boolean = true, +) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportNamer.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportNamer.kt index 5a0dccbb3e1..7b62e5cfc6f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportNamer.kt @@ -12,36 +12,23 @@ import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportPropertyName -interface KtObjCExportNamer { - context(KtAnalysisSession) - fun getClassOrProtocolName(symbol: KtClassLikeSymbol): ObjCExportClassOrProtocolName - context(KtAnalysisSession) - fun getPropertyName(symbol: KtPropertySymbol): ObjCExportPropertyName +context(KtAnalysisSession, KtObjCExportSession) +fun KtClassLikeSymbol.getObjCClassOrProtocolName(): ObjCExportClassOrProtocolName { + val resolvedObjCNameAnnotation = resolveObjCNameAnnotation() + + return ObjCExportClassOrProtocolName( + objCName = resolvedObjCNameAnnotation?.objCName ?: nameOrAnonymous.asString(), + swiftName = resolvedObjCNameAnnotation?.swiftName ?: nameOrAnonymous.asString() + ) } -fun KtObjCExportNamer(): KtObjCExportNamer { - return ObjCExportNamerImpl() -} - -private class ObjCExportNamerImpl : KtObjCExportNamer { - context(KtAnalysisSession) - override fun getClassOrProtocolName(symbol: KtClassLikeSymbol): ObjCExportClassOrProtocolName { - val resolvedObjCNameAnnotation = symbol.resolveObjCNameAnnotation() - - return ObjCExportClassOrProtocolName( - objCName = resolvedObjCNameAnnotation?.objCName ?: symbol.nameOrAnonymous.asString(), - swiftName = resolvedObjCNameAnnotation?.swiftName ?: symbol.nameOrAnonymous.asString() - ) - } - - context(KtAnalysisSession) - override fun getPropertyName(symbol: KtPropertySymbol): ObjCExportPropertyName { - val resolveObjCNameAnnotation = symbol.resolveObjCNameAnnotation() - - return ObjCExportPropertyName( - objCName = resolveObjCNameAnnotation?.objCName ?: symbol.name.asString(), - swiftName = resolveObjCNameAnnotation?.swiftName ?: symbol.name.asString() - ) - } +context(KtAnalysisSession, KtObjCExportSession) +fun KtPropertySymbol.getObjCPropertyName(): ObjCExportPropertyName { + val resolveObjCNameAnnotation = resolveObjCNameAnnotation() + + return ObjCExportPropertyName( + objCName = resolveObjCNameAnnotation?.objCName ?: name.asString(), + swiftName = resolveObjCNameAnnotation?.swiftName ?: name.asString() + ) } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportSession.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportSession.kt new file mode 100644 index 00000000000..3679c5982d3 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportSession.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2023 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 + +interface KtObjCExportSession { + val configuration: KtObjCExportConfiguration +} + +inline fun KtObjCExportSession( + configuration: KtObjCExportConfiguration, + block: KtObjCExportSession.() -> T, +): T { + return object : KtObjCExportSession { + override val configuration: KtObjCExportConfiguration = configuration + }.block() +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/objCStubOrigin.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/objCStubOrigin.kt new file mode 100644 index 00000000000..e3d146c14cd --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/objCStubOrigin.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2023 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.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossiblyNamedSymbol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStubOrigin +import org.jetbrains.kotlin.objcexport.analysisApiUtils.findKDocString + +context(KtAnalysisSession) +fun KtSymbol.objCStubOrigin(): ObjCExportStubOrigin { + // TODO: Differentiate origins + // TODO: Extract kdoc from deserialized symbols + return ObjCExportStubOrigin.Source( + name = let { it as? KtPossiblyNamedSymbol }?.name, + psi = psi, + kdoc = findKDocString() + ) +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtResolvedObjCNameAnnotation.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt similarity index 100% rename from native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtResolvedObjCNameAnnotation.kt rename to native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt 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 new file mode 100644 index 00000000000..6741f44ae06 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2023 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.scopes.KtScope +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.INTERFACE +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol + +context(KtAnalysisSession, KtObjCExportSession) +fun KtScope.translateToObjCHeader(): ObjCHeader { + val declarationsInScope = getAllSymbols() + .mapNotNull { symbol -> symbol.translateToObjCExportStubOrNull() } + .toList() + + return ObjCHeader( + stubs = declarationsInScope, + classForwardDeclarations = emptySet(), + protocolForwardDeclarations = declarationsInScope + .filterIsInstance() + .flatMap { it.superProtocols } + .toSet(), + additionalImports = emptyList(), + exportKDoc = configuration.exportKDoc + ) +} + +context(KtAnalysisSession, KtObjCExportSession) +private fun KtSymbol.translateToObjCExportStubOrNull(): ObjCExportStub? { + return when { + this is KtClassOrObjectSymbol && classKind == INTERFACE -> translateToObjCProtocol() + 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 new file mode 100644 index 00000000000..0ebe620aac4 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2023 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.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.types.KtClassType +import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCComment +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocolImpl + +context(KtAnalysisSession, KtObjCExportSession) +fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol { + // TODO: check if this symbol shall be exposed in the first place + require(classKind == KtClassKind.INTERFACE) + + // TODO: Check error type! + val name = getObjCClassOrProtocolName() + + // TODO: Build members + val members = emptyList() + + val superProtocols = superTypes + .asSequence() + .filter { type -> !type.isAny } + .mapNotNull { type -> type as? KtClassType } + .flatMap { type -> type.qualifiers } + .mapNotNull { qualifier -> qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier } + .mapNotNull { it.symbol as? KtClassOrObjectSymbol } + .filter { superInterface -> superInterface.classKind == KtClassKind.INTERFACE } + .map { superInterface -> superInterface.getObjCClassOrProtocolName().objCName } + .toList() + + // TODO: Resolve comment + val comment: ObjCComment? = null + + return ObjCProtocolImpl( + name = name.objCName, + comment = comment, + origin = this.objCStubOrigin(), + attributes = emptyList(), + superProtocols = superProtocols, + members = members + ) +} 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 c79e4fd788a..c34f256dd11 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 @@ -5,7 +5,12 @@ 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.objcexport.KtObjCExportConfiguration +import org.jetbrains.kotlin.objcexport.KtObjCExportSession +import org.jetbrains.kotlin.objcexport.translateToObjCHeader +import org.jetbrains.kotlin.psi.KtFile import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver @@ -23,6 +28,14 @@ class AnalysisApiHeaderGeneratorExtension : ParameterResolver { object AnalysisApiHeaderGenerator : HeaderGenerator { override fun generateHeaders(root: File): String { - TODO("Analysis Api based header generation in not yet implemented") + val session = createStandaloneAnalysisApiSession(root.listFiles().orEmpty().filter { it.extension == "kt" }) + val (module, files) = session.modulesWithFiles.entries.single() + return analyze(module) { + val scope = files.map { it as KtFile }.map { it.getFileSymbol().getFileScope() }.asCompositeScope() + + KtObjCExportSession(KtObjCExportConfiguration()) { + scope.translateToObjCHeader().toString() + } + } } } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/ObjCExportSessionUtil.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/ObjCExportSessionUtil.kt new file mode 100644 index 00000000000..92db97d1bd2 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/ObjCExportSessionUtil.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2023 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.testUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.objcexport.KtObjCExportConfiguration +import org.jetbrains.kotlin.objcexport.KtObjCExportSession +import org.jetbrains.kotlin.psi.KtElement + +inline fun analyzeWithObjCExport( + useSiteKtElement: KtElement, + configuration: KtObjCExportConfiguration = KtObjCExportConfiguration(), + action: context(KtAnalysisSession, KtObjCExportSession) () -> T, +): T = analyze(useSiteKtElement) { + KtObjCExportSession(configuration) { + action(this@analyze, this) + } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/KtObjCExportNamerTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/KtObjCExportNamerTest.kt index a6e9413863f..ac664b2792c 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/KtObjCExportNamerTest.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/KtObjCExportNamerTest.kt @@ -5,12 +5,12 @@ package org.jetbrains.kotlin.objcexport.tests -import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.objcexport.KtObjCExportNamer +import org.jetbrains.kotlin.objcexport.getObjCClassOrProtocolName import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.analyzeWithObjCExport import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -18,19 +18,17 @@ class KtObjCExportNamerTest( private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, ) { - private val namer = KtObjCExportNamer() - @Test fun `test - simple class`() { val foo = inlineSourceCodeAnalysis.createKtFile("class Foo") - analyze(foo) { + analyzeWithObjCExport(foo) { val fooSymbol = foo.getFileSymbol().getFileScope() .getClassifierSymbols(Name.identifier("Foo")) .single() as KtNamedClassOrObjectSymbol assertEquals( ObjCExportClassOrProtocolName("Foo", "Foo"), - namer.getClassOrProtocolName(fooSymbol) + fooSymbol.getObjCClassOrProtocolName() ) } }