[ObjCType] Add basic implementation of base method, fix tests

KT-65687
This commit is contained in:
eugene.levenetc
2024-02-22 15:28:22 +01:00
committed by Space Team
parent 1c2570ed7e
commit 078f0ff6e1
18 changed files with 471 additions and 14 deletions
@@ -36,6 +36,7 @@ internal fun KtCallableSymbol.isVisibleInObjC(): Boolean {
if (this.isHiddenFromObjCByAnnotation()) return false
if (this.isSealedClassConstructor()) return false
if (this.isComponentNMethod() && this.getDirectlyOverriddenSymbols().isEmpty()) return false
getContainingSymbol()?.let { if (!it.isVisibleInObjC()) return false }
return true
}
@@ -50,6 +51,7 @@ internal fun KtClassOrObjectSymbol.isVisibleInObjC(): Boolean {
if (!this.classKind.isVisibleInObjC()) return false
if (this.isExpect) return false
if (this.isInlined()) return false
getContainingSymbol()?.let { if (!it.isVisibleInObjC()) return false }
return true
}
@@ -0,0 +1,22 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
/**
* Very basic implementation
*
* It covers edge case: if something isn't visible in inheritance chain it should be skipped
* See [org.jetbrains.kotlin.objcexport.tests.IsContainingSymbolVisible]
* See also K1:
* - [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.getBaseMethods]
* - [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isBaseMethod]
*/
context(KtAnalysisSession)
internal val KtFunctionSymbol.baseMethod: KtFunctionSymbol
get() {
val overriddenSymbols = getAllOverriddenSymbols().filter { symbol -> symbol.isVisibleInObjC() }
return if (overriddenSymbols.isEmpty()) this
else overriddenSymbols.last() as KtFunctionSymbol
}
@@ -143,7 +143,9 @@ private class KtObjCExportHeaderGenerator {
2) Super interface / superclass symbol export stubs (result of translation) have to be present in the stubs list before the
original stub
*/
symbol.getDeclaredSuperInterfaceSymbols().forEach { superInterfaceSymbol ->
symbol.getDeclaredSuperInterfaceSymbols()
.filter { it.isVisibleInObjC() }
.forEach { superInterfaceSymbol ->
translateClassOrObjectSymbol(superInterfaceSymbol)?.let {
objCProtocolForwardDeclarations += it.name
}
@@ -32,7 +32,17 @@ context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.buildObjCMethod(
unavailable: Boolean = false,
): ObjCMethod {
val bridge = getFunctionMethodBridge()
val bridge = if (this is KtFunctionSymbol) {
/**
* Unlike constructor, a function can have base return type.
* So in case of function we need to call [getFunctionMethodBridge] on [baseMethod]
*/
baseMethod.getFunctionMethodBridge()
} else {
this.getFunctionMethodBridge()
}
val returnType: ObjCType = mapReturnType(bridge.returnBridge)
val parameters = translateToObjCParameters(bridge)
val selector = getSelector(bridge)
@@ -75,7 +85,6 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(
)
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerKt.toValidObjCSwiftIdentifier]
*/
@@ -295,4 +304,4 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue):
MethodBridge.ReturnValue.Instance.FactoryResult,
-> ObjCInstanceType
}
}
}
@@ -44,6 +44,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.superProtocols(): List<String> {
return getDeclaredSuperInterfaceSymbols()
.filter { it.isVisibleInObjC() }
.map { superInterface -> superInterface.getObjCClassOrProtocolName().objCName }
.toList()
}
@@ -181,4 +181,138 @@ class IsVisibleInObjCTest(
assertFalse(fooSymbol.isVisibleInObjC())
}
}
@Test
fun `test - containing symbol visible`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class PublicClass {
fun foo()
}
""".trimIndent()
)
analyze(file) {
assertTrue(file.getClassOrFail("PublicClass").getFunctionOrFail("foo").isVisibleInObjC())
}
}
@Test
fun `test - invisible symbol inside private class`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
private class PrivateClass {
fun foo()
}
""".trimIndent()
)
analyze(file) {
assertFalse(file.getClassOrFail("PrivateClass").getFunctionOrFail("foo").isVisibleInObjC())
}
}
@Test
fun `test - nested visible function`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class PublicA {
class PublicB {
class PublicC {
fun foo() {}
}
}
}
""".trimIndent()
)
analyze(file) {
val foo = file
.getClassOrFail("PublicA").getMemberScope()
.getClassOrFail("PublicB").getMemberScope()
.getClassOrFail("PublicC")
.getFunctionOrFail("foo")
assertTrue(foo.isVisibleInObjC())
}
}
@Test
fun `test - nested invisible function`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class PublicA {
private class PrivateB {
class PublicC {
fun foo() {}
}
}
}
""".trimIndent()
)
analyze(file) {
val foo = file
.getClassOrFail("PublicA").getMemberScope()
.getClassOrFail("PrivateB").getMemberScope()
.getClassOrFail("PublicC")
.getFunctionOrFail("foo")
assertFalse(foo.isVisibleInObjC())
}
}
@Test
fun `test - invisible symbol inside public class marked with @HidesFromObjC`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
@kotlin.native.HidesFromObjC
annotation class HideIt
@HideIt
class PublicClass {
fun foo()
}
""".trimIndent()
)
analyze(file) {
assertFalse(file.getClassOrFail("PublicClass").getFunctionOrFail("foo").isVisibleInObjC())
}
}
@Test
fun `test - invisible member inside nested class marked with @HidesFromObjC`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
@kotlin.native.HidesFromObjC
annotation class HideIt
class PublicA {
fun publicA() = Unit
@HideIt
class HiddenB {
fun hiddenB() = Unit
class HiddenC {
fun hiddenC() = Unit
}
}
}
""".trimIndent()
)
analyze(file) {
val publicA = file.getClassOrFail("PublicA")
val hiddenB = publicA.getMemberScope().getClassOrFail("HiddenB")
val hiddenC = hiddenB.getMemberScope().getClassOrFail("HiddenC")
assertFalse(hiddenB.isVisibleInObjC())
assertFalse(hiddenC.isVisibleInObjC())
assertTrue(publicA.getFunctionOrFail("publicA").isVisibleInObjC())
assertFalse(hiddenB.getFunctionOrFail("hiddenB").isVisibleInObjC())
assertFalse(hiddenC.getFunctionOrFail("hiddenC").isVisibleInObjC())
}
}
}