[ObjCExport] Implement rudimentary interface to ObjCProtocol translation

KT-64226
This commit is contained in:
Sebastian Sellmair
2023-12-13 20:07:20 +01:00
committed by Space Team
parent aa3fa9e8c5
commit 9909940b24
10 changed files with 208 additions and 36 deletions
@@ -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,
)
@@ -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()
)
}
@@ -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 <T> KtObjCExportSession(
configuration: KtObjCExportConfiguration,
block: KtObjCExportSession.() -> T,
): T {
return object : KtObjCExportSession {
override val configuration: KtObjCExportConfiguration = configuration
}.block()
}
@@ -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()
)
}
@@ -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<ObjCProtocol>()
.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
}
}
@@ -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<ObjCExportStub>()
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
)
}
@@ -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()
}
}
}
}
@@ -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 <T> analyzeWithObjCExport(
useSiteKtElement: KtElement,
configuration: KtObjCExportConfiguration = KtObjCExportConfiguration(),
action: context(KtAnalysisSession, KtObjCExportSession) () -> T,
): T = analyze(useSiteKtElement) {
KtObjCExportSession(configuration) {
action(this@analyze, this)
}
}
@@ -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()
)
}
}