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

Reply via email to