diff --git a/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts b/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts index 05f1158fe4f..b21759c615f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts +++ b/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts @@ -6,6 +6,7 @@ kotlin { compilerOptions { /* Required to use Analysis Api */ freeCompilerArgs.add("-Xcontext-receivers") + optIn.add("org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi") } } @@ -13,6 +14,7 @@ dependencies { api(project(":analysis:analysis-api")) api(project(":compiler:psi")) api(project(":native:objcexport-header-generator")) + implementation(project(":core:compiler.common.native")) testImplementation(projectTests(":native:objcexport-header-generator")) testApi(project(":analysis:analysis-api-standalone")) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCObjectType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCObjectType.kt new file mode 100644 index 00000000000..a6d9600e672 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCObjectType.kt @@ -0,0 +1,30 @@ +/* + * 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.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.types.KtClassType +import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier.KtResolvedClassTypeQualifier +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.name.NativeStandardInteropNames +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue + +context(KtAnalysisSession) +internal fun KtType.isObjCObjectType(): Boolean { + /* Check if this type represents 'ObjCObject' */ + (this as? KtClassType)?.qualifiers.orEmpty() + .filterIsInstance() + .asSequence() + .map { qualifier -> qualifier.symbol } + .filterIsInstance() + .any { symbol -> symbol.classIdIfNonLocal == NativeStandardInteropNames.objCObjectClassId } + .ifTrue { return true } + + + /* Check super types */ + return this.getDirectSuperTypes().any { superType -> superType.isObjCObjectType() } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCObjectTypeTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCObjectTypeTest.kt new file mode 100644 index 00000000000..fdae7893b08 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCObjectTypeTest.kt @@ -0,0 +1,57 @@ +/* + * 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.objcexport.analysisApiUtils.isObjCObjectType +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class IsObjCObjectTypeTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + @Test + fun `test - simple class`() { + val file = inlineSourceCodeAnalysis.createKtFile("class Foo") + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + assertFalse(fooSymbol.getMemberScope().getConstructors().single().returnType.isObjCObjectType()) + } + } + + @Test + fun `test - class implementing ObjCObject`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + class Foo: kotlinx.cinterop.ObjCObject() + """.trimIndent() + ) + + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + assertTrue(fooSymbol.getMemberScope().getConstructors().single().returnType.isObjCObjectType()) + } + } + + @Test + fun `test - class implementing ObjCObject transitively`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + abstract class A: kotlinx.cinterop.ObjCObject() + abstract class B: A() + class Foo: B() + """.trimIndent() + ) + + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + assertTrue(fooSymbol.getMemberScope().getConstructors().single().returnType.isObjCObjectType()) + } + } +} \ No newline at end of file