[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
|
||||
Reference in New Issue
Block a user