diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 9cac75750d0..eccc91ea420 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -83,7 +83,7 @@ internal open class ObjCExportNameTranslatorImpl( configuration: ObjCExportNamer.Configuration ) : ObjCExportNameTranslator { - private val helper = ObjCExportNamingHelper(configuration.topLevelNamePrefix) + private val helper = ObjCExportNamingHelper(configuration.topLevelNamePrefix, configuration.objcGenerics) override fun getFileClassName(file: KtFile): ObjCExportNamer.ClassOrProtocolName = helper.getFileClassName(file) @@ -100,39 +100,29 @@ internal open class ObjCExportNameTranslatorImpl( private fun getClassOrProtocolSwiftName( ktClassOrObject: KtClassOrObject ): String = buildString { - val ownName = ktClassOrObject.name!!.toIdentifier() val outerClass = ktClassOrObject.getStrictParentOfType() if (outerClass != null) { - append(getClassOrProtocolSwiftName(outerClass)) - - val importAsMember = when { - // FIXME: generics. - - ktClassOrObject.isInterface || outerClass.isInterface -> { - // Swift doesn't support neither nested nor outer protocols. - false - } - - this.contains('.') -> { - // Swift doesn't support swift_name with deeply nested names. - // It seems to support "OriginalObjCName.SwiftName" though, - // but this doesn't seem neither documented nor reliable. - false - } - - else -> true - } - - if (importAsMember) { - append(".").append(helper.mangleSwiftNestedClassName(ownName)) - } else { - append(ownName.capitalize()) - } + appendNameWithContainer(ktClassOrObject, outerClass) } else { - append(ownName) + append(ktClassOrObject.name!!.toIdentifier()) } } + private fun StringBuilder.appendNameWithContainer( + ktClassOrObject: KtClassOrObject, + outerClass: KtClassOrObject + ) = helper.appendNameWithContainer( + this, + ktClassOrObject, ktClassOrObject.name!!.toIdentifier(), + outerClass, getClassOrProtocolSwiftName(outerClass), + object : ObjCExportNamingHelper.ClassInfoProvider { + override fun hasGenerics(clazz: KtClassOrObject): Boolean = + clazz.typeParametersWithOuter.count() != 0 + + override fun isInterface(clazz: KtClassOrObject): Boolean = ktClassOrObject.isInterface + } + ) + override fun getTypeParameterName(ktTypeParameter: KtTypeParameter): String = buildString { append(ktTypeParameter.name!!.toIdentifier()) while (helper.isTypeParameterNameReserved(this.toString())) append('_') @@ -140,7 +130,8 @@ internal open class ObjCExportNameTranslatorImpl( } private class ObjCExportNamingHelper( - private val topLevelNamePrefix: String + private val topLevelNamePrefix: String, + private val objcGenerics: Boolean ) { fun translateFileName(fileName: String): String = @@ -164,6 +155,71 @@ private class ObjCExportNamingHelper( fun getFileClassName(file: KtFile): ObjCExportNamer.ClassOrProtocolName = getFileClassName(file.name) + fun appendNameWithContainer( + builder: StringBuilder, + clazz: T, + ownName: String, + containingClass: T, + containerName: String, + provider: ClassInfoProvider + ) = builder.apply { + if (clazz.canBeSwiftInner(provider)) { + append(containerName) + if (!this.contains('.') && containingClass.canBeSwiftOuter(provider)) { + // AB -> AB.C + append('.') + append(mangleSwiftNestedClassName(ownName)) + } else { + // AB -> ABC + // A.B -> A.BC + append(ownName.capitalize()) + } + } else { + // AB, A.B -> ABC + val dotIndex = containerName.indexOf('.') + if (dotIndex == -1) { + append(containerName) + } else { + append(containerName.substring(0, dotIndex)) + append(containerName.substring(dotIndex + 1).capitalize()) + } + append(ownName.capitalize()) + } + } + + interface ClassInfoProvider { + fun hasGenerics(clazz: T): Boolean + fun isInterface(clazz: T): Boolean + } + + private fun T.canBeSwiftOuter(provider: ClassInfoProvider): Boolean = when { + objcGenerics && provider.hasGenerics(this) -> { + // Swift nested classes are static but capture outer's generics. + false + } + + provider.isInterface(this) -> { + // Swift doesn't support outer protocols. + false + } + + else -> true + } + + private fun T.canBeSwiftInner(provider: ClassInfoProvider): Boolean = when { + objcGenerics && provider.hasGenerics(this) -> { + // Swift compiler doesn't seem to handle this case properly. + false + } + + provider.isInterface(this) -> { + // Swift doesn't support nested protocols. + false + } + + else -> true + } + fun mangleSwiftNestedClassName(name: String): String = when (name) { "Type" -> "${name}_" // See https://github.com/JetBrains/kotlin-native/issues/3167 else -> name @@ -209,7 +265,7 @@ internal class ObjCExportNamerImpl( private val objcGenerics get() = configuration.objcGenerics override val topLevelNamePrefix get() = configuration.topLevelNamePrefix - private val helper = ObjCExportNamingHelper(configuration.topLevelNamePrefix) + private val helper = ObjCExportNamingHelper(configuration.topLevelNamePrefix, objcGenerics) private fun String.toSpecialStandardClassOrProtocolName() = ObjCExportNamer.ClassOrProtocolName( swiftName = "Kotlin$this", @@ -327,31 +383,7 @@ internal class ObjCExportNamerImpl( StringBuilder().apply { val containingDeclaration = descriptor.containingDeclaration if (containingDeclaration is ClassDescriptor) { - append(getClassOrProtocolSwiftName(containingDeclaration)) - - val importAsMember = when { - objcGenerics && descriptor.hasGenericsInHierarchy() -> false - - descriptor.isInterface || containingDeclaration.isInterface -> { - // Swift doesn't support neither nested nor outer protocols. - false - } - - this.contains('.') -> { - // Swift doesn't support swift_name with deeply nested names. - // It seems to support "OriginalObjCName.SwiftName" though, - // but this doesn't seem neither documented nor reliable. - false - } - - else -> true - } - val ownName = descriptor.name.asString().toIdentifier() - if (importAsMember) { - append(".").append(helper.mangleSwiftNestedClassName(ownName)) - } else { - append(ownName.capitalize()) - } + appendNameWithContainer(descriptor, containingDeclaration) } else if (containingDeclaration is PackageFragmentDescriptor) { appendTopLevelClassBaseName(descriptor) } else { @@ -360,21 +392,20 @@ internal class ObjCExportNamerImpl( }.mangledBySuffixUnderscores() } - private fun ClassDescriptor.hasGenericsInHierarchy(): Boolean { - fun ClassDescriptor.hasGenericsChildren(): Boolean = - unsubstitutedMemberScope.getContributedDescriptors() - .asSequence() - .filterIsInstance() - .any { - it.typeConstructor.parameters.isNotEmpty() || - it.hasGenericsChildren() - } + private fun StringBuilder.appendNameWithContainer( + clazz: ClassDescriptor, + containingClass: ClassDescriptor + ) = helper.appendNameWithContainer( + this, + clazz, clazz.name.asString().toIdentifier(), + containingClass, getClassOrProtocolSwiftName(containingClass), + object : ObjCExportNamingHelper.ClassInfoProvider { + override fun hasGenerics(clazz: ClassDescriptor): Boolean = + clazz.typeConstructor.parameters.isNotEmpty() - val upGenerics = generateSequence(this) { it.containingDeclaration as? ClassDescriptor } - .any { it.typeConstructor.parameters.isNotEmpty() } - - return upGenerics || hasGenericsChildren() - } + override fun isInterface(clazz: ClassDescriptor): Boolean = clazz.isInterface + } + ) private fun getClassOrProtocolObjCName(descriptor: ClassDescriptor): String { val objCMapping = if (descriptor.isInterface) objCProtocolNames else objCClassNames diff --git a/backend.native/tests/framework/values/expectedLazy.h b/backend.native/tests/framework/values/expectedLazy.h index 71188cf202a..6e6cac264c2 100644 --- a/backend.native/tests/framework/values/expectedLazy.h +++ b/backend.native/tests/framework/values/expectedLazy.h @@ -309,6 +309,11 @@ __attribute__((swift_name("Deeply.NestedType"))) @property (readonly) int32_t thirtyTwo __attribute__((swift_name("thirtyTwo"))); @end; +__attribute__((swift_name("DeeplyNestedIType"))) +@protocol ValuesDeeplyNestedIType +@required +@end; + __attribute__((objc_subclassing_restricted)) __attribute__((swift_name("WithGenericDeeply"))) @interface ValuesWithGenericDeeply : ValuesBase @@ -324,7 +329,7 @@ __attribute__((swift_name("WithGenericDeeply.Nested"))) @end; __attribute__((objc_subclassing_restricted)) -__attribute__((swift_name("WithGenericDeeply.NestedType"))) +__attribute__((swift_name("WithGenericDeeplyNestedType"))) @interface ValuesWithGenericDeeplyNestedType : ValuesBase - (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); + (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index eb42247a731..72701384746 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -283,6 +283,8 @@ class Deeply { class Type { val thirtyTwo = 32 } + + interface IType } } diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 57d7657ecc2..c0491d1c6dd 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -546,6 +546,7 @@ func testNames() throws { try assertEquals(actual: WithGenericDeeplyNestedType().thirtyThree, expected: 33) try assertEquals(actual: CKeywords(float: 1.0, enum : 42, goto: true).goto_, expected: true) try assertEquals(actual: TypeOuter.Type_().thirtyFour, expected: 34) + try assertTrue(String(describing: DeeplyNestedIType.self).hasSuffix("DeeplyNestedIType")) } class Base123 : Base23, ExtendedBase1 { diff --git a/backend.native/tests/framework/values_generics/values.swift b/backend.native/tests/framework/values_generics/values.swift index 13b23dab6e3..0a4f67029ac 100644 --- a/backend.native/tests/framework/values_generics/values.swift +++ b/backend.native/tests/framework/values_generics/values.swift @@ -230,7 +230,7 @@ func testGenericInnerClass() throws { let deep2 = GenOuterDeep2() let deep2Before = GenOuterDeep2.Before(deep2) let deep2After = GenOuterDeep2.After(deep2) - let deep2soi = GenOuterDeep2GenShallowOuterInner(deep2) + let deep2soi = GenOuterDeep2.GenShallowOuterInner(deep2) let deep2si = GenOuterDeep2GenShallowOuterInnerGenShallowInner(deep2soi) let deep2i = GenOuterDeep2GenShallowOuterInnerGenShallowInnerGenDeepInner(deep2si)