[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())
}
}
}
@@ -49,11 +49,7 @@ class ObjCDependenciesTypesTest(
doTest(dependenciesDir.resolve("arrayList"))
}
/**
* Int* type conversion issue: KT-65687
*/
@Test
@TodoAnalysisApi
fun `test - implementIterator`() {
doTest(dependenciesDir.resolve("implementIterator"))
}
@@ -222,12 +222,7 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
doTest(headersTestDataDir.resolve("classReferencingOtherClassAsReturnType"))
}
/**
* - IntIterator has unwanted 'hasNext' exposed
* - IntIterator's next method returns int32_t instead of expected Int *
*/
@Test
@TodoAnalysisApi
fun `test - classReferencingDependencyClassAsReturnType`() {
doTest(headersTestDataDir.resolve("classReferencingDependencyClassAsReturnType"))
}
@@ -321,9 +316,34 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
doTest(headersTestDataDir.resolve("memberFunctionSignatureOrder"))
}
@Test
fun `test - multiple inheritance`() {
doTest(headersTestDataDir.resolve("multipleInheritance"))
}
@Test
fun `test - private super interface`() {
doTest(headersTestDataDir.resolve("privateSuperInterface"))
}
@Test
fun `test- privateSuperInterfaceWithCovariantOverride`() {
doTest(headersTestDataDir.resolve("privateSuperInterfaceWithCovariantOverride"))
}
@Test
fun `test - superClassWithCovariantOverride`() {
doTest(headersTestDataDir.resolve("superClassWithCovariantOverride"))
}
@Test
fun `test - privateGenericSuperInterface`() {
doTest(headersTestDataDir.resolve("privateGenericSuperInterface"))
}
private fun doTest(root: File, configuration: Configuration = Configuration()) {
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
val generatedHeaders = generator.generateHeaders(root, configuration).toString()
KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), generatedHeaders)
}
}
}
@@ -0,0 +1,82 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@class A<T>, B<T>;
@protocol RootA, RootB;
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
/**
* inheritance chain: A > B > C
*/
@interface A<T> : Base
/**
* inheritance chain: A > B > C
*/
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
/**
* inheritance chain: A > B > C
*/
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (T _Nullable)foo __attribute__((swift_name("foo()")));
@end
@interface B<T> : A<Int *>
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (Int *)foo __attribute__((swift_name("foo()")));
@end
@interface C : B<Int *>
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (Int *)foo __attribute__((swift_name("foo()")));
@end
/**
* inheritance tree:
* RootA RootB
* \ /
* Tree
*/
@protocol RootA
@required
- (id _Nullable)fooA __attribute__((swift_name("fooA()")));
@end
@protocol RootB
@required
- (id _Nullable)fooB __attribute__((swift_name("fooB()")));
@end
__attribute__((objc_subclassing_restricted))
@interface Tree : Base <RootA, RootB>
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (Int *)fooA __attribute__((swift_name("fooA()")));
- (NSString *)fooB __attribute__((swift_name("fooB()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,33 @@
/**
* inheritance chain: A > B > C
*/
abstract class A<T> {
abstract fun foo(): T
}
open class B<T> : A<Int>() {
override fun foo(): Int = 42
}
open class C : B<Int>() {
override fun foo(): Int = 42
}
/**
* inheritance tree:
* RootA RootB
* \ /
* Tree
*/
interface RootA<T> {
fun fooA(): T
}
interface RootB<T> {
fun fooB(): T
}
class Tree : RootA<Int>, RootB<String> {
override fun fooA(): Int = 42
override fun fooB(): String = "42"
}
@@ -0,0 +1,30 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((objc_subclassing_restricted))
@interface PublicClass : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (int32_t)foo __attribute__((swift_name("foo()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,7 @@
private interface PrivateInterface<T> {
fun foo(): T
}
class PublicClass : PrivateInterface<Int> {
override fun foo(): Int = error("stub")
}
@@ -0,0 +1,30 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((objc_subclassing_restricted))
@interface PublicB : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (void)foo __attribute__((swift_name("foo()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,7 @@
private interface PrivateA {
fun foo()
}
class PublicB : PrivateA {
override fun foo() = Unit
}
@@ -0,0 +1,30 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((objc_subclassing_restricted))
@interface PublicB : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (NSString *)foo __attribute__((swift_name("foo()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,7 @@
private interface PrivateA {
fun foo(): Any
}
class PublicB : PrivateA {
override fun foo() : String = error("stub")
}
@@ -0,0 +1,38 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@class Bar;
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
@interface Bar : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (id)foo __attribute__((swift_name("foo()")));
@end
__attribute__((objc_subclassing_restricted))
@interface Foo : Bar
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (NSString *)foo __attribute__((swift_name("foo()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,7 @@
abstract class Bar {
abstract fun foo(): Any
}
class Foo : Bar() {
override fun foo(): String = error("stub")
}