Improve support for invalid identifiers when producing framework
This commit is contained in:
committed by
SvyatoslavScherbina
parent
0831ca7e87
commit
9156cbd758
+2
-3
@@ -604,7 +604,7 @@ internal class ObjCExportTranslatorImpl(
|
||||
private fun buildMethod(method: FunctionDescriptor, baseMethod: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCMethod {
|
||||
fun collectParameters(baseMethodBridge: MethodBridge, method: FunctionDescriptor): List<ObjCParameter> {
|
||||
fun unifyName(initialName: String, usedNames: Set<String>): String {
|
||||
var unique = initialName
|
||||
var unique = initialName.toValidObjCSwiftIdentifier()
|
||||
while (unique in usedNames || unique in cKeywords) {
|
||||
unique += "_"
|
||||
}
|
||||
@@ -986,8 +986,7 @@ abstract class ObjCExportHeaderGenerator internal constructor(
|
||||
add("#pragma clang diagnostic push")
|
||||
listOf(
|
||||
"-Wunknown-warning-option",
|
||||
"-Wnullability",
|
||||
"-Wswift-name-attribute"
|
||||
"-Wnullability"
|
||||
).forEach {
|
||||
add("#pragma clang diagnostic ignored \"$it\"")
|
||||
}
|
||||
|
||||
+32
-15
@@ -96,6 +96,7 @@ internal open class ObjCExportNameTranslatorImpl(
|
||||
private fun getClassOrProtocolSwiftName(
|
||||
ktClassOrObject: KtClassOrObject
|
||||
): String = buildString {
|
||||
val ownName = ktClassOrObject.name!!.toIdentifier()
|
||||
val outerClass = ktClassOrObject.getStrictParentOfType<KtClassOrObject>()
|
||||
if (outerClass != null) {
|
||||
append(getClassOrProtocolSwiftName(outerClass))
|
||||
@@ -119,12 +120,12 @@ internal open class ObjCExportNameTranslatorImpl(
|
||||
}
|
||||
|
||||
if (importAsMember) {
|
||||
append(".").append(ktClassOrObject.name!!)
|
||||
append(".").append(ownName)
|
||||
} else {
|
||||
append(ktClassOrObject.name!!.capitalize())
|
||||
append(ownName.capitalize())
|
||||
}
|
||||
} else {
|
||||
append(ktClassOrObject.name)
|
||||
append(ownName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +134,8 @@ private class ObjCExportNamingHelper(
|
||||
private val topLevelNamePrefix: String
|
||||
) {
|
||||
|
||||
fun translateFileName(fileName: String): String = PackagePartClassUtils.getFilePartShortName(fileName)
|
||||
fun translateFileName(fileName: String): String =
|
||||
PackagePartClassUtils.getFilePartShortName(fileName).toIdentifier()
|
||||
|
||||
fun translateFileName(file: KtFile): String = translateFileName(file.name)
|
||||
|
||||
@@ -328,10 +330,11 @@ internal class ObjCExportNamerImpl(
|
||||
|
||||
else -> true
|
||||
}
|
||||
val ownName = descriptor.name.asString().toIdentifier()
|
||||
if (importAsMember) {
|
||||
append(".").append(descriptor.name.asString())
|
||||
append(".").append(ownName)
|
||||
} else {
|
||||
append(descriptor.name.asString().capitalize())
|
||||
append(ownName.capitalize())
|
||||
}
|
||||
} else if (containingDeclaration is PackageFragmentDescriptor) {
|
||||
appendTopLevelClassBaseName(descriptor)
|
||||
@@ -364,7 +367,7 @@ internal class ObjCExportNamerImpl(
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ClassDescriptor) {
|
||||
append(getClassOrProtocolObjCName(containingDeclaration))
|
||||
.append(descriptor.name.asString().capitalize())
|
||||
.append(descriptor.name.asString().toIdentifier().capitalize())
|
||||
|
||||
} else if (containingDeclaration is PackageFragmentDescriptor) {
|
||||
append(topLevelNamePrefix).appendTopLevelClassBaseName(descriptor)
|
||||
@@ -379,7 +382,7 @@ internal class ObjCExportNamerImpl(
|
||||
configuration.getAdditionalPrefix(descriptor.module)?.let {
|
||||
append(it)
|
||||
}
|
||||
append(descriptor.name.asString())
|
||||
append(descriptor.name.asString().toIdentifier())
|
||||
}
|
||||
|
||||
override fun getSelector(method: FunctionDescriptor): String = methodSelectors.getOrPut(method) {
|
||||
@@ -400,7 +403,7 @@ internal class ObjCExportNamerImpl(
|
||||
1 -> ""
|
||||
else -> "value"
|
||||
}
|
||||
else -> it!!.name.asString()
|
||||
else -> it!!.name.asString().toIdentifier()
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> "error"
|
||||
is MethodBridgeValueParameter.KotlinResultOutParameter -> "result"
|
||||
@@ -451,7 +454,7 @@ internal class ObjCExportNamerImpl(
|
||||
1 -> "_"
|
||||
else -> "value"
|
||||
}
|
||||
else -> it!!.name.asString()
|
||||
else -> it!!.name.asString().toIdentifier()
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters
|
||||
is MethodBridgeValueParameter.KotlinResultOutParameter -> "result"
|
||||
@@ -482,7 +485,7 @@ internal class ObjCExportNamerImpl(
|
||||
assert(mapper.isObjCProperty(property))
|
||||
|
||||
StringBuilder().apply {
|
||||
append(property.name.asString())
|
||||
append(property.name.asString().toIdentifier())
|
||||
}.mangledSequence {
|
||||
append('_')
|
||||
}
|
||||
@@ -492,7 +495,7 @@ internal class ObjCExportNamerImpl(
|
||||
assert(descriptor.kind == ClassKind.OBJECT)
|
||||
|
||||
return objectInstanceSelectors.getOrPut(descriptor) {
|
||||
val name = descriptor.name.asString().decapitalize().mangleIfSpecialFamily("get")
|
||||
val name = descriptor.name.asString().decapitalize().toIdentifier().mangleIfSpecialFamily("get")
|
||||
|
||||
StringBuilder(name).mangledBySuffixUnderscores()
|
||||
}
|
||||
@@ -506,7 +509,7 @@ internal class ObjCExportNamerImpl(
|
||||
val name = descriptor.name.asString().split('_').mapIndexed { index, s ->
|
||||
val lower = s.toLowerCase()
|
||||
if (index == 0) lower else lower.capitalize()
|
||||
}.joinToString("").mangleIfSpecialFamily("the")
|
||||
}.joinToString("").toIdentifier().mangleIfSpecialFamily("the")
|
||||
|
||||
StringBuilder(name).mangledBySuffixUnderscores()
|
||||
}
|
||||
@@ -515,7 +518,7 @@ internal class ObjCExportNamerImpl(
|
||||
override fun getTypeParameterName(typeParameterDescriptor: TypeParameterDescriptor): String {
|
||||
return genericTypeParameterNameMapping.getOrPut(typeParameterDescriptor) {
|
||||
StringBuilder().apply {
|
||||
append(typeParameterDescriptor.name.asString())
|
||||
append(typeParameterDescriptor.name.asString().toIdentifier())
|
||||
}.mangledSequence {
|
||||
append('_')
|
||||
}
|
||||
@@ -580,7 +583,7 @@ internal class ObjCExportNamerImpl(
|
||||
is PropertyGetterDescriptor -> this.correspondingProperty.name.asString()
|
||||
is PropertySetterDescriptor -> "set${this.correspondingProperty.name.asString().capitalize()}"
|
||||
else -> this.name.asString()
|
||||
}
|
||||
}.toIdentifier()
|
||||
|
||||
return candidate.mangleIfSpecialFamily("do")
|
||||
}
|
||||
@@ -825,3 +828,17 @@ internal fun abbreviate(name: String): String {
|
||||
|
||||
return normalizedName
|
||||
}
|
||||
|
||||
// Note: most usages of this method rely on the fact that concatenation of valid identifiers is valid identifier.
|
||||
// This may sometimes be a bit conservative (since it requires mangling non-first character as if it was first);
|
||||
// ignore this for simplicity as having Kotlin identifiers starting from digits is supposed to be rare case.
|
||||
internal fun String.toValidObjCSwiftIdentifier(): String {
|
||||
if (this.isEmpty()) return "__"
|
||||
|
||||
return this.replace('$', '_') // TODO: handle more special characters.
|
||||
.let { if (it.first().isDigit()) "_$it" else it }
|
||||
.let { if (it == "_") "__" else it }
|
||||
}
|
||||
|
||||
// Private shortcut.
|
||||
private fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier()
|
||||
|
||||
@@ -593,6 +593,54 @@ __attribute__((swift_name("TestClashesImpl")))
|
||||
@property (readonly) ValuesInt *clashingProperty_ __attribute__((swift_name("clashingProperty_")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("TestInvalidIdentifiers")))
|
||||
@interface ValuesTestInvalidIdentifiers : KotlinBase
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (int32_t)a_d_d_1:(int32_t)_1 _2:(int32_t)_2 _3:(int32_t)_3 __attribute__((swift_name("a_d_d(_1:_2:_3:)")));
|
||||
@property NSString *_status __attribute__((swift_name("_status")));
|
||||
@property (readonly) unichar __ __attribute__((swift_name("__")));
|
||||
@property (readonly) unichar __ __attribute__((swift_name("__")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("TestInvalidIdentifiers._Foo")))
|
||||
@interface ValuesTestInvalidIdentifiers_Foo : KotlinBase
|
||||
- (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("TestInvalidIdentifiers.Bar_")))
|
||||
@interface ValuesTestInvalidIdentifiersBar_ : KotlinBase
|
||||
- (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("TestInvalidIdentifiers.E")))
|
||||
@interface ValuesTestInvalidIdentifiersE : ValuesKotlinEnum
|
||||
+ (instancetype)alloc __attribute__((unavailable));
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
|
||||
@property (class, readonly) ValuesTestInvalidIdentifiersE *_4_ __attribute__((swift_name("_4_")));
|
||||
@property (class, readonly) ValuesTestInvalidIdentifiersE *_5_ __attribute__((swift_name("_5_")));
|
||||
@property (class, readonly) ValuesTestInvalidIdentifiersE *__ __attribute__((swift_name("__")));
|
||||
@property (class, readonly) ValuesTestInvalidIdentifiersE *__ __attribute__((swift_name("__")));
|
||||
- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (int32_t)compareToOther:(ValuesTestInvalidIdentifiersE *)other __attribute__((swift_name("compareTo(other:)")));
|
||||
@property (readonly) int32_t value __attribute__((swift_name("value")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("TestInvalidIdentifiers.Companion_")))
|
||||
@interface ValuesTestInvalidIdentifiersCompanion_ : KotlinBase
|
||||
+ (instancetype)alloc __attribute__((unavailable));
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
|
||||
+ (instancetype)companion_ __attribute__((swift_name("init()")));
|
||||
@property (readonly) int32_t _42 __attribute__((swift_name("_42")));
|
||||
@end;
|
||||
|
||||
@interface ValuesEnumeration (ValuesKt)
|
||||
- (ValuesEnumeration *)getAnswer __attribute__((swift_name("getAnswer()")));
|
||||
@end;
|
||||
|
||||
@@ -468,3 +468,26 @@ class TestClashesImpl : TestClashes1, TestClashes2 {
|
||||
override val clashingProperty_: Int
|
||||
get() = 2
|
||||
}
|
||||
|
||||
class TestInvalidIdentifiers {
|
||||
class `$Foo`
|
||||
class `Bar$`
|
||||
|
||||
fun `a$d$d`(`$1`: Int, `2`: Int, `3`: Int): Int = `$1` + `2` + `3`
|
||||
|
||||
var `$status`: String = ""
|
||||
|
||||
enum class E(val value: Int) {
|
||||
`4$`(4),
|
||||
`5$`(5),
|
||||
`_`(6),
|
||||
`__`(7)
|
||||
}
|
||||
|
||||
companion object `Companion$` {
|
||||
val `42` = 42
|
||||
}
|
||||
|
||||
val `$` = '$'
|
||||
val `_` = '_'
|
||||
}
|
||||
|
||||
@@ -623,6 +623,27 @@ func testClashes() throws {
|
||||
try assertEquals(actual: 2, expected: test2.clashingProperty__ as! Int32)
|
||||
}
|
||||
|
||||
func testInvalidIdentifiers() throws {
|
||||
let test = TestInvalidIdentifiers()
|
||||
|
||||
try assertTrue(TestInvalidIdentifiers._Foo() is TestInvalidIdentifiers._Foo)
|
||||
try assertFalse(TestInvalidIdentifiers.Bar_() is TestInvalidIdentifiers._Foo)
|
||||
|
||||
try assertEquals(actual: 42, expected: test.a_d_d(_1: 13, _2: 14, _3: 15))
|
||||
|
||||
test._status = "OK"
|
||||
try assertEquals(actual: "OK", expected: test._status)
|
||||
|
||||
try assertEquals(actual: TestInvalidIdentifiers.E._4_.value, expected: 4)
|
||||
try assertEquals(actual: TestInvalidIdentifiers.E._5_.value, expected: 5)
|
||||
try assertEquals(actual: TestInvalidIdentifiers.E.__.value, expected: 6)
|
||||
try assertEquals(actual: TestInvalidIdentifiers.E.___.value, expected: 7)
|
||||
|
||||
try assertEquals(actual: TestInvalidIdentifiers.Companion_()._42, expected: 42)
|
||||
|
||||
try assertEquals(actual: Set([test.__, test.___]), expected: Set(["$".utf16.first, "_".utf16.first]))
|
||||
}
|
||||
|
||||
// See https://github.com/JetBrains/kotlin-native/issues/2931
|
||||
func testGH2931() throws {
|
||||
for i in 0..<50000 {
|
||||
@@ -689,6 +710,7 @@ class ValuesTests : TestProvider {
|
||||
TestCase(name: "TestKClass", method: withAutorelease(testKClass)),
|
||||
TestCase(name: "TestSR10177Workaround", method: withAutorelease(testSR10177Workaround)),
|
||||
TestCase(name: "TestClashes", method: withAutorelease(testClashes)),
|
||||
TestCase(name: "TestInvalidIdentifiers", method: withAutorelease(testInvalidIdentifiers)),
|
||||
TestCase(name: "TestGH2931", method: withAutorelease(testGH2931)),
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user