https://github.com/allevato created https://github.com/llvm/llvm-project/pull/199531
This is the upstream version of https://github.com/swiftlang/llvm-project/pull/12995 from the swiftlang fork. **Motivation:** Swift 6.2 added support for [raw identifiers](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0451-escaped-identifiers.md), which are backtick-delimited identifiers that can contain non-identifier characters like `` let `hello world` = `foo/bar:baz` ``. This change ensures that those identifiers can be used when setting Swift names for C decls in APINotes and the `swift_name` attribute. >From 3d839735f3e275da078e41866386c00dcc8407ba Mon Sep 17 00:00:00 2001 From: Tony Allevato <[email protected]> Date: Sat, 16 May 2026 12:01:06 -0400 Subject: [PATCH] [Sema] Add support for Swift raw identifiers in the `swift_name` attribute and APINotes. --- clang/lib/Sema/SemaSwift.cpp | 91 ++++++++++++++++--- .../Headers/RawIdentifiers.apinotes | 14 +++ .../Headers/RawIdentifiers.h | 14 +++ .../Modules/module.modulemap | 5 + .../APINotes/swift-name-raw-identifiers.m | 23 +++++ clang/test/SemaObjC/attr-swift_name.m | 27 ++++++ 6 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.apinotes create mode 100644 clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.h create mode 100644 clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Modules/module.modulemap create mode 100644 clang/test/APINotes/swift-name-raw-identifiers.m diff --git a/clang/lib/Sema/SemaSwift.cpp b/clang/lib/Sema/SemaSwift.cpp index f0c9cc8620af9..687e1c76045b0 100644 --- a/clang/lib/Sema/SemaSwift.cpp +++ b/clang/lib/Sema/SemaSwift.cpp @@ -72,13 +72,78 @@ static bool isValidSwiftErrorResultType(QualType Ty) { return isValidSwiftContextType(Ty); } +static bool isValidIdentifierEscapedChar(char c) { + if (c == '`' || c == '\\') + return false; + + unsigned char uc = static_cast<unsigned char>(c); + // ASCII control characters and non-ASCII characters are not allowed. + if (uc < 0x20 || uc >= 0x7F) + return false; + + return true; +} + +static bool isValidAsEscapedIdentifier(StringRef string) { + if (string.empty()) + return false; + + bool allSpace = true; + for (char c : string) { + if (!isValidIdentifierEscapedChar(c)) + return false; + if (c != ' ') + allSpace = false; + } + + return !allSpace; +} + +static std::pair<StringRef, StringRef> backtickAwareSplit(StringRef text, + char separator) { + bool inBackticks = false; + for (size_t i = 0; i < text.size(); ++i) { + char c = text[i]; + if (c == '`') { + inBackticks = !inBackticks; + } else if (c == separator && !inBackticks) { + return {text.substr(0, i), text.substr(i + 1)}; + } + } + return {text, StringRef()}; +} + +static std::pair<StringRef, StringRef> backtickAwareRSplit(StringRef text, + char separator) { + bool inBackticks = false; + for (size_t i = text.size(); i > 0; --i) { + char c = text[i - 1]; + if (c == '`') { + inBackticks = !inBackticks; + } else if (c == separator && !inBackticks) { + return {text.substr(0, i - 1), text.substr(i)}; + } + } + return {text, StringRef()}; +} + +/// Returns true if the string is a valid ASCII Swift identifier. This includes +/// raw identifiers if they are surrounded by backticks (e.g., "`My Struct`"). +static bool isValidSwiftIdentifier(StringRef text) { + if (text.size() > 2 && text.front() == '`' && text.back() == '`') + return isValidAsEscapedIdentifier(text.drop_front().drop_back()); + return isValidAsciiIdentifier(text); +} + static bool isValidSwiftContextName(StringRef ContextName) { // ContextName might be qualified, e.g. 'MyNamespace.MyStruct'. - SmallVector<StringRef, 1> ContextNameComponents; - ContextName.split(ContextNameComponents, '.'); - return all_of(ContextNameComponents, [&](StringRef Component) { - return isValidAsciiIdentifier(Component); - }); + StringRef First, Rest = ContextName; + do { + std::tie(First, Rest) = backtickAwareSplit(Rest, '.'); + if (!isValidSwiftIdentifier(First)) + return false; + } while (!Rest.empty()); + return true; } void SemaSwift::handleAttrAttr(Decl *D, const ParsedAttr &AL) { @@ -360,11 +425,11 @@ static bool validateSwiftFunctionName(Sema &S, const ParsedAttr &AL, bool IsMember = false; StringRef ContextName, BaseName, Parameters; - std::tie(BaseName, Parameters) = Name.split('('); + std::tie(BaseName, Parameters) = backtickAwareSplit(Name, '('); - // Split at the first '.', if it exists, which separates the context name + // Split at the last '.', if it exists, which separates the context name // from the base name. - std::tie(ContextName, BaseName) = BaseName.rsplit('.'); + std::tie(ContextName, BaseName) = backtickAwareRSplit(BaseName, '.'); if (BaseName.empty()) { BaseName = ContextName; ContextName = StringRef(); @@ -376,7 +441,7 @@ static bool validateSwiftFunctionName(Sema &S, const ParsedAttr &AL, IsMember = true; } - if (!isValidAsciiIdentifier(BaseName) || BaseName == "_") { + if (!isValidSwiftIdentifier(BaseName) || BaseName == "_") { S.Diag(Loc, diag::warn_attr_swift_name_invalid_identifier) << AL << /*basename*/ 0; return false; @@ -424,9 +489,9 @@ static bool validateSwiftFunctionName(Sema &S, const ParsedAttr &AL, unsigned NewValueCount = 0; std::optional<unsigned> NewValueLocation; do { - std::tie(CurrentParam, Parameters) = Parameters.split(':'); + std::tie(CurrentParam, Parameters) = backtickAwareSplit(Parameters, ':'); - if (!isValidAsciiIdentifier(CurrentParam)) { + if (!isValidSwiftIdentifier(CurrentParam)) { S.Diag(Loc, diag::warn_attr_swift_name_invalid_identifier) << AL << /*parameter*/ 2; return false; @@ -592,7 +657,7 @@ bool SemaSwift::DiagnoseName(Decl *D, StringRef Name, SourceLocation Loc, !IsAsync) { StringRef ContextName, BaseName; - std::tie(ContextName, BaseName) = Name.rsplit('.'); + std::tie(ContextName, BaseName) = backtickAwareRSplit(Name, '.'); if (BaseName.empty()) { BaseName = ContextName; ContextName = StringRef(); @@ -602,7 +667,7 @@ bool SemaSwift::DiagnoseName(Decl *D, StringRef Name, SourceLocation Loc, return false; } - if (!isValidAsciiIdentifier(BaseName)) { + if (!isValidSwiftIdentifier(BaseName)) { Diag(Loc, diag::warn_attr_swift_name_invalid_identifier) << AL << /*basename*/ 0; return false; diff --git a/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.apinotes b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.apinotes new file mode 100644 index 0000000000000..736f3c2e42f62 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.apinotes @@ -0,0 +1,14 @@ +--- +Name: RawIdentifiers +Classes: +- Name: NSSomeClass + SwiftName: '`Some Class`' + Methods: + - Selector: 'methodWithRawName:' + SwiftName: '`raw method`(`raw param`:)' + MethodKind: Instance +Enumerators: +- Name: NSSomeEnumWithRed + SwiftName: red +- Name: NSSomeEnumWithRawName + SwiftName: '`raw constant`' diff --git a/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.h b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.h new file mode 100644 index 0000000000000..44d3e1af44857 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Headers/RawIdentifiers.h @@ -0,0 +1,14 @@ +@interface NSSomeClass +-(instancetype)init; +@end + +@interface NSSomeClass (SomeCategory) +- (void)methodWithRawName:(int)x; +@end + +enum NSSomeEnum { + NSSomeEnumWithRed, + NSSomeEnumWithGreen, + NSSomeEnumWithBlue, + NSSomeEnumWithRawName, +}; diff --git a/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Modules/module.modulemap b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..a4fe650e3f3f2 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/RawIdentifiers.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module RawIdentifiers { + umbrella header "RawIdentifiers.h" + export * + module * { export * } +} diff --git a/clang/test/APINotes/swift-name-raw-identifiers.m b/clang/test/APINotes/swift-name-raw-identifiers.m new file mode 100644 index 0000000000000..4719ac5d7ab21 --- /dev/null +++ b/clang/test/APINotes/swift-name-raw-identifiers.m @@ -0,0 +1,23 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/RawIdentifiers -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -x objective-c +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/RawIdentifiers -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter NSSomeClass -x objective-c | FileCheck %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/RawIdentifiers -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter NSSomeEnumWith -x objective-c | FileCheck -check-prefix=CHECK-ENUM-CASE %s + +#import <RawIdentifiers/RawIdentifiers.h> + +// CHECK: Dumping NSSomeClass: +// CHECK-NEXT: ObjCInterfaceDecl {{.+}} imported in RawIdentifiers <undeserialized declarations> NSSomeClass +// CHECK-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "`Some Class`" + +// CHECK: Dumping NSSomeClass::methodWithRawName:: +// CHECK-NEXT: ObjCMethodDecl {{.+}} imported in RawIdentifiers - methodWithRawName: 'void' +// CHECK-NEXT: ParmVarDecl +// CHECK-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "`raw method`(`raw param`:)" + +// CHECK-ENUM-CASE: Dumping NSSomeEnumWithRed: +// CHECK-ENUM-CASE-NEXT: EnumConstantDecl {{.+}} imported in RawIdentifiers NSSomeEnumWithRed 'int' +// CHECK-ENUM-CASE-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "red" + +// CHECK-ENUM-CASE: Dumping NSSomeEnumWithRawName: +// CHECK-ENUM-CASE-NEXT: EnumConstantDecl {{.+}} imported in RawIdentifiers NSSomeEnumWithRawName 'int' +// CHECK-ENUM-CASE-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "`raw constant`" diff --git a/clang/test/SemaObjC/attr-swift_name.m b/clang/test/SemaObjC/attr-swift_name.m index e77a4bb04ddd9..fb9effa22defe 100644 --- a/clang/test/SemaObjC/attr-swift_name.m +++ b/clang/test/SemaObjC/attr-swift_name.m @@ -205,3 +205,30 @@ - (void)brokenAttr __attribute__((__swift_async_name__("brokenAttr", 2))); // expected-error@+1 {{'__swift_async_name__' attribute only applies to Objective-C methods and functions}} SWIFT_ASYNC_NAME("NoAsync") @protocol NoAsync @end + +// --- Raw identifiers + +@interface RawIdentifiers +- (void)m1:(int)x SWIFT_NAME("regularBase(`raw param`:)"); +- (void)m2:(int)x SWIFT_NAME("`raw base`(`raw param` :)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} +- (void)m3:(int)x SWIFT_NAME("`raw base`(`raw param`:)"); +- (void)m4:(int)x SWIFT_NAME("`raw base`(`raw param` :)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} +- (void)m5:(int)x SWIFT_NAME("` `(param:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the base name}} +- (void)m6:(int)x SWIFT_NAME("base(` `:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} +- (void)m7:(int)x SWIFT_NAME("`ba\\se`(_:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the base name}} +- (void)m8:(int)x SWIFT_NAME("base(`pa\\ram`:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} + +- (void)m9:(int)x SWIFT_NAME("if(for:)"); +- (void)m10:(int)x SWIFT_NAME("`if`(`for`:)"); +@end + +void goodQualifiedRawFn1(int x) SWIFT_NAME("`Raw Module`.`Raw Class`.`raw base`(`raw param`:)"); + +// Ensure we don't split delimiters inside backticks +void goodQualifiedRawFn2(int x) SWIFT_NAME("`Raw.(Module`.`Raw.)Class`.`raw:.(base)`(`raw:param`:)"); + +// Mismatched or extra backticks +void badQualifiedRawFn1(int x) SWIFT_NAME("`raw base(`raw param`:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the base name}} +void badQualifiedRawFn2(int x) SWIFT_NAME("`raw``base`(`raw param`:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the base name}} +void badQualifiedRawFn3(int x) SWIFT_NAME("`raw base`(`raw param:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} +void badQualifiedRawFn4(int x) SWIFT_NAME("`raw base`(`raw``param`:)"); // expected-warning {{'__swift_name__' attribute has invalid identifier for the parameter name}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
