diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCBaseCallable.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCBaseCallable.kt new file mode 100644 index 00000000000..128a0da029c --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isObjCBaseCallable.kt @@ -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.objcexport.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol + +/** + * 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. + */ +context(KtAnalysisSession) +fun KtCallableSymbol.isObjCBaseCallable(): Boolean { + return getAllOverriddenSymbols().none { overriddenSymbol -> + overriddenSymbol.isVisibleInObjC() + } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCBaseCallableTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCBaseCallableTest.kt new file mode 100644 index 00000000000..332bcad5b1a --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsObjCBaseCallableTest.kt @@ -0,0 +1,70 @@ +/* + * 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.isObjCBaseCallable +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class IsObjCBaseCallableTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + @Test + fun `test - top level function`() { + val file = inlineSourceCodeAnalysis.createKtFile("fun foo() = Unit") + analyze(file) { + val fooSymbol = file.getFunctionOrFail("foo") + assertTrue(fooSymbol.isObjCBaseCallable()) + } + } + + @Test + fun `test - function overriding abstract function`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + abstract class Bar { + abstract fun x() + } + + class Foo : Bar() { + override fun x() = Unit + } + + """.trimIndent() + ) + + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + val xSymbol = fooSymbol.getMemberScope().getFunctionOrFail("x") + assertFalse(xSymbol.isObjCBaseCallable()) + } + } + + @Test + fun `test - function overriding private interface function`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + private interface I { + fun x() + } + class Foo: I { + override fun x() = Unit + } + """.trimIndent() + ) + + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + val xSymbol = fooSymbol.getMemberScope().getFunctionOrFail("x") + assertTrue(xSymbol.isObjCBaseCallable()) + } + } +}