[ObjCExport] Split ObjCExport into K1 and Analysis Api implementation
FL-23390 ^KT-64168 Fixed
This commit is contained in:
committed by
Space Team
parent
3e57265fcb
commit
e409c60780
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
/* Required to use Analysis Api */
|
||||
freeCompilerArgs.add("-Xcontext-receivers")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":native:objcexport-header-generator"))
|
||||
api(project(":analysis:analysis-api"))
|
||||
|
||||
testImplementation(projectTests(":native:objcexport-header-generator"))
|
||||
testApi(project(":analysis:analysis-api-standalone"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
testsJar()
|
||||
|
||||
nativeTest("test", tag = null) {
|
||||
useJUnitPlatform()
|
||||
enableJunit5ExtensionsAutodetection()
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.KtClassLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||
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
|
||||
}
|
||||
|
||||
fun ObjCExportNamer(): 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.annotations.KtConstantAnnotationValue
|
||||
import org.jetbrains.kotlin.analysis.api.base.KtConstantValue
|
||||
import org.jetbrains.kotlin.analysis.api.base.KtConstantValue.KtStringConstantValue
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotatedSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||
|
||||
/**
|
||||
* Represents the values resolved from the [kotlin.native.ObjCName] annotation.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* **Given a class Foo**
|
||||
* ```kotlin
|
||||
* @ObjCName("FooObjC", "FooSwift", true)
|
||||
* class Foo
|
||||
* ```
|
||||
*
|
||||
* **Given class Foo being analyzed**
|
||||
* ```kotlin
|
||||
* val foo = getFooClassOrObjectSymbol()
|
||||
* // ^
|
||||
* // Imaginary method to get the symbol 'Foo' from above
|
||||
*
|
||||
* val resolvedObjCNameAnnotation = foo.resolveObjCNameAnnotation()
|
||||
* // ^
|
||||
* // objCName = "FooObjC"
|
||||
* // swiftName = "FooSwift"
|
||||
* // isExaclt = true
|
||||
* ```
|
||||
*/
|
||||
internal class KtResolvedObjCNameAnnotation(
|
||||
val objCName: String?,
|
||||
val swiftName: String?,
|
||||
val isExact: Boolean,
|
||||
)
|
||||
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtAnnotatedSymbol.resolveObjCNameAnnotation(): KtResolvedObjCNameAnnotation {
|
||||
var objCName: String? = null
|
||||
var swiftName: String? = null
|
||||
var isExact = false
|
||||
|
||||
annotationsList.annotations.find { it.classId?.asSingleFqName() == KonanFqNames.objCName }?.let { annotation ->
|
||||
annotation.arguments.forEach { argument ->
|
||||
when (argument.name.identifier) {
|
||||
"name" -> objCName = argument.expression.let { it as? KtConstantAnnotationValue }
|
||||
?.constantValue?.let { it as KtStringConstantValue }
|
||||
?.value
|
||||
"swiftName" -> swiftName = argument.expression.let { it as? KtConstantAnnotationValue }
|
||||
?.constantValue?.let { it as KtStringConstantValue }
|
||||
?.value
|
||||
"exact" -> isExact = argument.expression.let { it as? KtConstantAnnotationValue }
|
||||
?.constantValue?.let { it as KtConstantValue.KtBooleanConstantValue }
|
||||
?.value ?: isExact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return KtResolvedObjCNameAnnotation(
|
||||
objCName = objCName,
|
||||
swiftName = swiftName,
|
||||
isExact = isExact
|
||||
)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.backend.konan.tests.ObjCExportHeaderGeneratorTest.HeaderGenerator
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.ParameterContext
|
||||
import org.junit.jupiter.api.extension.ParameterResolver
|
||||
import java.io.File
|
||||
|
||||
class AnalysisApiHeaderGeneratorExtension : ParameterResolver {
|
||||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
|
||||
return parameterContext.parameter.type == HeaderGenerator::class.java
|
||||
}
|
||||
|
||||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
|
||||
return AnalysisApiHeaderGenerator
|
||||
}
|
||||
}
|
||||
|
||||
object AnalysisApiHeaderGenerator : HeaderGenerator {
|
||||
override fun generateHeaders(root: File): String {
|
||||
TODO("Analysis Api based header generation in not yet implemented")
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.intellij.lang.annotations.Language
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
|
||||
import org.junit.jupiter.api.extension.ParameterContext
|
||||
import org.junit.jupiter.api.extension.ParameterResolver
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
/**
|
||||
* Provides ability to quickly write tests with 'inline source code' aka passing Kotlin source code as String.
|
||||
*
|
||||
* This interface can be injected into any test class constructor.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* class MyTest(
|
||||
* private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis
|
||||
* ) {
|
||||
* @Test
|
||||
* fun `test - something important`() {
|
||||
* val myFile = inlineSourceCodeAnalysis.createKtFile("class Foo")
|
||||
* analyze(myFile) {
|
||||
* // Use analysis session to write advanced tests
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface InlineSourceCodeAnalysis {
|
||||
fun createKtFile(@Language("kotlin") sourceCode: String): KtFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension used to inject an instance of [InlineSourceCodeAnalysis] into tests.
|
||||
*/
|
||||
class InlineSourceCodeAnalysisExtension : ParameterResolver, AfterEachCallback {
|
||||
private companion object {
|
||||
val namespace: Namespace = Namespace.create(Any())
|
||||
val tempDirKey = Any()
|
||||
}
|
||||
|
||||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
|
||||
return parameterContext.parameter.type == InlineSourceCodeAnalysis::class.java
|
||||
}
|
||||
|
||||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
|
||||
val temporaryDirectory = Files.createTempDirectory("inlineSourceCode").toFile()
|
||||
extensionContext.getStore(namespace.append(extensionContext.requiredTestClass)).put(tempDirKey, temporaryDirectory)
|
||||
return InlineSourceCodeAnalysisImpl(temporaryDirectory)
|
||||
}
|
||||
|
||||
override fun afterEach(context: ExtensionContext) {
|
||||
context.getStore(namespace.append(context.requiredTestClass))?.get(tempDirKey, File::class.java)?.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple implementation [InlineSourceCodeAnalysis]
|
||||
*/
|
||||
private class InlineSourceCodeAnalysisImpl(private val tempDir: File) : InlineSourceCodeAnalysis {
|
||||
override fun createKtFile(@Language("kotlin") sourceCode: String): KtFile {
|
||||
return createStandaloneAnalysisApiSession(tempDir, listOf(sourceCode))
|
||||
.modulesWithFiles.entries.single()
|
||||
.value.single() as KtFile
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.KtAnalysisApiInternals
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
|
||||
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
|
||||
import org.jetbrains.kotlin.backend.konan.testUtils.kotlinNativeStdlibPath
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.platform.konan.NativePlatforms
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
|
||||
/**
|
||||
* Creates a standalone analysis session from Kotlin source code passed as [kotlinSources]
|
||||
*/
|
||||
fun createStandaloneAnalysisApiSession(
|
||||
tempDir: File,
|
||||
kotlinSources: List<String>,
|
||||
): StandaloneAnalysisAPISession {
|
||||
val testModuleRoot = tempDir.resolve("testModule")
|
||||
testModuleRoot.mkdirs()
|
||||
|
||||
kotlinSources.forEachIndexed { index, kotlinSource ->
|
||||
testModuleRoot.resolve("TestSources$index.kt").apply {
|
||||
writeText(kotlinSource)
|
||||
}
|
||||
}
|
||||
return createStandaloneAnalysisApiSession(listOf(testModuleRoot))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standalone analysis session from [kotlinFiles] on disk.
|
||||
* The Kotlin/Native stdlib will be provided as dependency
|
||||
*/
|
||||
fun createStandaloneAnalysisApiSession(kotlinFiles: List<File>): StandaloneAnalysisAPISession {
|
||||
val currentArchitectureTarget = HostManager.host
|
||||
val nativePlatform = NativePlatforms.nativePlatformByTargets(listOf(currentArchitectureTarget))
|
||||
return buildStandaloneAnalysisAPISession {
|
||||
@OptIn(KtAnalysisApiInternals::class)
|
||||
registerProjectService(KtLifetimeTokenProvider::class.java, KtAlwaysAccessibleLifetimeTokenProvider())
|
||||
|
||||
buildKtModuleProvider {
|
||||
platform = nativePlatform
|
||||
val kLib = addModule(
|
||||
buildKtLibraryModule {
|
||||
addBinaryRoot(Path(kotlinNativeStdlibPath))
|
||||
platform = nativePlatform
|
||||
libraryName = "klib"
|
||||
}
|
||||
)
|
||||
addModule(
|
||||
buildKtSourceModule {
|
||||
addSourceRoots(kotlinFiles.map { it.toPath() })
|
||||
addRegularDependency(kLib)
|
||||
platform = nativePlatform
|
||||
moduleName = "source"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.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.ObjCExportNamer
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class KtObjCExportNamerTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
|
||||
private val namer = ObjCExportNamer()
|
||||
|
||||
@Test
|
||||
fun `test - simple class`() {
|
||||
val foo = inlineSourceCodeAnalysis.createKtFile("class Foo")
|
||||
analyze(foo) {
|
||||
val fooSymbol = foo.getFileSymbol().getFileScope()
|
||||
.getClassifierSymbols(Name.identifier("Foo"))
|
||||
.single() as KtNamedClassOrObjectSymbol
|
||||
|
||||
assertEquals(
|
||||
ObjCExportClassOrProtocolName("Foo", "Foo"),
|
||||
namer.getClassOrProtocolName(fooSymbol)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.tests
|
||||
|
||||
import org.jetbrains.kotlin.objcexport.resolveObjCNameAnnotation
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class KtResolvedObjCNameAnnotationTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun `test - class - no ObjCName annotation`() {
|
||||
val ktFile = inlineSourceCodeAnalysis.createKtFile("class Foo")
|
||||
analyze(ktFile) {
|
||||
val fooSymbol = ktFile.getFileSymbol().getFileScope().getClassifierSymbols(Name.identifier("Foo")).single() as KtClassLikeSymbol
|
||||
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
|
||||
assertNull(resolvedObjCAnnotation.swiftName)
|
||||
assertNull(resolvedObjCAnnotation.objCName)
|
||||
assertFalse(resolvedObjCAnnotation.isExact)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - class - with ObjCName annotation`() {
|
||||
val ktFile = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
@kotlin.native.ObjCName("FooObjC", "FooSwift", true)
|
||||
class Foo
|
||||
""".trimIndent()
|
||||
)
|
||||
analyze(ktFile) {
|
||||
val fooSymbol = ktFile.getFileSymbol().getFileScope().getClassifierSymbols(Name.identifier("Foo")).single() as KtClassLikeSymbol
|
||||
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
|
||||
assertEquals("FooObjC", resolvedObjCAnnotation.objCName)
|
||||
assertEquals("FooSwift", resolvedObjCAnnotation.swiftName)
|
||||
assertTrue(resolvedObjCAnnotation.isExact)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - function - with ObjCName annotation`() {
|
||||
val ktFile = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
@kotlin.native.ObjCName("fooObjC", "fooSwift", true)
|
||||
fun foo() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
analyze(ktFile) {
|
||||
val fooSymbol = ktFile.getFileSymbol().getFileScope().getCallableSymbols(Name.identifier("foo")).single() as KtFunctionSymbol
|
||||
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
|
||||
assertEquals("fooObjC", resolvedObjCAnnotation.objCName)
|
||||
assertEquals("fooSwift", resolvedObjCAnnotation.swiftName)
|
||||
assertTrue(resolvedObjCAnnotation.isExact)
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiHeaderGeneratorExtension
|
||||
org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysisExtension
|
||||
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":native:objcexport-header-generator"))
|
||||
implementation(project(":compiler:cli-base"))
|
||||
implementation(project(":compiler:ir.objcinterop"))
|
||||
implementation(project(":compiler:ir.serialization.native"))
|
||||
implementation(project(":core:descriptors"))
|
||||
|
||||
testImplementation(projectTests(":native:objcexport-header-generator"))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
optIn.add("org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi")
|
||||
}
|
||||
}
|
||||
|
||||
testsJar()
|
||||
|
||||
nativeTest("test", tag = null) {
|
||||
useJUnitPlatform()
|
||||
enableJunit5ExtensionsAutodetection()
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionTypeKind
|
||||
import org.jetbrains.kotlin.builtins.getFunctionTypeKind
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ClassDescriptor.isMappedFunctionClass() =
|
||||
this.getFunctionTypeKind() == FunctionTypeKind.Function &&
|
||||
// Type parameters include return type.
|
||||
declaredTypeParameters.size - 1 < CustomTypeMappers.functionTypeMappersArityLimit
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
interface CustomTypeMapper {
|
||||
val mappedClassId: ClassId
|
||||
fun mapType(
|
||||
mappedSuperType: KotlinType,
|
||||
translator: ObjCExportTranslatorImpl,
|
||||
objCExportScope: ObjCExportScope,
|
||||
): ObjCNonNullReferenceType
|
||||
}
|
||||
|
||||
internal object CustomTypeMappers {
|
||||
/**
|
||||
* Custom type mappers.
|
||||
*
|
||||
* Don't forget to update [hiddenTypes] after adding new one.
|
||||
*/
|
||||
private val predefined: Map<ClassId, CustomTypeMapper> = with(StandardNames.FqNames) {
|
||||
val result = mutableListOf<CustomTypeMapper>()
|
||||
|
||||
result += Collection(list, "NSArray")
|
||||
result += Collection(mutableList, "NSMutableArray")
|
||||
result += Collection(set, "NSSet")
|
||||
result += Collection(mutableSet, { namer.mutableSetName.objCName })
|
||||
result += Collection(map, "NSDictionary")
|
||||
result += Collection(mutableMap, { namer.mutableMapName.objCName })
|
||||
|
||||
NSNumberKind.values().forEach {
|
||||
// TODO: NSNumber seem to have different equality semantics.
|
||||
val classId = it.mappedKotlinClassId
|
||||
if (classId != null) {
|
||||
result += Simple(classId, { namer.numberBoxName(classId).objCName })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result += Simple(ClassId.topLevel(string.toSafe()), "NSString")
|
||||
|
||||
result.associateBy { it.mappedClassId }
|
||||
}
|
||||
|
||||
internal val functionTypeMappersArityLimit = 33 // not including, i.e. [0..33)
|
||||
|
||||
fun hasMapper(descriptor: ClassDescriptor): Boolean {
|
||||
// Should be equivalent to `getMapper(descriptor) != null`.
|
||||
if (descriptor.classId in predefined) return true
|
||||
if (descriptor.isMappedFunctionClass()) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun getMapper(descriptor: ClassDescriptor): CustomTypeMapper? {
|
||||
val classId = descriptor.classId
|
||||
|
||||
predefined[classId]?.let { return it }
|
||||
|
||||
if (descriptor.isMappedFunctionClass()) {
|
||||
// TODO: somewhat hacky, consider using FunctionClassDescriptor.arity later.
|
||||
val arity = descriptor.declaredTypeParameters.size - 1 // Type parameters include return type.
|
||||
assert(classId == StandardNames.getFunctionClassId(arity))
|
||||
return Function(arity)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
val hiddenTypes: Set<ClassId> = 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()
|
||||
|
||||
private class Simple(
|
||||
override val mappedClassId: ClassId,
|
||||
private val getObjCClassName: ObjCExportTranslatorImpl.() -> String,
|
||||
) : CustomTypeMapper {
|
||||
|
||||
constructor(
|
||||
mappedClassId: ClassId,
|
||||
objCClassName: String,
|
||||
) : this(mappedClassId, { objCClassName })
|
||||
|
||||
override fun mapType(
|
||||
mappedSuperType: KotlinType,
|
||||
translator: ObjCExportTranslatorImpl,
|
||||
objCExportScope: ObjCExportScope,
|
||||
): ObjCNonNullReferenceType =
|
||||
ObjCClassType(translator.getObjCClassName())
|
||||
}
|
||||
|
||||
private class Collection(
|
||||
mappedClassFqName: FqName,
|
||||
private val getObjCClassName: ObjCExportTranslatorImpl.() -> String,
|
||||
) : CustomTypeMapper {
|
||||
|
||||
constructor(
|
||||
mappedClassFqName: FqName,
|
||||
objCClassName: String,
|
||||
) : this(mappedClassFqName, { objCClassName })
|
||||
|
||||
override val mappedClassId = ClassId.topLevel(mappedClassFqName)
|
||||
|
||||
override fun mapType(
|
||||
mappedSuperType: KotlinType,
|
||||
translator: ObjCExportTranslatorImpl,
|
||||
objCExportScope: ObjCExportScope,
|
||||
): ObjCNonNullReferenceType {
|
||||
val typeArguments = mappedSuperType.arguments.map {
|
||||
val argument = it.type
|
||||
if (TypeUtils.isNullableType(argument)) {
|
||||
// Kotlin `null` keys and values are represented as `NSNull` singleton.
|
||||
ObjCIdType
|
||||
} else {
|
||||
translator.mapReferenceTypeIgnoringNullability(argument, objCExportScope)
|
||||
}
|
||||
}
|
||||
|
||||
return ObjCClassType(translator.getObjCClassName(), typeArguments)
|
||||
}
|
||||
}
|
||||
|
||||
private class Function(private val parameterCount: Int) : CustomTypeMapper {
|
||||
override val mappedClassId: ClassId
|
||||
get() = StandardNames.getFunctionClassId(parameterCount)
|
||||
|
||||
override fun mapType(
|
||||
mappedSuperType: KotlinType,
|
||||
translator: ObjCExportTranslatorImpl,
|
||||
objCExportScope: ObjCExportScope,
|
||||
): ObjCNonNullReferenceType {
|
||||
return translator.mapFunctionTypeIgnoringNullability(mappedSuperType, objCExportScope, returnsVoid = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.isSubpackageOf
|
||||
|
||||
/**
|
||||
* Tries to infer a main package name that can be used
|
||||
* for bundle ID of a framework.
|
||||
*/
|
||||
@InternalKotlinNativeApi
|
||||
class MainPackageGuesser {
|
||||
fun guess(
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
includedLibraryDescriptors: List<ModuleDescriptor>,
|
||||
exportedDependencies: List<ModuleDescriptor>,
|
||||
): FqName {
|
||||
// Consider exported libraries only if we cannot infer the package from sources or included libs.
|
||||
return guessMainPackage(includedLibraryDescriptors + moduleDescriptor)
|
||||
?: guessMainPackage(exportedDependencies)
|
||||
?: FqName.ROOT
|
||||
}
|
||||
|
||||
private fun guessMainPackage(modules: List<ModuleDescriptor>): FqName? {
|
||||
if (modules.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val allPackages = modules.flatMap {
|
||||
it.getPackageFragments() // Includes also all parent packages, e.g. the root one.
|
||||
}
|
||||
|
||||
val nonEmptyPackages = allPackages
|
||||
.filter { it.getMemberScope().getContributedDescriptors().isNotEmpty() }
|
||||
.map { it.fqName }.distinct()
|
||||
|
||||
return allPackages.map { it.fqName }.distinct()
|
||||
.filter { candidate -> nonEmptyPackages.all { it.isSubpackageOf(candidate) } }
|
||||
// Now there are all common ancestors of non-empty packages. Longest of them is the least common accessor:
|
||||
.maxByOrNull { it.asString().length }
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
sealed class TypeBridge
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
object ReferenceBridge : TypeBridge()
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
data class BlockPointerBridge(
|
||||
val numberOfParameters: Int,
|
||||
val returnsVoid: Boolean,
|
||||
) : TypeBridge()
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
data class ValueTypeBridge(val objCValueType: ObjCValueType) : TypeBridge()
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
sealed class MethodBridgeParameter
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
sealed class MethodBridgeReceiver : MethodBridgeParameter() {
|
||||
object Static : MethodBridgeReceiver()
|
||||
object Factory : MethodBridgeReceiver()
|
||||
object Instance : MethodBridgeReceiver()
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
object MethodBridgeSelector : MethodBridgeParameter()
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
sealed class MethodBridgeValueParameter : MethodBridgeParameter() {
|
||||
data class Mapped(val bridge: TypeBridge) : MethodBridgeValueParameter()
|
||||
object ErrorOutParameter : MethodBridgeValueParameter()
|
||||
data class SuspendCompletion(val useUnitCompletion: Boolean) : MethodBridgeValueParameter()
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
data class MethodBridge(
|
||||
val returnBridge: ReturnValue,
|
||||
val receiver: MethodBridgeReceiver,
|
||||
val valueParameters: List<MethodBridgeValueParameter>,
|
||||
) {
|
||||
|
||||
sealed class ReturnValue {
|
||||
object Void : ReturnValue()
|
||||
object HashCode : ReturnValue()
|
||||
data class Mapped(val bridge: TypeBridge) : ReturnValue()
|
||||
sealed class Instance : ReturnValue() {
|
||||
object InitResult : Instance()
|
||||
object FactoryResult : Instance()
|
||||
}
|
||||
|
||||
sealed class WithError : ReturnValue() {
|
||||
object Success : WithError()
|
||||
data class ZeroForError(val successBridge: ReturnValue, val successMayBeZero: Boolean) : WithError()
|
||||
}
|
||||
|
||||
object Suspend : ReturnValue()
|
||||
}
|
||||
|
||||
val paramBridges: List<MethodBridgeParameter> =
|
||||
listOf(receiver) + MethodBridgeSelector + valueParameters
|
||||
|
||||
// TODO: it is not exactly true in potential future cases.
|
||||
val isInstance: Boolean
|
||||
get() = when (receiver) {
|
||||
MethodBridgeReceiver.Static,
|
||||
MethodBridgeReceiver.Factory,
|
||||
-> false
|
||||
|
||||
MethodBridgeReceiver.Instance -> true
|
||||
}
|
||||
|
||||
val returnsError: Boolean
|
||||
get() = returnBridge is ReturnValue.WithError
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun MethodBridge.valueParametersAssociated(
|
||||
descriptor: FunctionDescriptor,
|
||||
): List<Pair<MethodBridgeValueParameter, ParameterDescriptor?>> {
|
||||
val kotlinParameters = descriptor.allParameters.iterator()
|
||||
val skipFirstKotlinParameter = when (this.receiver) {
|
||||
MethodBridgeReceiver.Static -> false
|
||||
MethodBridgeReceiver.Factory, MethodBridgeReceiver.Instance -> true
|
||||
}
|
||||
if (skipFirstKotlinParameter) {
|
||||
kotlinParameters.next()
|
||||
}
|
||||
|
||||
return this.valueParameters.map {
|
||||
when (it) {
|
||||
is MethodBridgeValueParameter.Mapped -> it to kotlinParameters.next()
|
||||
|
||||
is MethodBridgeValueParameter.SuspendCompletion,
|
||||
is MethodBridgeValueParameter.ErrorOutParameter,
|
||||
-> it to null
|
||||
}
|
||||
}.also { assert(!kotlinParameters.hasNext()) }
|
||||
}
|
||||
+1563
File diff suppressed because it is too large
Load Diff
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
|
||||
internal class ObjCExportHeaderGeneratorImpl(
|
||||
moduleDescriptors: List<ModuleDescriptor>,
|
||||
mapper: ObjCExportMapper,
|
||||
namer: ObjCExportNamer,
|
||||
problemCollector: ObjCExportProblemCollector,
|
||||
objcGenerics: Boolean,
|
||||
override val shouldExportKDoc: Boolean,
|
||||
private val additionalImports: List<String>,
|
||||
) : ObjCExportHeaderGenerator(moduleDescriptors, mapper, namer, objcGenerics, problemCollector) {
|
||||
override fun getAdditionalImports(): List<String> =
|
||||
additionalImports
|
||||
}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.konan.isNativeStdlib
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
|
||||
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorResolver
|
||||
import org.jetbrains.kotlin.resolve.TypeResolver
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LocalRedeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
|
||||
import org.jetbrains.kotlin.types.error.ErrorUtils
|
||||
|
||||
interface ObjCExportLazy {
|
||||
interface Configuration {
|
||||
val frameworkName: String
|
||||
fun isIncluded(moduleInfo: ModuleInfo): Boolean
|
||||
fun getCompilerModuleName(moduleInfo: ModuleInfo): String
|
||||
val objcGenerics: Boolean
|
||||
|
||||
val disableSwiftMemberNameMangling: Boolean
|
||||
get() = false
|
||||
|
||||
val unitSuspendFunctionExport: UnitSuspendFunctionObjCExport
|
||||
val ignoreInterfaceMethodCollisions: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
fun generateBase(): List<ObjCTopLevel>
|
||||
|
||||
fun translate(file: KtFile): List<ObjCTopLevel>
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun createObjCExportLazy(
|
||||
configuration: ObjCExportLazy.Configuration,
|
||||
problemCollector: ObjCExportProblemCollector,
|
||||
codeAnalyzer: KotlinCodeAnalyzer,
|
||||
typeResolver: TypeResolver,
|
||||
descriptorResolver: DescriptorResolver,
|
||||
fileScopeProvider: FileScopeProvider,
|
||||
builtIns: KotlinBuiltIns,
|
||||
deprecationResolver: DeprecationResolver? = null,
|
||||
): ObjCExportLazy = ObjCExportLazyImpl(
|
||||
configuration,
|
||||
problemCollector,
|
||||
codeAnalyzer,
|
||||
typeResolver,
|
||||
descriptorResolver,
|
||||
fileScopeProvider,
|
||||
builtIns,
|
||||
deprecationResolver
|
||||
)
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
class ObjCExportLazyImpl(
|
||||
private val configuration: ObjCExportLazy.Configuration,
|
||||
problemCollector: ObjCExportProblemCollector,
|
||||
private val codeAnalyzer: KotlinCodeAnalyzer,
|
||||
private val typeResolver: TypeResolver,
|
||||
private val descriptorResolver: DescriptorResolver,
|
||||
private val fileScopeProvider: FileScopeProvider,
|
||||
builtIns: KotlinBuiltIns,
|
||||
deprecationResolver: DeprecationResolver?,
|
||||
) : ObjCExportLazy {
|
||||
|
||||
private val namerConfiguration = createNamerConfiguration(configuration)
|
||||
|
||||
private val nameTranslator: ObjCExportNameTranslator = ObjCExportNameTranslatorImpl(namerConfiguration)
|
||||
|
||||
private val mapper = ObjCExportMapper(deprecationResolver, local = true, configuration.unitSuspendFunctionExport)
|
||||
|
||||
private val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local = true)
|
||||
|
||||
private val translator: ObjCExportTranslator = ObjCExportTranslatorImpl(
|
||||
null,
|
||||
mapper,
|
||||
namer,
|
||||
problemCollector,
|
||||
configuration.objcGenerics
|
||||
)
|
||||
|
||||
private val isValid: Boolean
|
||||
get() = codeAnalyzer.moduleDescriptor.isValid
|
||||
|
||||
override fun generateBase() = translator.generateBaseDeclarations()
|
||||
|
||||
override fun translate(file: KtFile): List<ObjCTopLevel> =
|
||||
translateClasses(file) + translateTopLevels(file)
|
||||
|
||||
private fun translateClasses(container: KtDeclarationContainer): List<ObjCClass> {
|
||||
val result = mutableListOf<ObjCClass>()
|
||||
container.declarations.forEach { declaration ->
|
||||
// Supposed to be true if ObjCExportMapper.shouldBeVisible is true.
|
||||
if (declaration is KtClassOrObject && declaration.isPublic && declaration !is KtEnumEntry
|
||||
&& !declaration.hasExpectModifier()
|
||||
) {
|
||||
|
||||
if (!declaration.isAnnotation() && !declaration.hasModifier(KtTokens.INLINE_KEYWORD)) {
|
||||
result += translateClass(declaration)
|
||||
}
|
||||
|
||||
declaration.body?.let {
|
||||
result += translateClasses(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun translateClass(ktClassOrObject: KtClassOrObject): ObjCClass {
|
||||
val name = nameTranslator.getClassOrProtocolName(ktClassOrObject)
|
||||
|
||||
// Note: some attributes may be missing (e.g. "unavailable" for unexposed classes).
|
||||
|
||||
return if (ktClassOrObject.isInterface) {
|
||||
LazyObjCProtocolImpl(name, ktClassOrObject, this)
|
||||
} else {
|
||||
val isFinal = ktClassOrObject.modalityModifier() == null ||
|
||||
ktClassOrObject.hasModifier(KtTokens.FINAL_KEYWORD)
|
||||
|
||||
val attributes = if (isFinal) {
|
||||
listOf(OBJC_SUBCLASSING_RESTRICTED)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
LazyObjCInterfaceImpl(
|
||||
name,
|
||||
attributes,
|
||||
generics = translateGenerics(ktClassOrObject),
|
||||
psi = ktClassOrObject,
|
||||
lazy = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateGenerics(ktClassOrObject: KtClassOrObject): List<ObjCGenericTypeDeclaration> = if (configuration.objcGenerics) {
|
||||
ktClassOrObject.typeParametersWithOuter
|
||||
.map {
|
||||
ObjCGenericTypeRawDeclaration(
|
||||
nameTranslator.getTypeParameterName(it),
|
||||
ObjCVariance.fromKotlinVariance(it.variance)
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
private fun translateTopLevels(file: KtFile): List<ObjCInterface> {
|
||||
val extensions =
|
||||
mutableMapOf<ClassDescriptor, MutableList<KtCallableDeclaration>>()
|
||||
|
||||
val topLevel = mutableListOf<KtCallableDeclaration>()
|
||||
|
||||
file.children.filterIsInstance<KtCallableDeclaration>().forEach {
|
||||
// Supposed to be similar to ObjCExportMapper.shouldBeVisible.
|
||||
if ((it is KtFunction || it is KtProperty) && it.isPublic && !it.hasExpectModifier()) {
|
||||
val classDescriptor = getClassIfExtension(it)
|
||||
if (classDescriptor != null) {
|
||||
// If a class is hidden from Objective-C API then it is meaningless
|
||||
// to export its extensions.
|
||||
if (!classDescriptor.isHiddenFromObjC()) {
|
||||
extensions.getOrPut(classDescriptor, { mutableListOf() }) += it
|
||||
}
|
||||
} else {
|
||||
topLevel += it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = mutableListOf<ObjCInterface>()
|
||||
|
||||
extensions.mapTo(result) { (classDescriptor, declarations) ->
|
||||
translateExtensions(file, classDescriptor, declarations)
|
||||
}
|
||||
|
||||
if (topLevel.isNotEmpty()) result += translateFileClass(file, topLevel)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun translateFileClass(file: KtFile, declarations: List<KtCallableDeclaration>): ObjCInterface {
|
||||
val name = nameTranslator.getFileClassName(file)
|
||||
return LazyObjCFileInterface(name, file, declarations, this)
|
||||
}
|
||||
|
||||
private fun translateExtensions(
|
||||
file: KtFile,
|
||||
classDescriptor: ClassDescriptor,
|
||||
declarations: List<KtCallableDeclaration>,
|
||||
): ObjCInterface {
|
||||
// TODO: consider using file-based categories in compiler too.
|
||||
|
||||
val name = if (ErrorUtils.isError(classDescriptor)) {
|
||||
ObjCExportNamer.ClassOrProtocolName("ERROR", "ERROR")
|
||||
} else {
|
||||
namer.getClassOrProtocolName(classDescriptor)
|
||||
}
|
||||
|
||||
return LazyObjCExtensionInterface(name, nameTranslator.getCategoryName(file), classDescriptor, declarations, this)
|
||||
}
|
||||
|
||||
private fun resolveDeclaration(ktDeclaration: KtDeclaration): DeclarationDescriptor =
|
||||
codeAnalyzer.resolveToDescriptor(ktDeclaration)
|
||||
|
||||
private fun resolve(ktClassOrObject: KtClassOrObject) =
|
||||
resolveDeclaration(ktClassOrObject) as ClassDescriptor
|
||||
|
||||
private fun resolve(ktCallableDeclaration: KtCallableDeclaration) =
|
||||
resolveDeclaration(ktCallableDeclaration) as CallableMemberDescriptor
|
||||
|
||||
private fun getClassIfExtension(topLevelDeclaration: KtCallableDeclaration): ClassDescriptor? {
|
||||
val receiverType = topLevelDeclaration.receiverTypeReference ?: return null
|
||||
val fileScope = fileScopeProvider.getFileResolutionScope(topLevelDeclaration.containingKtFile)
|
||||
|
||||
val trace = BindingTraceContext() // TODO: revise.
|
||||
|
||||
val kotlinReceiverType = typeResolver.resolveType(
|
||||
createHeaderScope(topLevelDeclaration, fileScope, trace),
|
||||
receiverType,
|
||||
trace,
|
||||
checkBounds = false
|
||||
)
|
||||
|
||||
return translator.getClassIfExtension(kotlinReceiverType)
|
||||
}
|
||||
|
||||
private fun createHeaderScope(
|
||||
declaration: KtCallableDeclaration,
|
||||
parent: LexicalScope,
|
||||
trace: BindingTrace,
|
||||
): LexicalScope {
|
||||
if (declaration.typeParameters.isEmpty()) return parent
|
||||
|
||||
val fakeName = Name.special("<fake>")
|
||||
val sourceElement = SourceElement.NO_SOURCE
|
||||
|
||||
val descriptor: CallableMemberDescriptor
|
||||
val scopeKind: LexicalScopeKind
|
||||
|
||||
when (declaration) {
|
||||
is KtFunction -> {
|
||||
descriptor = SimpleFunctionDescriptorImpl.create(
|
||||
parent.ownerDescriptor,
|
||||
Annotations.EMPTY,
|
||||
fakeName,
|
||||
CallableMemberDescriptor.Kind.DECLARATION,
|
||||
sourceElement
|
||||
)
|
||||
scopeKind = LexicalScopeKind.FUNCTION_HEADER
|
||||
}
|
||||
is KtProperty -> {
|
||||
descriptor = PropertyDescriptorImpl.create(
|
||||
parent.ownerDescriptor,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
DescriptorVisibilities.PUBLIC,
|
||||
declaration.isVar,
|
||||
fakeName,
|
||||
CallableMemberDescriptor.Kind.DECLARATION,
|
||||
sourceElement,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
)
|
||||
scopeKind = LexicalScopeKind.PROPERTY_HEADER
|
||||
}
|
||||
else -> TODO("${declaration::class}")
|
||||
}
|
||||
|
||||
val result = LexicalWritableScope(
|
||||
parent,
|
||||
descriptor,
|
||||
false,
|
||||
LocalRedeclarationChecker.DO_NOTHING,
|
||||
scopeKind
|
||||
)
|
||||
|
||||
val typeParameters = descriptorResolver.resolveTypeParametersForDescriptor(
|
||||
descriptor,
|
||||
result,
|
||||
result,
|
||||
declaration.typeParameters,
|
||||
trace
|
||||
)
|
||||
|
||||
descriptorResolver.resolveGenericBounds(declaration, descriptor, result, typeParameters, trace)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private class LazyObjCProtocolImpl(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
private val psi: KtClassOrObject,
|
||||
private val lazy: ObjCExportLazyImpl,
|
||||
) : LazyObjCProtocol(name) {
|
||||
private val descriptor: ClassDescriptor by lazy { lazy.resolve(psi) }
|
||||
|
||||
override val origin: ObjCExportStubOrigin? by lazy { ObjCExportStubOrigin(descriptor) }
|
||||
|
||||
override fun computeRealStub(): ObjCProtocol = lazy.translator.translateInterface(descriptor)
|
||||
}
|
||||
|
||||
private class LazyObjCInterfaceImpl(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
attributes: List<String>,
|
||||
generics: List<ObjCGenericTypeDeclaration>,
|
||||
private val psi: KtClassOrObject,
|
||||
private val lazy: ObjCExportLazyImpl,
|
||||
) : LazyObjCInterface(name = name, generics = generics, categoryName = null, attributes = attributes) {
|
||||
private val descriptor: ClassDescriptor by lazy { lazy.resolve(psi) }
|
||||
|
||||
override val origin: ObjCExportStubOrigin? by lazy { ObjCExportStubOrigin(descriptor) }
|
||||
|
||||
override fun computeRealStub(): ObjCInterface = lazy.translator.translateClass(descriptor)
|
||||
}
|
||||
|
||||
private class LazyObjCFileInterface(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
private val file: KtFile,
|
||||
private val declarations: List<KtCallableDeclaration>,
|
||||
private val lazy: ObjCExportLazyImpl,
|
||||
) : LazyObjCInterface(name = name, generics = emptyList(), categoryName = null, attributes = listOf(OBJC_SUBCLASSING_RESTRICTED)) {
|
||||
|
||||
override val origin: Nothing? = null
|
||||
|
||||
override fun computeRealStub(): ObjCInterface = lazy.translator.translateFile(
|
||||
PsiSourceFile(file),
|
||||
declarations.mapNotNull { declaration ->
|
||||
lazy.resolve(declaration).takeIf { descriptor ->
|
||||
lazy.mapper.shouldBeExposed(descriptor)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private class LazyObjCExtensionInterface(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
categoryName: String,
|
||||
private val classDescriptor: ClassDescriptor,
|
||||
private val declarations: List<KtCallableDeclaration>,
|
||||
private val lazy: ObjCExportLazyImpl,
|
||||
) : LazyObjCInterface(name = name.objCName, generics = emptyList(), categoryName = categoryName, attributes = emptyList()) {
|
||||
|
||||
override val origin: ObjCExportStubOrigin? = ObjCExportStubOrigin(classDescriptor)
|
||||
|
||||
override fun computeRealStub(): ObjCInterface = lazy.translator.translateExtensions(
|
||||
classDescriptor,
|
||||
declarations.mapNotNull { declaration ->
|
||||
lazy.resolve(declaration).takeIf { lazy.mapper.shouldBeExposed(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class LazyObjCInterface(
|
||||
override val name: String,
|
||||
override val generics: List<ObjCGenericTypeDeclaration>,
|
||||
override val categoryName: String?,
|
||||
override val attributes: List<String>,
|
||||
) : ObjCInterface() {
|
||||
|
||||
constructor(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
generics: List<ObjCGenericTypeDeclaration>,
|
||||
categoryName: String?,
|
||||
attributes: List<String>,
|
||||
) : this(name.objCName, generics, categoryName, attributes + name.toNameAttributes())
|
||||
|
||||
protected abstract fun computeRealStub(): ObjCInterface
|
||||
|
||||
private val realStub by lazy { computeRealStub() }
|
||||
|
||||
override val members: List<ObjCExportStub>
|
||||
get() = realStub.members
|
||||
|
||||
override val superProtocols: List<String>
|
||||
get() = realStub.superProtocols
|
||||
|
||||
override val superClass: String?
|
||||
get() = realStub.superClass
|
||||
|
||||
override val superClassGenerics: List<ObjCNonNullReferenceType>
|
||||
get() = realStub.superClassGenerics
|
||||
|
||||
final override val comment: Nothing? = null
|
||||
}
|
||||
|
||||
private abstract class LazyObjCProtocol(
|
||||
name: ObjCExportNamer.ClassOrProtocolName,
|
||||
) : ObjCProtocol() {
|
||||
|
||||
override val name: String = name.objCName
|
||||
|
||||
override val comment: Nothing? = null
|
||||
|
||||
override val attributes: List<String> = name.toNameAttributes()
|
||||
|
||||
protected abstract fun computeRealStub(): ObjCProtocol
|
||||
|
||||
private val realStub by lazy { computeRealStub() }
|
||||
|
||||
override val members: List<ObjCExportStub>
|
||||
get() = realStub.members
|
||||
|
||||
override val superProtocols: List<String>
|
||||
get() = realStub.superProtocols
|
||||
}
|
||||
|
||||
internal fun createNamerConfiguration(configuration: ObjCExportLazy.Configuration): ObjCExportNamer.Configuration {
|
||||
return object : ObjCExportNamer.Configuration {
|
||||
override val topLevelNamePrefix = abbreviate(configuration.frameworkName)
|
||||
|
||||
override fun getAdditionalPrefix(module: ModuleDescriptor): String? {
|
||||
if (module.isNativeStdlib() || module.isCommonStdlibCheckSpecificallyForIDE()) return "Kotlin"
|
||||
|
||||
// Note: incorrect for compiler since it doesn't store ModuleInfo to ModuleDescriptor.
|
||||
val moduleInfo = module.getCapability(ModuleInfo.Capability) ?: return null
|
||||
if (configuration.isIncluded(moduleInfo)) return null
|
||||
return abbreviate(configuration.getCompilerModuleName(moduleInfo))
|
||||
}
|
||||
|
||||
override val objcGenerics = configuration.objcGenerics
|
||||
override val disableSwiftMemberNameMangling = configuration.disableSwiftMemberNameMangling
|
||||
|
||||
override val ignoreInterfaceMethodCollisions: Boolean = configuration.ignoreInterfaceMethodCollisions
|
||||
}
|
||||
}
|
||||
|
||||
private val kotlinSequenceClassId = ClassId.topLevel(FqName("kotlin.sequences.Sequence"))
|
||||
|
||||
// This is a special workaround needed for resolve in the IDE.
|
||||
private fun ModuleDescriptor.isCommonStdlibCheckSpecificallyForIDE() =
|
||||
findClassAcrossModuleDependencies(kotlinSequenceClassId)?.module == this
|
||||
|
||||
private val KtModifierListOwner.isPublic: Boolean
|
||||
get() = this.visibilityModifierTypeOrDefault() == KtTokens.PUBLIC_KEYWORD
|
||||
|
||||
internal val KtPureClassOrObject.isInterface: Boolean
|
||||
get() = this is KtClass && this.isInterface()
|
||||
|
||||
internal val KtClassOrObject.typeParametersWithOuter
|
||||
get() = generateSequence(this, { if (it is KtClass && it.isInner()) it.containingClassOrObject else null })
|
||||
.flatMap { it.typeParameters.asSequence() }
|
||||
+18
@@ -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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportLazy.dumpObjCHeader(files: Collection<KtFile>, outputFile: String, shouldExportKDoc: Boolean) {
|
||||
val lines = (this.generateBase() + files.flatMap { this.translate(it) })
|
||||
.flatMap { StubRenderer.render(it, shouldExportKDoc) + listOf("") }
|
||||
|
||||
File(outputFile).writeLines(lines)
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.builtins.*
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.ir.objcinterop.isObjCObjectType
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.DataClassResolver
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
class ObjCExportMapper(
|
||||
internal val deprecationResolver: DeprecationResolver? = null,
|
||||
private val local: Boolean = false,
|
||||
internal val unitSuspendFunctionExport: UnitSuspendFunctionObjCExport,
|
||||
) {
|
||||
fun getCustomTypeMapper(descriptor: ClassDescriptor): CustomTypeMapper? = CustomTypeMappers.getMapper(descriptor)
|
||||
|
||||
val hiddenTypes: Set<ClassId> get() = CustomTypeMappers.hiddenTypes
|
||||
|
||||
fun isSpecialMapped(descriptor: ClassDescriptor): Boolean {
|
||||
// TODO: this method duplicates some of the [ObjCExportTranslatorImpl.mapReferenceType] logic.
|
||||
return KotlinBuiltIns.isAny(descriptor) ||
|
||||
descriptor.getAllSuperClassifiers().any { it is ClassDescriptor && CustomTypeMappers.hasMapper(it) }
|
||||
}
|
||||
|
||||
private val methodBridgeCache = mutableMapOf<FunctionDescriptor, MethodBridge>()
|
||||
|
||||
fun bridgeMethod(descriptor: FunctionDescriptor): MethodBridge = if (local) {
|
||||
bridgeMethodImpl(descriptor)
|
||||
} else {
|
||||
methodBridgeCache.getOrPut(descriptor) {
|
||||
bridgeMethodImpl(descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.getClassIfCategory(descriptor: CallableMemberDescriptor): ClassDescriptor? {
|
||||
if (descriptor.dispatchReceiverParameter != null) return null
|
||||
|
||||
val extensionReceiverType = descriptor.extensionReceiverParameter?.type ?: return null
|
||||
|
||||
return getClassIfCategory(extensionReceiverType)
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.getClassIfCategory(extensionReceiverType: KotlinType): ClassDescriptor? {
|
||||
// FIXME: this code must rely on type mapping instead of copying its logic.
|
||||
|
||||
if (extensionReceiverType.isObjCObjectType()) return null
|
||||
|
||||
val erasedClass = extensionReceiverType.getErasedTypeClass()
|
||||
return if (!erasedClass.isInterface && !erasedClass.isInlined() && !this.isSpecialMapped(erasedClass)) {
|
||||
erasedClass
|
||||
} else {
|
||||
// E.g. receiver is protocol, or some type with custom mapping.
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSealedClassConstructor(descriptor: ConstructorDescriptor) = descriptor.constructedClass.isSealed()
|
||||
|
||||
/**
|
||||
* Check that given [method] is a synthetic .componentN() method of a data class.
|
||||
*/
|
||||
private fun isComponentNMethod(method: CallableMemberDescriptor): Boolean {
|
||||
if ((method as? FunctionDescriptor)?.isOperator != true) return false
|
||||
val parent = method.containingDeclaration
|
||||
if (parent is ClassDescriptor && parent.isData && DataClassResolver.isComponentLike(method.name)) {
|
||||
// componentN method of data class.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Note: partially duplicated in ObjCExportLazyImpl.translateTopLevels.
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportMapper.shouldBeExposed(descriptor: CallableMemberDescriptor): Boolean = when {
|
||||
!descriptor.isEffectivelyPublicApi -> false
|
||||
descriptor.isExpect -> false
|
||||
isHiddenByDeprecation(descriptor) -> false
|
||||
descriptor is ConstructorDescriptor && isSealedClassConstructor(descriptor) -> false
|
||||
// KT-42641. Don't expose componentN methods of data classes
|
||||
// because they are useless in Objective-C/Swift.
|
||||
isComponentNMethod(descriptor) && descriptor.overriddenDescriptors.isEmpty() -> false
|
||||
descriptor.isHiddenFromObjC() -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
private fun CallableMemberDescriptor.isHiddenFromObjC(): Boolean = when {
|
||||
// Note: the front-end checker requires all overridden descriptors to be either refined or not refined.
|
||||
overriddenDescriptors.isNotEmpty() -> overriddenDescriptors.first().isHiddenFromObjC()
|
||||
else -> annotations.any { annotation ->
|
||||
annotation.annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } == true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given class or its enclosing declaration is marked as @HiddenFromObjC.
|
||||
*/
|
||||
internal fun ClassDescriptor.isHiddenFromObjC(): Boolean = when {
|
||||
(this.containingDeclaration as? ClassDescriptor)?.isHiddenFromObjC() == true -> true
|
||||
else -> annotations.any { annotation ->
|
||||
annotation.annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } == true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Boolean =
|
||||
shouldBeVisible(descriptor) && !isSpecialMapped(descriptor) && !descriptor.defaultType.isObjCObjectType()
|
||||
|
||||
private fun ObjCExportMapper.isHiddenByDeprecation(descriptor: CallableMemberDescriptor): Boolean {
|
||||
// Note: ObjCExport generally expect overrides of exposed methods to be exposed.
|
||||
// So don't hide a "deprecated hidden" method which overrides non-hidden one:
|
||||
if (deprecationResolver != null && deprecationResolver.isDeprecatedHidden(descriptor) &&
|
||||
descriptor.overriddenDescriptors.all { isHiddenByDeprecation(it) }
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Note: ObjCExport expects members of unexposed classes to be unexposed too.
|
||||
// So hide a declaration if it is from a hidden class:
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ClassDescriptor && isHiddenByDeprecation(containingDeclaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.getDeprecation(descriptor: DeclarationDescriptor): DeprecationInfo? {
|
||||
deprecationResolver?.getDeprecations(descriptor).orEmpty().maxByOrNull {
|
||||
when (it.deprecationLevel) {
|
||||
DeprecationLevelValue.WARNING -> 1
|
||||
DeprecationLevelValue.ERROR -> 2
|
||||
DeprecationLevelValue.HIDDEN -> 3
|
||||
}
|
||||
}?.let { return it }
|
||||
|
||||
(descriptor as? ConstructorDescriptor)?.let {
|
||||
// Note: a deprecation can't be applied to a class itself when generating header
|
||||
// since the class can be referred from the header.
|
||||
// Apply class deprecations to its constructors instead:
|
||||
return getDeprecation(it.constructedClass)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun ObjCExportMapper.isHiddenByDeprecation(descriptor: ClassDescriptor): Boolean {
|
||||
if (deprecationResolver == null) return false
|
||||
if (deprecationResolver.isDeprecatedHidden(descriptor)) return true
|
||||
|
||||
// Note: ObjCExport requires super class of exposed class to be exposed.
|
||||
// So hide a class if its super class is hidden:
|
||||
val superClass = descriptor.getSuperClassNotAny()
|
||||
if (superClass != null && isHiddenByDeprecation(superClass)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Note: ObjCExport requires enclosing class of exposed class to be exposed.
|
||||
// Also in Kotlin hidden class members (including other classes) aren't directly accessible.
|
||||
// So hide a class if its enclosing class is hidden:
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ClassDescriptor && isHiddenByDeprecation(containingDeclaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Note: the logic is partially duplicated in ObjCExportLazyImpl.translateClasses.
|
||||
internal fun ObjCExportMapper.shouldBeVisible(descriptor: ClassDescriptor): Boolean =
|
||||
descriptor.isEffectivelyPublicApi && when (descriptor.kind) {
|
||||
ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true
|
||||
ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false
|
||||
} && !descriptor.isExpect && !descriptor.isInlined() && !isHiddenByDeprecation(descriptor) && !descriptor.isHiddenFromObjC()
|
||||
|
||||
private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean =
|
||||
descriptor.overriddenDescriptors.all { !shouldBeExposed(it) }
|
||||
// e.g. it is not `override`, or overrides only unexposed methods.
|
||||
|
||||
/**
|
||||
* Check that given [descriptor] is a so-called "base method", i.e. method
|
||||
* that doesn't override anything in a generated Objective-C interface.
|
||||
* Note that it does not mean that it has no "override" keyword.
|
||||
* Consider example:
|
||||
* ```kotlin
|
||||
* private interface I {
|
||||
* fun f()
|
||||
* }
|
||||
*
|
||||
* class C : I {
|
||||
* override fun f() {}
|
||||
* }
|
||||
* ```
|
||||
* Interface `I` is not exposed to the generated header, so C#f is considered to be a base method even though it has an "override" keyword.
|
||||
*/
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportMapper.isBaseMethod(descriptor: FunctionDescriptor) =
|
||||
this.isBase(descriptor)
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportMapper.getBaseMethods(descriptor: FunctionDescriptor): List<FunctionDescriptor> =
|
||||
if (isBaseMethod(descriptor)) {
|
||||
listOf(descriptor)
|
||||
} else {
|
||||
descriptor.overriddenDescriptors.filter { shouldBeExposed(it) }
|
||||
.flatMap { getBaseMethods(it.original) }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportMapper.isBaseProperty(descriptor: PropertyDescriptor) =
|
||||
isBase(descriptor)
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ObjCExportMapper.getBaseProperties(descriptor: PropertyDescriptor): List<PropertyDescriptor> =
|
||||
if (isBaseProperty(descriptor)) {
|
||||
listOf(descriptor)
|
||||
} else {
|
||||
descriptor.overriddenDescriptors
|
||||
.flatMap { getBaseProperties(it.original) }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
@Suppress("NO_TAIL_CALLS_FOUND", "NON_TAIL_RECURSIVE_CALL") // K2 warning suppression, TODO: KT-62472
|
||||
internal tailrec fun KotlinType.getErasedTypeClass(): ClassDescriptor =
|
||||
TypeUtils.getClassDescriptor(this) ?: this.constructor.supertypes.first().getErasedTypeClass()
|
||||
|
||||
internal fun ObjCExportMapper.isTopLevel(descriptor: CallableMemberDescriptor): Boolean =
|
||||
descriptor.containingDeclaration !is ClassDescriptor && this.getClassIfCategory(descriptor) == null
|
||||
|
||||
internal fun ObjCExportMapper.isObjCProperty(property: PropertyDescriptor): Boolean =
|
||||
property.extensionReceiverParameter == null || getClassIfCategory(property) != null
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ClassDescriptor.getEnumValuesFunctionDescriptor(): SimpleFunctionDescriptor? {
|
||||
require(this.kind == ClassKind.ENUM_CLASS)
|
||||
|
||||
return this.staticScope.getContributedFunctions(
|
||||
StandardNames.ENUM_VALUES,
|
||||
NoLookupLocation.FROM_BACKEND
|
||||
).singleOrNull { it.extensionReceiverParameter == null && it.valueParameters.size == 0 }
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
fun ClassDescriptor.getEnumEntriesPropertyDescriptor(): PropertyDescriptor? {
|
||||
require(this.kind == ClassKind.ENUM_CLASS)
|
||||
|
||||
return this.staticScope.getContributedVariables(
|
||||
StandardNames.ENUM_ENTRIES,
|
||||
NoLookupLocation.FROM_BACKEND
|
||||
).singleOrNull { it.extensionReceiverParameter == null }
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.doesThrow(method: FunctionDescriptor): Boolean = method.allOverriddenDescriptors.any {
|
||||
it.overriddenDescriptors.isEmpty() && it.annotations.hasAnnotation(KonanFqNames.throws)
|
||||
}
|
||||
|
||||
private fun ObjCExportMapper.bridgeType(
|
||||
kotlinType: KotlinType,
|
||||
): TypeBridge = kotlinType.unwrapToPrimitiveOrReference<TypeBridge>(
|
||||
eachInlinedClass = { inlinedClass, _ ->
|
||||
when (inlinedClass.classId) {
|
||||
UnsignedType.UBYTE.classId -> return ValueTypeBridge(ObjCValueType.UNSIGNED_CHAR)
|
||||
UnsignedType.USHORT.classId -> return ValueTypeBridge(ObjCValueType.UNSIGNED_SHORT)
|
||||
UnsignedType.UINT.classId -> return ValueTypeBridge(ObjCValueType.UNSIGNED_INT)
|
||||
UnsignedType.ULONG.classId -> return ValueTypeBridge(ObjCValueType.UNSIGNED_LONG_LONG)
|
||||
}
|
||||
},
|
||||
ifPrimitive = { primitiveType, _ ->
|
||||
val objCValueType = when (primitiveType) {
|
||||
KonanPrimitiveType.BOOLEAN -> ObjCValueType.BOOL
|
||||
KonanPrimitiveType.CHAR -> ObjCValueType.UNICHAR
|
||||
KonanPrimitiveType.BYTE -> ObjCValueType.CHAR
|
||||
KonanPrimitiveType.SHORT -> ObjCValueType.SHORT
|
||||
KonanPrimitiveType.INT -> ObjCValueType.INT
|
||||
KonanPrimitiveType.LONG -> ObjCValueType.LONG_LONG
|
||||
KonanPrimitiveType.FLOAT -> ObjCValueType.FLOAT
|
||||
KonanPrimitiveType.DOUBLE -> ObjCValueType.DOUBLE
|
||||
KonanPrimitiveType.NON_NULL_NATIVE_PTR -> ObjCValueType.POINTER
|
||||
KonanPrimitiveType.VECTOR128 -> TODO()
|
||||
}
|
||||
ValueTypeBridge(objCValueType)
|
||||
},
|
||||
ifReference = {
|
||||
if (kotlinType.isFunctionType) {
|
||||
bridgeFunctionType(kotlinType)
|
||||
} else {
|
||||
ReferenceBridge
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun ObjCExportMapper.bridgeFunctionType(kotlinType: KotlinType): TypeBridge {
|
||||
// kotlinType.arguments include return type: <P1, P2, ..., Pn, R>
|
||||
val numberOfParameters = kotlinType.arguments.size - 1
|
||||
|
||||
val returnType = kotlinType.getReturnTypeFromFunctionType()
|
||||
val returnsVoid = returnType.isUnit() || returnType.isNothing()
|
||||
// Note: this is correct because overriding method can't turn this into false
|
||||
// neither for a parameter nor for a return type.
|
||||
|
||||
return BlockPointerBridge(numberOfParameters, returnsVoid)
|
||||
}
|
||||
|
||||
private fun ObjCExportMapper.bridgeParameter(parameter: ParameterDescriptor): MethodBridgeValueParameter =
|
||||
MethodBridgeValueParameter.Mapped(bridgeType(parameter.type))
|
||||
|
||||
private fun ObjCExportMapper.bridgeReturnType(
|
||||
descriptor: FunctionDescriptor,
|
||||
convertExceptionsToErrors: Boolean,
|
||||
): MethodBridge.ReturnValue {
|
||||
val returnType = descriptor.returnType!!
|
||||
return when {
|
||||
descriptor.isSuspend -> MethodBridge.ReturnValue.Suspend
|
||||
|
||||
descriptor is ConstructorDescriptor -> if (descriptor.constructedClass.isArray) {
|
||||
MethodBridge.ReturnValue.Instance.FactoryResult
|
||||
} else {
|
||||
MethodBridge.ReturnValue.Instance.InitResult
|
||||
}.let {
|
||||
if (convertExceptionsToErrors) {
|
||||
MethodBridge.ReturnValue.WithError.ZeroForError(it, successMayBeZero = false)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } &&
|
||||
descriptor.name.asString() == "hashCode" -> {
|
||||
assert(!convertExceptionsToErrors)
|
||||
MethodBridge.ReturnValue.HashCode
|
||||
}
|
||||
|
||||
descriptor is PropertyGetterDescriptor -> {
|
||||
assert(!convertExceptionsToErrors)
|
||||
MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty))
|
||||
}
|
||||
|
||||
returnType.isUnit() || returnType.isNothing() -> if (convertExceptionsToErrors) {
|
||||
MethodBridge.ReturnValue.WithError.Success
|
||||
} else {
|
||||
MethodBridge.ReturnValue.Void
|
||||
}
|
||||
|
||||
else -> {
|
||||
val returnTypeBridge = bridgeType(returnType)
|
||||
val successReturnValueBridge = MethodBridge.ReturnValue.Mapped(returnTypeBridge)
|
||||
if (convertExceptionsToErrors) {
|
||||
val canReturnZero = !returnTypeBridge.isReferenceOrPointer() || TypeUtils.isNullableType(returnType)
|
||||
MethodBridge.ReturnValue.WithError.ZeroForError(
|
||||
successReturnValueBridge,
|
||||
successMayBeZero = canReturnZero
|
||||
)
|
||||
} else {
|
||||
successReturnValueBridge
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TypeBridge.isReferenceOrPointer(): Boolean = when (this) {
|
||||
ReferenceBridge, is BlockPointerBridge -> true
|
||||
is ValueTypeBridge -> this.objCValueType == ObjCValueType.POINTER
|
||||
}
|
||||
|
||||
private fun ObjCExportMapper.bridgeMethodImpl(descriptor: FunctionDescriptor): MethodBridge {
|
||||
assert(isBaseMethod(descriptor))
|
||||
|
||||
val convertExceptionsToErrors = this.doesThrow(descriptor)
|
||||
|
||||
val kotlinParameters = descriptor.allParameters.iterator()
|
||||
|
||||
val isTopLevel = isTopLevel(descriptor)
|
||||
|
||||
val receiver = if (descriptor is ConstructorDescriptor && descriptor.constructedClass.isArray) {
|
||||
kotlinParameters.next()
|
||||
MethodBridgeReceiver.Factory
|
||||
} else if (isTopLevel) {
|
||||
MethodBridgeReceiver.Static
|
||||
} else {
|
||||
kotlinParameters.next()
|
||||
MethodBridgeReceiver.Instance
|
||||
}
|
||||
|
||||
val valueParameters = mutableListOf<MethodBridgeValueParameter>()
|
||||
kotlinParameters.forEach {
|
||||
valueParameters += bridgeParameter(it)
|
||||
}
|
||||
|
||||
val returnBridge = bridgeReturnType(descriptor, convertExceptionsToErrors)
|
||||
|
||||
if (descriptor.isSuspend) {
|
||||
val useUnitCompletion = (unitSuspendFunctionExport == UnitSuspendFunctionObjCExport.PROPER) && (descriptor.returnType!!.isUnit())
|
||||
valueParameters += MethodBridgeValueParameter.SuspendCompletion(useUnitCompletion)
|
||||
} else if (convertExceptionsToErrors) {
|
||||
// Add error out parameter before tail block parameters. The convention allows this.
|
||||
// Placing it after would trigger https://bugs.swift.org/browse/SR-12201
|
||||
// (see also https://github.com/JetBrains/kotlin-native/issues/3825).
|
||||
val tailBlocksCount = valueParameters.reversed().takeWhile { it.isBlockPointer() }.count()
|
||||
valueParameters.add(valueParameters.size - tailBlocksCount, MethodBridgeValueParameter.ErrorOutParameter)
|
||||
}
|
||||
|
||||
return MethodBridge(returnBridge, receiver, valueParameters)
|
||||
}
|
||||
|
||||
private fun MethodBridgeValueParameter.isBlockPointer(): Boolean = when (this) {
|
||||
is MethodBridgeValueParameter.Mapped -> when (this.bridge) {
|
||||
ReferenceBridge, is ValueTypeBridge -> false
|
||||
is BlockPointerBridge -> true
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> false
|
||||
is MethodBridgeValueParameter.SuspendCompletion -> true
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor): TypeBridge {
|
||||
assert(isBaseProperty(descriptor))
|
||||
|
||||
return bridgeType(descriptor.type)
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
enum class NSNumberKind(val mappedKotlinClassId: ClassId?, val objCType: ObjCType) {
|
||||
CHAR(PrimitiveType.BYTE, ObjCPrimitiveType.char),
|
||||
UNSIGNED_CHAR(UnsignedType.UBYTE, ObjCPrimitiveType.unsigned_char),
|
||||
SHORT(PrimitiveType.SHORT, ObjCPrimitiveType.short),
|
||||
UNSIGNED_SHORT(UnsignedType.USHORT, ObjCPrimitiveType.unsigned_short),
|
||||
INT(PrimitiveType.INT, ObjCPrimitiveType.int),
|
||||
UNSIGNED_INT(UnsignedType.UINT, ObjCPrimitiveType.unsigned_int),
|
||||
LONG(ObjCPrimitiveType.long),
|
||||
UNSIGNED_LONG(ObjCPrimitiveType.unsigned_long),
|
||||
LONG_LONG(PrimitiveType.LONG, ObjCPrimitiveType.long_long),
|
||||
UNSIGNED_LONG_LONG(UnsignedType.ULONG, ObjCPrimitiveType.unsigned_long_long),
|
||||
FLOAT(PrimitiveType.FLOAT, ObjCPrimitiveType.float),
|
||||
DOUBLE(PrimitiveType.DOUBLE, ObjCPrimitiveType.double),
|
||||
BOOL(PrimitiveType.BOOLEAN, ObjCPrimitiveType.BOOL),
|
||||
INTEGER(ObjCPrimitiveType.NSInteger),
|
||||
UNSIGNED_INTEGER(ObjCPrimitiveType.NSUInteger)
|
||||
|
||||
;
|
||||
|
||||
// UNSIGNED_SHORT -> unsignedShort
|
||||
private val kindName = this.name.split('_')
|
||||
.joinToString("") { it.lowercase().replaceFirstChar(Char::uppercaseChar) }.replaceFirstChar(Char::lowercaseChar)
|
||||
|
||||
|
||||
val valueSelector = kindName // unsignedShort
|
||||
val initSelector = "initWith${kindName.replaceFirstChar(Char::uppercaseChar)}:" // initWithUnsignedShort:
|
||||
val factorySelector = "numberWith${kindName.replaceFirstChar(Char::uppercaseChar)}:" // numberWithUnsignedShort:
|
||||
|
||||
constructor(
|
||||
primitiveType: PrimitiveType,
|
||||
objCPrimitiveType: ObjCPrimitiveType,
|
||||
) : this(ClassId.topLevel(primitiveType.typeFqName), objCPrimitiveType)
|
||||
|
||||
constructor(
|
||||
unsignedType: UnsignedType,
|
||||
objCPrimitiveType: ObjCPrimitiveType,
|
||||
) : this(unsignedType.classId, objCPrimitiveType)
|
||||
|
||||
constructor(objCPrimitiveType: ObjCPrimitiveType) : this(null, objCPrimitiveType)
|
||||
}
|
||||
+1141
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
interface ObjCExportScope {
|
||||
class RecursionBreachException : Exception {
|
||||
constructor(type: KotlinType) : super("$type was already encountered during type mapping process.")
|
||||
}
|
||||
|
||||
val parent: ObjCExportScope?
|
||||
get() = null
|
||||
|
||||
fun deriveForType(kotlinType: KotlinType): ObjCTypeExportScope = ObjCTypeExportScopeImpl(kotlinType, this)
|
||||
fun deriveForClass(container: DeclarationDescriptor, namer: ObjCExportNamer): ObjCClassExportScope =
|
||||
ObjCClassExportScopeImpl(container, namer, this)
|
||||
}
|
||||
|
||||
internal inline fun <reified T : ObjCExportScope> ObjCExportScope.nearestScopeOfType(): T? {
|
||||
var parent: ObjCExportScope? = this
|
||||
while (parent != null) {
|
||||
if (parent is T) {
|
||||
return parent
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
object ObjCRootExportScope : ObjCExportScope
|
||||
|
||||
interface ObjCClassExportScope : ObjCExportScope {
|
||||
fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage?
|
||||
}
|
||||
|
||||
private class ObjCClassExportScopeImpl constructor(
|
||||
container: DeclarationDescriptor,
|
||||
val namer: ObjCExportNamer,
|
||||
override val parent: ObjCExportScope?,
|
||||
) : ObjCClassExportScope {
|
||||
private val typeParameterNames: List<TypeParameterDescriptor> =
|
||||
if (container is ClassDescriptor && !container.isInterface) {
|
||||
container.typeConstructor.parameters
|
||||
} else {
|
||||
emptyList<TypeParameterDescriptor>()
|
||||
}
|
||||
|
||||
override fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage? {
|
||||
return typeParameterDescriptor?.let { descriptor ->
|
||||
typeParameterNames.firstOrNull {
|
||||
it == descriptor || it.isCapturedFromOuterDeclaration && it.original == descriptor
|
||||
}?.let {
|
||||
ObjCGenericTypeParameterUsage(it, namer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjCTypeExportScope : ObjCExportScope {
|
||||
val kotlinType: KotlinType
|
||||
}
|
||||
|
||||
private class ObjCTypeExportScopeImpl(override val kotlinType: KotlinType, override val parent: ObjCExportScope?) : ObjCTypeExportScope {
|
||||
init {
|
||||
var parent = this.parent
|
||||
while (parent != null && parent is ObjCTypeExportScope) {
|
||||
if (parent.kotlinType == kotlinType)
|
||||
throw ObjCExportScope.RecursionBreachException(kotlinType)
|
||||
parent = parent.parent
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.extractSerializedKdocString
|
||||
import org.jetbrains.kotlin.backend.common.serialization.metadata.findKDocString
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
||||
|
||||
fun ObjCExportStubOrigin(descriptor: DeclarationDescriptor?): ObjCExportStubOrigin? {
|
||||
if (descriptor == null) return null
|
||||
|
||||
if (descriptor is DeclarationDescriptorWithSource) {
|
||||
return ObjCExportStubOrigin.Source(descriptor.name, descriptor.findKDocString(), (descriptor.source as? PsiSourceElement)?.psi)
|
||||
}
|
||||
|
||||
assert(descriptor is DeserializedDescriptor) { "Expected '$descriptor' to implement ${DeserializedDescriptor::class.simpleName}" }
|
||||
return ObjCExportStubOrigin.Binary(descriptor.name, descriptor.extractSerializedKdocString())
|
||||
}
|
||||
|
||||
fun ObjCProtocolImpl(
|
||||
name: String,
|
||||
descriptor: ClassDescriptor,
|
||||
superProtocols: List<String>,
|
||||
members: List<ObjCExportStub>,
|
||||
attributes: List<String> = emptyList(),
|
||||
comment: ObjCComment? = null,
|
||||
) = ObjCProtocolImpl(
|
||||
name = name,
|
||||
comment = comment,
|
||||
origin = ObjCExportStubOrigin(descriptor),
|
||||
attributes = attributes,
|
||||
superProtocols = superProtocols,
|
||||
members = members
|
||||
)
|
||||
|
||||
fun ObjCInterfaceImpl(
|
||||
name: String,
|
||||
generics: List<ObjCGenericTypeDeclaration> = emptyList(),
|
||||
descriptor: ClassDescriptor? = null,
|
||||
superClass: String? = null,
|
||||
superClassGenerics: List<ObjCNonNullReferenceType> = emptyList(),
|
||||
superProtocols: List<String> = emptyList(),
|
||||
categoryName: String? = null,
|
||||
members: List<ObjCExportStub> = emptyList(),
|
||||
attributes: List<String> = emptyList(),
|
||||
comment: ObjCComment? = null,
|
||||
) = ObjCInterfaceImpl(
|
||||
name = name,
|
||||
comment = comment,
|
||||
origin = ObjCExportStubOrigin(descriptor),
|
||||
attributes = attributes,
|
||||
superProtocols = superProtocols,
|
||||
members = members,
|
||||
categoryName = categoryName,
|
||||
generics = generics,
|
||||
superClass = superClass,
|
||||
superClassGenerics = superClassGenerics
|
||||
)
|
||||
|
||||
fun ObjCMethod(
|
||||
descriptor: DeclarationDescriptor?,
|
||||
isInstanceMethod: Boolean,
|
||||
returnType: ObjCType,
|
||||
selectors: List<String>,
|
||||
parameters: List<ObjCParameter>,
|
||||
attributes: List<String>,
|
||||
comment: ObjCComment? = null,
|
||||
) = ObjCMethod(
|
||||
comment = comment,
|
||||
origin = ObjCExportStubOrigin(descriptor),
|
||||
isInstanceMethod = isInstanceMethod,
|
||||
returnType = returnType,
|
||||
selectors = selectors,
|
||||
parameters = parameters,
|
||||
attributes = attributes
|
||||
)
|
||||
|
||||
fun ObjCParameter(
|
||||
name: String,
|
||||
descriptor: ParameterDescriptor?,
|
||||
type: ObjCType,
|
||||
) = ObjCParameter(
|
||||
name = name,
|
||||
origin = ObjCExportStubOrigin(descriptor),
|
||||
type = type,
|
||||
todo = null
|
||||
)
|
||||
|
||||
fun ObjCProperty(
|
||||
name: String,
|
||||
descriptor: DeclarationDescriptorWithSource?,
|
||||
type: ObjCType,
|
||||
propertyAttributes: List<String>,
|
||||
setterName: String? = null,
|
||||
getterName: String? = null,
|
||||
declarationAttributes: List<String> = emptyList(),
|
||||
comment: ObjCComment? = null,
|
||||
) = ObjCProperty(
|
||||
name = name,
|
||||
comment = comment,
|
||||
origin = ObjCExportStubOrigin(descriptor),
|
||||
type = type,
|
||||
propertyAttributes = propertyAttributes,
|
||||
setterName = setterName,
|
||||
getterName = getterName,
|
||||
declarationAttributes = declarationAttributes
|
||||
)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
|
||||
class ObjCExportTranslatorMobile internal constructor(private val delegate: ObjCExportTranslatorImpl) : ObjCExportTranslator by delegate {
|
||||
companion object {
|
||||
fun create(namer: ObjCExportNamer, configuration: ObjCExportLazy.Configuration): ObjCExportTranslatorMobile {
|
||||
val mapper = ObjCExportMapper(local = true, unitSuspendFunctionExport = configuration.unitSuspendFunctionExport)
|
||||
return ObjCExportTranslatorMobile(
|
||||
ObjCExportTranslatorImpl(
|
||||
null,
|
||||
mapper,
|
||||
namer,
|
||||
ObjCExportProblemCollector.SILENT,
|
||||
configuration.objcGenerics
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun translateBaseFunction(descriptor: FunctionDescriptor): ObjCMethod {
|
||||
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor
|
||||
val scope = classDescriptor?.let { delegate.createGenericExportScope(it) } ?: ObjCRootExportScope
|
||||
return delegate.buildMethod(descriptor, descriptor, scope)
|
||||
}
|
||||
}
|
||||
+23
@@ -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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
|
||||
fun ObjCGenericTypeParameterUsage(
|
||||
typeParameterDescriptor: TypeParameterDescriptor,
|
||||
namer: ObjCExportNamer,
|
||||
) = ObjCGenericTypeParameterUsage(
|
||||
typeName = namer.getTypeParameterName(typeParameterDescriptor)
|
||||
)
|
||||
|
||||
fun ObjCGenericTypeParameterDeclaration(
|
||||
typeParameterDescriptor: TypeParameterDescriptor,
|
||||
namer: ObjCExportNamer,
|
||||
) = ObjCGenericTypeParameterDeclaration(
|
||||
typeName = namer.getTypeParameterName(typeParameterDescriptor),
|
||||
variance = ObjCVariance.fromKotlinVariance(typeParameterDescriptor.variance)
|
||||
)
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SourceFile
|
||||
|
||||
@InternalKotlinNativeApi
|
||||
class ObjCExportedInterface internal constructor(
|
||||
val generatedClasses: Set<ClassDescriptor>,
|
||||
val categoryMembers: Map<ClassDescriptor, List<CallableMemberDescriptor>>,
|
||||
val topLevel: Map<SourceFile, List<CallableMemberDescriptor>>,
|
||||
val headerLines: List<String>,
|
||||
val namer: ObjCExportNamer,
|
||||
val mapper: ObjCExportMapper,
|
||||
)
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.konan.file.File
|
||||
|
||||
// For now, the object is pretty dumb.
|
||||
// Later it will accept an object with ObjC declarations instead of lines.
|
||||
class ObjCHeaderWriter {
|
||||
fun write(
|
||||
headerName: String,
|
||||
headerLines: List<String>,
|
||||
headersDirectory: File,
|
||||
) {
|
||||
headersDirectory.child(headerName).writeLines(headerLines)
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
|
||||
class ObjcExportHeaderGeneratorMobile internal constructor(
|
||||
moduleDescriptors: List<ModuleDescriptor>,
|
||||
mapper: ObjCExportMapper,
|
||||
namer: ObjCExportNamer,
|
||||
problemCollector: ObjCExportProblemCollector,
|
||||
objcGenerics: Boolean,
|
||||
private val restrictToLocalModules: Boolean,
|
||||
) : ObjCExportHeaderGenerator(moduleDescriptors, mapper, namer, objcGenerics, problemCollector) {
|
||||
|
||||
companion object {
|
||||
fun createInstance(
|
||||
configuration: ObjCExportLazy.Configuration,
|
||||
problemCollector: ObjCExportProblemCollector,
|
||||
builtIns: KotlinBuiltIns,
|
||||
moduleDescriptors: List<ModuleDescriptor>,
|
||||
deprecationResolver: DeprecationResolver? = null,
|
||||
local: Boolean = false,
|
||||
restrictToLocalModules: Boolean = false,
|
||||
): ObjCExportHeaderGenerator {
|
||||
val mapper = ObjCExportMapper(deprecationResolver, local, configuration.unitSuspendFunctionExport)
|
||||
val namerConfiguration = createNamerConfiguration(configuration)
|
||||
val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local)
|
||||
|
||||
return ObjcExportHeaderGeneratorMobile(
|
||||
moduleDescriptors,
|
||||
mapper,
|
||||
namer,
|
||||
problemCollector,
|
||||
configuration.objcGenerics,
|
||||
restrictToLocalModules
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldTranslateExtraClass(descriptor: ClassDescriptor): Boolean =
|
||||
!restrictToLocalModules || descriptor.module in moduleDescriptors
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.backend.konan.objcexport
|
||||
|
||||
internal class StubBuilder<S : ObjCExportStub>(private val problemCollector: ObjCExportProblemCollector) {
|
||||
private val children = mutableListOf<S>()
|
||||
|
||||
inline fun add(provider: () -> S) {
|
||||
try {
|
||||
children.add(provider())
|
||||
} catch (t: Throwable) {
|
||||
problemCollector.reportException(t)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plusAssign(set: Collection<S>) {
|
||||
children += set
|
||||
}
|
||||
|
||||
fun build(): List<S> = children
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.backend.konan.testUtils
|
||||
|
||||
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
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.ParameterContext
|
||||
import org.junit.jupiter.api.extension.ParameterResolver
|
||||
import java.io.File
|
||||
|
||||
|
||||
class Fe10HeaderGeneratorExtension : ParameterResolver, AfterEachCallback {
|
||||
|
||||
companion object {
|
||||
val namespace = ExtensionContext.Namespace.create(Fe10HeaderGeneratorExtension::class)
|
||||
val disposableKey = "disposable"
|
||||
}
|
||||
|
||||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
|
||||
return parameterContext.parameter.type == ObjCExportHeaderGeneratorTest.HeaderGenerator::class.java
|
||||
}
|
||||
|
||||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
|
||||
val disposable = Disposer.newDisposable()
|
||||
extensionContext.getStore(namespace).put(disposableKey, disposable)
|
||||
return Fe10HeaderGeneratorImpl(disposable)
|
||||
}
|
||||
|
||||
override fun afterEach(context: ExtensionContext) {
|
||||
val disposable = context.getStore(namespace).get(disposableKey, Disposable::class.java) ?: return
|
||||
Disposer.dispose(disposable)
|
||||
}
|
||||
}
|
||||
|
||||
private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) :
|
||||
ObjCExportHeaderGeneratorTest.HeaderGenerator {
|
||||
override fun generateHeaders(root: File): String {
|
||||
val headerGenerator = createObjCExportHeaderGenerator(disposable, root)
|
||||
headerGenerator.translateModuleDeclarations()
|
||||
return headerGenerator.build().joinToString(System.lineSeparator())
|
||||
}
|
||||
|
||||
private fun createObjCExportHeaderGenerator(disposable: Disposable, root: File): ObjCExportHeaderGenerator {
|
||||
val mapper = ObjCExportMapper(
|
||||
unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT
|
||||
)
|
||||
|
||||
val namer = ObjCExportNamerImpl(
|
||||
mapper = mapper,
|
||||
builtIns = DefaultBuiltIns.Instance,
|
||||
local = false,
|
||||
problemCollector = ObjCExportProblemCollector.SILENT,
|
||||
configuration = object : ObjCExportNamer.Configuration {
|
||||
override val topLevelNamePrefix: String get() = ""
|
||||
override fun getAdditionalPrefix(module: ModuleDescriptor): String? = null
|
||||
override val objcGenerics: Boolean = true
|
||||
}
|
||||
)
|
||||
|
||||
val environment: KotlinCoreEnvironment = createKotlinCoreEnvironment(disposable)
|
||||
|
||||
val kotlinFiles = root.walkTopDown().filter { it.isFile }.filter { it.extension == "kt" }.toList()
|
||||
|
||||
return ObjCExportHeaderGeneratorImpl(
|
||||
moduleDescriptors = listOf(createModuleDescriptor(environment, kotlinFiles)),
|
||||
mapper = mapper,
|
||||
namer = namer,
|
||||
problemCollector = ObjCExportProblemCollector.SILENT,
|
||||
objcGenerics = true,
|
||||
shouldExportKDoc = true,
|
||||
additionalImports = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.backend.konan.testUtils
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer
|
||||
import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory
|
||||
import org.jetbrains.kotlin.backend.common.serialization.metadata.DynamicTypeDeserializer
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.createFlexiblePhaseConfig
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.library.metadata.KlibMetadataFactories
|
||||
import org.jetbrains.kotlin.library.resolveSingleFileKlib
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.platform.CommonPlatforms
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.konan.NativePlatforms
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.resolve.CompilerEnvironment
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
import org.jetbrains.kotlin.resolve.konan.platform.NativePlatformAnalyzerServices
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||
import java.io.File
|
||||
|
||||
fun createModuleDescriptor(
|
||||
environment: KotlinCoreEnvironment,
|
||||
tempDir: File,
|
||||
kotlinSources: List<String>,
|
||||
): ModuleDescriptor {
|
||||
val testModuleRoot = tempDir.resolve("testModule")
|
||||
testModuleRoot.mkdirs()
|
||||
val testSourcesFiles = kotlinSources.mapIndexed { index, kotlinSource ->
|
||||
testModuleRoot.resolve("TestSources$index.kt").apply {
|
||||
writeText(kotlinSource)
|
||||
}
|
||||
}
|
||||
return createModuleDescriptor(environment, testSourcesFiles)
|
||||
}
|
||||
|
||||
fun createModuleDescriptor(
|
||||
environment: KotlinCoreEnvironment,
|
||||
kotlinFiles: List<File>,
|
||||
): ModuleDescriptor {
|
||||
val psiFactory = KtPsiFactory(environment.project)
|
||||
val kotlinPsiFiles = kotlinFiles.map { file -> psiFactory.createFile(file.name, KtTestUtil.doLoadFile(file)) }
|
||||
|
||||
val analysisResult = CommonResolverForModuleFactory.analyzeFiles(
|
||||
files = kotlinPsiFiles,
|
||||
moduleName = Name.special("<test_module>"),
|
||||
dependOnBuiltIns = true,
|
||||
languageVersionSettings = environment.configuration.languageVersionSettings,
|
||||
targetPlatform = CommonPlatforms.defaultCommonPlatform,
|
||||
targetEnvironment = CompilerEnvironment,
|
||||
dependenciesContainer = DependenciesContainerImpl,
|
||||
) { content ->
|
||||
environment.createPackagePartProvider(content.moduleContentScope)
|
||||
}
|
||||
|
||||
return analysisResult.moduleDescriptor
|
||||
}
|
||||
|
||||
fun createKotlinCoreEnvironment(
|
||||
disposable: Disposable, compilerConfiguration: CompilerConfiguration = createCompilerConfiguration(),
|
||||
): KotlinCoreEnvironment {
|
||||
return KotlinCoreEnvironment.createForTests(
|
||||
parentDisposable = disposable,
|
||||
initialConfiguration = compilerConfiguration,
|
||||
extensionConfigs = EnvironmentConfigFiles.METADATA_CONFIG_FILES
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCompilerConfiguration(): CompilerConfiguration {
|
||||
val configuration = KotlinTestUtils.newConfiguration()
|
||||
configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, createLanguageVersionSettings())
|
||||
configuration.put(CLIConfigurationKeys.FLEXIBLE_PHASE_CONFIG, createFlexiblePhaseConfig(K2NativeCompilerArguments()))
|
||||
return configuration
|
||||
}
|
||||
|
||||
private fun createLanguageVersionSettings() = LanguageVersionSettingsImpl(
|
||||
languageVersion = LanguageVersion.LATEST_STABLE,
|
||||
apiVersion = ApiVersion.LATEST_STABLE
|
||||
)
|
||||
|
||||
private object DependenciesContainerImpl : CommonDependenciesContainer {
|
||||
override val moduleInfos: List<ModuleInfo> get() = listOf(DefaultBuiltInsModuleInfo, KotlinNativeStdlibModuleInfo)
|
||||
override val friendModuleInfos: List<ModuleInfo> get() = emptyList()
|
||||
override val refinesModuleInfos: List<ModuleInfo> get() = emptyList()
|
||||
override fun registerDependencyForAllModules(moduleInfo: ModuleInfo, descriptorForModule: ModuleDescriptorImpl) = Unit
|
||||
override fun packageFragmentProviderForModuleInfo(moduleInfo: ModuleInfo): PackageFragmentProvider? = null
|
||||
|
||||
private val klibFactory = KlibMetadataFactories(::KonanBuiltIns, DynamicTypeDeserializer)
|
||||
|
||||
private val stdlibModuleDescriptor = klibFactory.DefaultDeserializedDescriptorFactory.createDescriptor(
|
||||
library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File(kotlinNativeStdlibPath)),
|
||||
languageVersionSettings = createLanguageVersionSettings(),
|
||||
builtIns = DefaultBuiltIns.Instance,
|
||||
storageManager = LockBasedStorageManager.NO_LOCKS,
|
||||
packageAccessHandler = null
|
||||
).also { it.setDependencies(it) }
|
||||
|
||||
override fun moduleDescriptorForModuleInfo(moduleInfo: ModuleInfo): ModuleDescriptor {
|
||||
if (moduleInfo == DefaultBuiltInsModuleInfo) return DefaultBuiltIns.Instance.builtInsModule
|
||||
if (moduleInfo == KotlinNativeStdlibModuleInfo) return stdlibModuleDescriptor
|
||||
error("Unknown module info $moduleInfo")
|
||||
}
|
||||
}
|
||||
|
||||
private object DefaultBuiltInsModuleInfo : ModuleInfo {
|
||||
override val name get() = DefaultBuiltIns.Instance.builtInsModule.name
|
||||
override fun dependencies() = listOf(this)
|
||||
override fun dependencyOnBuiltIns() = ModuleInfo.DependencyOnBuiltIns.LAST
|
||||
override val platform get() = NativePlatforms.unspecifiedNativePlatform
|
||||
override val analyzerServices get() = NativePlatformAnalyzerServices
|
||||
}
|
||||
|
||||
private object KotlinNativeStdlibModuleInfo : ModuleInfo {
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = NativePlatformAnalyzerServices
|
||||
override val name: Name = Name.special("<stdlib>")
|
||||
override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform
|
||||
override fun dependencies(): List<ModuleInfo> = listOf(this)
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.backend.konan.testUtils
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Test Environment that enables quick 'inline' creation of [ModuleDescriptor] instances.
|
||||
*
|
||||
* # Examples
|
||||
* ## Test operating on a single Kotlin Source file:
|
||||
*
|
||||
* ```kotlin
|
||||
* class MyTest : InlineSourceTestEnvironment {
|
||||
* @Test
|
||||
* fun `test - my unit`() {
|
||||
* val moduleDescriptor = createModuleDescriptor("""
|
||||
* package com.example
|
||||
* class MyClassUnderTest
|
||||
* """.trimIndent()
|
||||
* )
|
||||
*
|
||||
* // my test!
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## Test that requires several source files (e.g. multiple packages are involved)
|
||||
* ```kotlin
|
||||
* @Test
|
||||
* fun `test - my unit`() {
|
||||
* val moduleDescriptor = createModuleDescriptor {
|
||||
* source("class Foo")
|
||||
* source("""
|
||||
* package com.example
|
||||
* class Foo
|
||||
* """)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface InlineSourceTestEnvironment {
|
||||
val kotlinCoreEnvironment: KotlinCoreEnvironment
|
||||
val testDisposable: Disposable
|
||||
val testTempDir: File
|
||||
}
|
||||
|
||||
interface InlineSourceCodeCollector {
|
||||
fun source(@Language("kotlin") sourceCode: String)
|
||||
}
|
||||
|
||||
fun InlineSourceTestEnvironment.createModuleDescriptor(@Language("kotlin") sourceCode: String): ModuleDescriptor =
|
||||
createModuleDescriptor(kotlinCoreEnvironment, testTempDir, listOf(sourceCode))
|
||||
|
||||
fun InlineSourceTestEnvironment.createModuleDescriptor(build: InlineSourceCodeCollector.() -> Unit): ModuleDescriptor {
|
||||
val sources = mutableListOf<String>()
|
||||
object : InlineSourceCodeCollector {
|
||||
override fun source(sourceCode: String) {
|
||||
sources.add(sourceCode)
|
||||
}
|
||||
}.build()
|
||||
|
||||
return createModuleDescriptor(kotlinCoreEnvironment, testTempDir, sources.toList())
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.backend.konan.testUtils
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapper
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportProblemCollector
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
|
||||
internal fun createObjCExportNamerConfiguration(
|
||||
topLevelNamePrefix: String = "",
|
||||
additionalPrefix: (moduleName: Name) -> String? = { null },
|
||||
): ObjCExportNamer.Configuration {
|
||||
return object : ObjCExportNamer.Configuration {
|
||||
override val topLevelNamePrefix: String get() = topLevelNamePrefix
|
||||
override fun getAdditionalPrefix(module: ModuleDescriptor): String? = additionalPrefix(module.name)
|
||||
override val objcGenerics: Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createObjCExportMapper(
|
||||
deprecationResolver: DeprecationResolver? = null,
|
||||
local: Boolean = false,
|
||||
unitSuspendFunctionObjCExport: UnitSuspendFunctionObjCExport = UnitSuspendFunctionObjCExport.DEFAULT,
|
||||
): ObjCExportMapper {
|
||||
return ObjCExportMapper(deprecationResolver, local, unitSuspendFunctionObjCExport)
|
||||
}
|
||||
|
||||
internal fun createObjCExportNamer(
|
||||
configuration: ObjCExportNamer.Configuration = createObjCExportNamerConfiguration(),
|
||||
builtIns: KotlinBuiltIns = DefaultBuiltIns.Instance,
|
||||
local: Boolean = false,
|
||||
problemCollector: ObjCExportProblemCollector = ObjCExportProblemCollector.SILENT,
|
||||
mapper: ObjCExportMapper = createObjCExportMapper(local = local),
|
||||
): ObjCExportNamer {
|
||||
return ObjCExportNamerImpl(
|
||||
builtIns = builtIns,
|
||||
mapper = mapper,
|
||||
local = local,
|
||||
problemCollector = problemCollector,
|
||||
configuration = configuration
|
||||
)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.backend.konan.tests
|
||||
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.backend.konan.testUtils.*
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.types.KotlinTypeFactory
|
||||
import org.jetbrains.kotlin.types.TypeAttributes
|
||||
import org.jetbrains.kotlin.types.TypeProjectionImpl
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
/**
|
||||
* ## Test Scope
|
||||
* This test shall cover test the [ObjCExportMapper];
|
||||
* The test will invoke the type mapping a known type (e.g. List<Int>) and check
|
||||
* the corresponding conversion to [ObjCNonNullReferenceType].
|
||||
*
|
||||
* The test also acts as quick entry point for debugging the [ObjCExportMapper]
|
||||
*/
|
||||
class ObjCExportMapperTest : InlineSourceTestEnvironment {
|
||||
override val testDisposable = Disposer.newDisposable("${ObjCExportMapperTest::class.simpleName}.testDisposable")
|
||||
override val kotlinCoreEnvironment: KotlinCoreEnvironment = createKotlinCoreEnvironment(testDisposable)
|
||||
|
||||
@TempDir
|
||||
override lateinit var testTempDir: File
|
||||
|
||||
@AfterEach
|
||||
fun dispose() {
|
||||
Disposer.dispose(testDisposable)
|
||||
}
|
||||
|
||||
/**
|
||||
* No 'type mapper' expected for a simple Kotlin class like 'Foo'.
|
||||
* Only well known standard types (List, ...) will get mapped to their corresponding
|
||||
* ObjC counterpart (NSArray, ...)
|
||||
*/
|
||||
@Test
|
||||
fun `test - simple class`() {
|
||||
val module = createModuleDescriptor("class Foo")
|
||||
val foo = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!!
|
||||
assertNull(createObjCExportMapper().getCustomTypeMapper(foo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Will test ObjC type mapping from List<Int> to NSArray<Int *> *
|
||||
*/
|
||||
@Test
|
||||
fun `test - List of int`() {
|
||||
/* We are only using built in / stdlib parts, so we can provide empty source code */
|
||||
val module = createModuleDescriptor("")
|
||||
val objcExportMapper = createObjCExportMapper()
|
||||
val objcExportNamer = createObjCExportNamer(mapper = objcExportMapper)
|
||||
|
||||
val objcExportTranslator = ObjCExportTranslatorImpl(
|
||||
generator = ObjCExportHeaderGeneratorImpl(
|
||||
moduleDescriptors = listOf(module),
|
||||
mapper = objcExportMapper,
|
||||
namer = objcExportNamer,
|
||||
problemCollector = ObjCExportProblemCollector.SILENT,
|
||||
objcGenerics = true,
|
||||
shouldExportKDoc = false,
|
||||
additionalImports = emptyList()
|
||||
),
|
||||
mapper = objcExportMapper,
|
||||
namer = objcExportNamer,
|
||||
problemCollector = ObjCExportProblemCollector.SILENT,
|
||||
objcGenerics = true
|
||||
)
|
||||
|
||||
val listClassDescriptor = module.findClassAcrossModuleDependencies(ClassId.fromString("kotlin/collections/List"))!!
|
||||
val intClassDescriptor = module.findClassAcrossModuleDependencies(ClassId.fromString("kotlin/Int"))!!
|
||||
|
||||
/* Represents List<Int> */
|
||||
val listOfIntType = KotlinTypeFactory.simpleNotNullType(
|
||||
TypeAttributes.Empty, listClassDescriptor, listOf(
|
||||
TypeProjectionImpl(KotlinTypeFactory.simpleNotNullType(TypeAttributes.Empty, intClassDescriptor, emptyList()))
|
||||
)
|
||||
)
|
||||
|
||||
val typeMapper = assertNotNull(objcExportMapper.getCustomTypeMapper(listClassDescriptor))
|
||||
assertEquals(ClassId.fromString("kotlin/collections/List"), typeMapper.mappedClassId)
|
||||
val listOfIntMapped = typeMapper.mapType(listOfIntType, objcExportTranslator, objCExportScope = ObjCRootExportScope)
|
||||
|
||||
assertEquals(ObjCClassType("NSArray", typeArguments = listOf(ObjCClassType("Int"))), listOfIntMapped)
|
||||
assertEquals("NSArray<Int *> *", listOfIntMapped.toString())
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.backend.konan.tests
|
||||
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer.ClassOrProtocolName
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer.PropertyName
|
||||
import org.jetbrains.kotlin.backend.konan.testUtils.*
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi2ir.findSingleFunction
|
||||
import org.jetbrains.kotlin.resolve.scopes.findFirstVariable
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* ## Test Scope
|
||||
* This test will cover basic cases of the [ObjCExportNamer]. <br>
|
||||
*
|
||||
* The __input__ to this test will be descriptors defined as 'inline source code'.
|
||||
* (e.g. a String like "class Foo").<br>
|
||||
*
|
||||
* The __output__ of this test will be result of the request to the [ObjCExportNamer].
|
||||
* (e.g. the [ClassOrProtocolName])
|
||||
*/
|
||||
class ObjCExportNamerTest : InlineSourceTestEnvironment {
|
||||
|
||||
override val testDisposable = Disposer.newDisposable("${ObjCExportNamerTest::class.simpleName}.testDisposable")
|
||||
|
||||
override val kotlinCoreEnvironment = createKotlinCoreEnvironment(testDisposable)
|
||||
|
||||
@TempDir
|
||||
override lateinit var testTempDir: File
|
||||
|
||||
@AfterEach
|
||||
fun dispose() {
|
||||
Disposer.dispose(testDisposable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple class`() {
|
||||
val module = createModuleDescriptor("class Foo")
|
||||
val clazz = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!!
|
||||
assertEquals(
|
||||
ClassOrProtocolName("Foo", "Foo"),
|
||||
createObjCExportNamer().getClassOrProtocolName(clazz)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple class - with prefix`() {
|
||||
val module = createModuleDescriptor("class Foo")
|
||||
val clazz = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!!
|
||||
assertEquals(
|
||||
ClassOrProtocolName("Foo", "XFoo"),
|
||||
createObjCExportNamer(createObjCExportNamerConfiguration(topLevelNamePrefix = "X")).getClassOrProtocolName(clazz)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple function`() {
|
||||
val module = createModuleDescriptor("fun foo() = 42")
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo"))
|
||||
assertEquals("foo()", createObjCExportNamer().getSwiftName(foo))
|
||||
assertEquals("foo", createObjCExportNamer().getSelector(foo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - function with parameters`() {
|
||||
val module = createModuleDescriptor("fun foo(a: Int, b: Int) = a + b")
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo"))
|
||||
assertEquals("foo(a:b:)", createObjCExportNamer().getSwiftName(foo))
|
||||
assertEquals("fooA:b:", createObjCExportNamer().getSelector(foo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple property`() {
|
||||
val module = createModuleDescriptor("val foo = 42")
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findFirstVariable("foo") { true }!!
|
||||
assertEquals(PropertyName("foo", "foo"), createObjCExportNamer().getPropertyName(foo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple property - with prefix`() {
|
||||
val module = createModuleDescriptor("val foo = 42")
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findFirstVariable("foo") { true }!!
|
||||
assertEquals(
|
||||
PropertyName("foo", "foo"),
|
||||
createObjCExportNamer(createObjCExportNamerConfiguration(topLevelNamePrefix = "X")).getPropertyName(foo)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - function inside class`() {
|
||||
val module = createModuleDescriptor(
|
||||
"""
|
||||
package bar
|
||||
class Foo {
|
||||
fun someFunction(a: Int, b: Int) = a + b
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val fooClass = module.findClassAcrossModuleDependencies(ClassId.fromString("bar/Foo"))!!
|
||||
val someFunction = fooClass.unsubstitutedMemberScope.findSingleFunction(Name.identifier("someFunction"))
|
||||
assertEquals("someFunction(a:b:)", createObjCExportNamer().getSwiftName(someFunction))
|
||||
assertEquals("someFunctionA:b:", createObjCExportNamer().getSelector(someFunction))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - class with ObjCName annotation`() {
|
||||
val module = createModuleDescriptor(
|
||||
"""
|
||||
@kotlin.native.ObjCName("ObjCFoo", "SwiftFoo")
|
||||
class Foo
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val fooClass = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!!
|
||||
assertEquals(
|
||||
ClassOrProtocolName(swiftName = "SwiftFoo", objCName = "ObjCFoo"),
|
||||
createObjCExportNamer().getClassOrProtocolName(fooClass)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple parameter`() {
|
||||
val module = createModuleDescriptor("fun foo(a: Int)")
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo"))
|
||||
val parameterA = foo.valueParameters.find { it.name == Name.identifier("a") }!!
|
||||
assertEquals("a", createObjCExportNamer().getParameterName(parameterA))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - parameter with ObjCName annotation`() {
|
||||
val module = createModuleDescriptor(
|
||||
"""
|
||||
import kotlin.native.ObjCName
|
||||
fun foo(@ObjCName("aObjC", "aSwift") a: Int)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo"))
|
||||
val parameterA = foo.valueParameters.find { it.name == Name.identifier("a") }!!
|
||||
assertEquals("aObjC", createObjCExportNamer().getParameterName(parameterA))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - class mangling`() {
|
||||
val module = createModuleDescriptor {
|
||||
source(
|
||||
"""
|
||||
package a
|
||||
class Foo
|
||||
""".trimIndent()
|
||||
)
|
||||
source(
|
||||
"""
|
||||
package b
|
||||
class Foo
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
val aFoo = module.findClassAcrossModuleDependencies(ClassId.fromString("a/Foo"))!!
|
||||
val bFoo = module.findClassAcrossModuleDependencies(ClassId.fromString("b/Foo"))!!
|
||||
|
||||
val namer = createObjCExportNamer()
|
||||
assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(aFoo))
|
||||
assertEquals(ClassOrProtocolName("Foo_", "Foo_"), namer.getClassOrProtocolName(bFoo))
|
||||
|
||||
/* Check caching in namer: Should return same name again */
|
||||
assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(aFoo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - simple enum`() {
|
||||
val module = createModuleDescriptor(
|
||||
"""
|
||||
enum class Foo {
|
||||
A, B, C
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val foo = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!!
|
||||
val fooA = foo.enumEntries.find { it.name == Name.identifier("A") }!!
|
||||
val namer = createObjCExportNamer()
|
||||
|
||||
assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(foo))
|
||||
assertEquals("a", namer.getEnumEntrySelector(fooA))
|
||||
assertEquals("a", namer.getEnumEntrySwiftName(fooA))
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
org.jetbrains.kotlin.backend.konan.testUtils.Fe10HeaderGeneratorExtension
|
||||
Reference in New Issue
Block a user