Improve support for invalid identifiers when producing framework

This commit is contained in:
Svyatoslav Scherbina
2019-06-24 11:25:42 +03:00
committed by SvyatoslavScherbina
parent 0831ca7e87
commit 9156cbd758
5 changed files with 127 additions and 18 deletions
@@ -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\"")
}
@@ -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)),
]
}