[ObjCExport] Support naming of nested classes

^KT-65204 Fixed
This commit is contained in:
Sebastian Sellmair
2024-02-08 12:52:55 +01:00
committed by Space Team
parent f1fd84f539
commit 4b6624c920
9 changed files with 264 additions and 8 deletions
@@ -6,16 +6,109 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
context(KtAnalysisSession, KtObjCExportSession)
fun KtClassLikeSymbol.getObjCClassOrProtocolName(): ObjCExportClassOrProtocolName {
val resolvedObjCNameAnnotation = resolveObjCNameAnnotation()
return ObjCExportClassOrProtocolName(
objCName = resolvedObjCNameAnnotation?.objCName ?: nameOrAnonymous.asString(),
swiftName = resolvedObjCNameAnnotation?.swiftName ?: nameOrAnonymous.asString()
objCName = getObjCName(resolvedObjCNameAnnotation),
swiftName = getSwiftName(resolvedObjCNameAnnotation)
)
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassLikeSymbol.getObjCName(
resolvedObjCNameAnnotation: KtResolvedObjCNameAnnotation? = resolveObjCNameAnnotation(),
): String {
val objCName = (resolvedObjCNameAnnotation?.objCName ?: nameOrAnonymous.asString()).toValidObjCSwiftIdentifier()
if (resolvedObjCNameAnnotation != null && resolvedObjCNameAnnotation.isExact) {
return objCName
}
getContainingSymbol()?.let { it as? KtClassLikeSymbol }?.let { containingClass ->
return containingClass.getObjCName() + objCName.capitalizeAsciiOnly()
}
// KT-65670: Append module specific prefixes?
return configuration.frameworkName.orEmpty() + objCName
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassLikeSymbol.getSwiftName(
resolvedObjCNameAnnotation: KtResolvedObjCNameAnnotation? = resolveObjCNameAnnotation(),
): String {
val swiftName = (resolvedObjCNameAnnotation?.swiftName ?: nameOrAnonymous.asString()).toValidObjCSwiftIdentifier()
if (resolvedObjCNameAnnotation != null && resolvedObjCNameAnnotation.isExact) {
return swiftName
}
getContainingSymbol()?.let { it as? KtClassLikeSymbol }?.let { containingClass ->
val containingClassSwiftName = containingClass.getSwiftName()
return buildString {
if (canBeInnerSwift()) {
append(containingClassSwiftName)
if ("." !in this && containingClass.canBeOuterSwift()) {
// AB -> AB.C
append(".")
append(mangleSwiftNestedClassName(swiftName))
} else {
// AB -> ABC
// A.B -> A.BC
append(swiftName.capitalizeAsciiOnly())
}
} else {
append(containingClassSwiftName.replaceFirst(".", ""))
append(swiftName.capitalizeAsciiOnly())
}
}
}
// KT-65670: Append module specific prefixes?
return swiftName
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassLikeSymbol.canBeInnerSwift(): Boolean {
if (configuration.objcGenerics && this.typeParameters.isNotEmpty()) {
// Swift compiler doesn't seem to handle this case properly.
// See https://bugs.swift.org/browse/SR-14607.
// This behaviour of Kotlin is reported as https://youtrack.jetbrains.com/issue/KT-46518.
return false
}
if (this is KtClassOrObjectSymbol && this.classKind == KtClassKind.INTERFACE) {
// Swift doesn't support nested protocols.
return false
}
return true
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassLikeSymbol.canBeOuterSwift(): Boolean {
if (configuration.objcGenerics && this.typeParameters.isNotEmpty()) {
// Swift nested classes are static but capture outer's generics.
return false
}
if (this is KtClassOrObjectSymbol && this.classKind == KtClassKind.INTERFACE) {
// Swift doesn't support outer protocols.
return false
}
return true
}
private fun mangleSwiftNestedClassName(name: String): String = when (name) {
"Type" -> "${name}_" // See https://github.com/JetBrains/kotlin-native/issues/3167
else -> name
}
@@ -17,9 +17,10 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
val enumKind = this.classKind == KtClassKind.ENUM_CLASS
val final = if (this is KtSymbolWithModality) this.modality == Modality.FINAL else false
val attributes = if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()
val name = getObjCClassOrProtocolName()
val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
val comment: ObjCComment? = annotationsList.translateToObjCComment()
val origin: ObjCExportStubOrigin = getObjCExportStubOrigin()
@@ -70,15 +70,26 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
doTest(headersTestDataDir.resolve("sameClassNameInDifferentPackage"))
}
/**
* Naming of nested classes: KT-65633
*/
@Test
@TodoAnalysisApi
fun `test - nestedClass`() {
doTest(headersTestDataDir.resolve("nestedClass"))
}
@Test
fun `test - nestedClassWithFrameworkName`() {
doTest(headersTestDataDir.resolve("nestedClassWithFrameworkName"), Configuration(frameworkName = "Shared"))
}
@Test
fun `test - nestedGenericClass`() {
doTest(headersTestDataDir.resolve("nestedGenericClass"))
}
@Test
fun `test - nestedInterface`() {
doTest(headersTestDataDir.resolve("nestedInterface"))
}
@Test
@TodoAnalysisApi
fun `test - samePropertyAndFunctionName`() {
@@ -97,7 +108,6 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
}
@Test
@TodoAnalysisApi
fun `test - classWithObjCNameAnnotation`() {
doTest(headersTestDataDir.resolve("classWithObjCNameAnnotation"))
}
@@ -0,0 +1,58 @@
#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))
__attribute__((swift_name("A")))
@interface SharedA : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("A.A1")))
@interface SharedAA1 : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("A.A1B1")))
@interface SharedAA1B1 : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("A.A2")))
@interface SharedAA2 : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("A.A2B2")))
@interface SharedAA2B2 : SharedBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,8 @@
class A {
class A1 {
class B1
}
class A2 {
class B2
}
}
@@ -0,0 +1,41 @@
#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 A : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
@interface AB<T> : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
@interface ABC<T> : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,5 @@
class A {
class B<T> {
class C<T>
}
}
@@ -0,0 +1,35 @@
#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
@protocol A
@required
@end
@protocol AB
@required
@end
@protocol ABC
@required
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,5 @@
interface A {
interface B {
interface C
}
}