https://github.com/eisenwave created 
https://github.com/llvm/llvm-project/pull/206326

1. Fix typo in `note_invalid_ucn_name_loose_matching` message.
2. Fix unprintable characters appearing in diagnostic messages.
3. Stop offering low-value fix suggestions when illegal characters appear in 
the name.

>From e2a20d710a1de9b67bcefe162ec3034249722c35 Mon Sep 17 00:00:00 2001
From: Eisenwave <[email protected]>
Date: Sun, 28 Jun 2026 14:08:36 +0200
Subject: [PATCH] [clang] Improve diagnostics for invalid
 named-universal-characters

1. Fix typo in note_invalid_ucn_name_loose_matching message.
2. Fix unprintable characterss appearing in diagnostic messages.
3. Stop offering low-value fix suggestions when illegal characters appear in 
the name.
---
 .../include/clang/Basic/DiagnosticLexKinds.td |  6 +-
 clang/lib/Lex/LiteralSupport.cpp              | 60 ++++++++++++++++---
 clang/test/CXX/drs/cwg26xx.cpp                |  6 +-
 clang/test/Lexer/char-escapes-delimited.c     | 10 +++-
 clang/test/Lexer/unicode.c                    |  2 +-
 clang/test/Preprocessor/ucn-pp-identifier.c   |  2 +-
 6 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td 
b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 383bf1a7fdb3f..decfd6c781dbf 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -170,9 +170,11 @@ def err_hex_escape_no_digits : Error<
 def err_invalid_ucn_name : Error<
   "'%0' is not a valid Unicode character name">;
 def note_invalid_ucn_name_loose_matching : Note<
-  "characters names in Unicode escape sequences are sensitive to case and 
whitespaces">;
+  "character names in Unicode escape sequences are sensitive to case and 
whitespaces">;
 def note_invalid_ucn_name_candidate : Note<
-  "did you mean %0 ('%2' U+%1)?">;
+  "did you mean %0 (%1)?">;
+def note_invalid_ucn_name_character : Note<
+  "character %0 cannot appear in a Unicode character name">;
 
 def warn_ucn_escape_no_digits : Warning<
   "\\%0 used with no following hex digits; "
diff --git a/clang/lib/Lex/LiteralSupport.cpp b/clang/lib/Lex/LiteralSupport.cpp
index 482146ccf8654..e941e5eecbe8a 100644
--- a/clang/lib/Lex/LiteralSupport.cpp
+++ b/clang/lib/Lex/LiteralSupport.cpp
@@ -539,6 +539,12 @@ static bool ProcessNumericUCNEscape(const char 
*ThisTokBegin,
   return !HasError;
 }
 
+static bool AllowedInCharacterName(llvm::UTF32 CodePoint) {
+  return (CodePoint >= U'A' && CodePoint <= U'Z') ||
+         (CodePoint >= U'0' && CodePoint <= U'9') || CodePoint == U'-' ||
+         CodePoint == U' ';
+}
+
 static void DiagnoseInvalidUnicodeCharacterName(
     DiagnosticsEngine *Diags, const LangOptions &Features, FullSourceLoc Loc,
     const char *TokBegin, const char *TokRangeBegin, const char *TokRangeEnd,
@@ -550,6 +556,44 @@ static void DiagnoseInvalidUnicodeCharacterName(
 
   namespace u = llvm::sys::unicode;
 
+  auto StringifyCodePoint = [](llvm::UTF32 CodePoint) -> llvm::SmallString<16> 
{
+    llvm::SmallString<16> Result;
+    if (u::isPrintable(CodePoint)) {
+      std::string CharUTF8;
+      llvm::convertUTF32ToUTF8String(llvm::ArrayRef<llvm::UTF32>(&CodePoint, 
1),
+                                     CharUTF8);
+      Result.append("'");
+      Result.append(CharUTF8);
+      Result.append("' U+");
+    } else {
+      Result.append("U+");
+    }
+    llvm::raw_svector_ostream OS(Result);
+    llvm::write_hex(OS, CodePoint, llvm::HexPrintStyle::Upper, 4);
+    return Result;
+  };
+
+  bool HasIllegalCharacter = false;
+  for (const char *P = Name.begin(), *E = Name.end(); P != E;) {
+    const auto *Src = reinterpret_cast<const llvm::UTF8 *>(P);
+    const auto *SrcEnd = reinterpret_cast<const llvm::UTF8 *>(E);
+    llvm::UTF32 CodePoint = 0;
+    if (llvm::convertUTF8Sequence(&Src, SrcEnd, &CodePoint,
+                                  llvm::strictConversion) != 
llvm::conversionOK)
+      break;
+    if (AllowedInCharacterName(CodePoint)) {
+      P = reinterpret_cast<const char *>(Src);
+      continue;
+    }
+    SourceLocation CharLoc = Lexer::AdvanceToTokenCharacter(
+        Loc, (TokRangeBegin - TokBegin) + (P - Name.begin()), Loc.getManager(),
+        Features);
+    Diags->Report(CharLoc, diag::note_invalid_ucn_name_character)
+        << StringifyCodePoint(CodePoint);
+    HasIllegalCharacter = true;
+    break;
+  }
+
   std::optional<u::LooseMatchingResult> Res =
       u::nameToCodepointLooseMatching(Name);
   if (Res) {
@@ -562,6 +606,12 @@ static void DiagnoseInvalidUnicodeCharacterName(
     return;
   }
 
+  // Providing illegal characters suggests a fundamental misuse of the feature,
+  // like providing emoji in \N{}. Offering alternative suggestions is often
+  // unhelpful in that scenario.
+  if (HasIllegalCharacter)
+    return;
+
   unsigned Distance = 0;
   SmallVector<u::MatchForCodepointName> Matches =
       u::nearestMatchesForCodepointName(Name, 5);
@@ -576,17 +626,9 @@ static void DiagnoseInvalidUnicodeCharacterName(
       break;
     Distance = Match.Distance;
 
-    std::string Str;
-    llvm::UTF32 V = Match.Value;
-    bool Converted =
-        llvm::convertUTF32ToUTF8String(llvm::ArrayRef<llvm::UTF32>(&V, 1), 
Str);
-    (void)Converted;
-    assert(Converted && "Found a match wich is not a unicode character");
-
     Diag(Diags, Features, Loc, TokBegin, TokRangeBegin, TokRangeEnd,
          diag::note_invalid_ucn_name_candidate)
-        << Match.Name << llvm::utohexstr(Match.Value)
-        << Str // FIXME: Fix the rendering of non printable characters
+        << Match.Name << StringifyCodePoint(Match.Value)
         << FixItHint::CreateReplacement(
                MakeCharSourceRange(Features, Loc, TokBegin, TokRangeBegin,
                                    TokRangeEnd),
diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp
index 45653743ae574..6a15513879bbd 100644
--- a/clang/test/CXX/drs/cwg26xx.cpp
+++ b/clang/test/CXX/drs/cwg26xx.cpp
@@ -214,11 +214,7 @@ int \N{Λ} = 0;
 // expected-error@-2 {{expected unqualified-id}}
 const char* emoji = "\N{🤡}";
 // expected-error@-1 {{'🤡' is not a valid Unicode character name}}
-//   expected-note@-2 {{did you mean OX ('🐂' U+1F402)?}}
-//   expected-note@-3 {{did you mean ANT ('🐜' U+1F41C)?}}
-//   expected-note@-4 {{did you mean ARC ('⌒' U+2312)?}}
-//   expected-note@-5 {{did you mean AXE ('🪓' U+1FA93)?}}
-//   expected-note@-6 {{did you mean BAT ('🦇' U+1F987)?}}
+// expected-note@-2 {{character '🤡' U+1F921 cannot appear in a Unicode 
character name}}
 
 #define z(x) 0
 #define cwg2640_a z(
diff --git a/clang/test/Lexer/char-escapes-delimited.c 
b/clang/test/Lexer/char-escapes-delimited.c
index 7a8986bc5f867..1c20456701d75 100644
--- a/clang/test/Lexer/char-escapes-delimited.c
+++ b/clang/test/Lexer/char-escapes-delimited.c
@@ -82,7 +82,8 @@ void named(void) {
 
   char b  = '\N{DOLLAR SIGN}'; // ext-warning {{extension}} cxx23-warning 
{{C++23}}
   char b_ = '\N{ DOL-LAR _SIGN }'; // expected-error {{' DOL-LAR _SIGN ' is 
not a valid Unicode character name}} \
-                               // expected-note {{characters names in Unicode 
escape sequences are sensitive to case and whitespaces}}
+                               // expected-note {{character names in Unicode 
escape sequences are sensitive to case and whitespaces}} \
+                               // expected-note {{character '_' U+005F cannot 
appear in a Unicode character name}}
 
   char c = '\N{NOTATHING}'; // expected-error {{'NOTATHING' is not a valid 
Unicode character name}} \
                             // expected-note 5{{did you mean}}
@@ -100,9 +101,12 @@ void named(void) {
   unsigned k = u'\N{LOTUS';                       // expected-error 
{{incomplete universal character name}}
 
   const char* emoji = "\N{🤡}"; // expected-error {{'🤡' is not a valid Unicode 
character name}} \
-                                // expected-note 5{{did you mean}}
+                                // expected-note {{character '🤡' U+1F921 
cannot appear in a Unicode character name}}
   const char* nested = "\N{\N{SPARKLE}}"; // expected-error {{'\N{SPARKLE' is 
not a valid Unicode character name}} \
-                                          // expected-note 5{{did you mean}}
+                                          // expected-note {{cannot appear in 
a Unicode character name}}
+  const char* line_feed = "\N{LINE FEE}"; // expected-error {{'LINE FEE' is 
not a valid Unicode character name}} \
+                                          // expected-note {{did you mean LINE 
FEED (U+000A)}} \
+                                          // expected-note 4{{did you mean}}
 }
 
 void separators(void) {
diff --git a/clang/test/Lexer/unicode.c b/clang/test/Lexer/unicode.c
index e0489e11b9da9..2d1edc77a1550 100644
--- a/clang/test/Lexer/unicode.c
+++ b/clang/test/Lexer/unicode.c
@@ -49,7 +49,7 @@ extern int \u{16D80};   // CHISOI LETTER A - Added in Unicode 
18.0
 extern int a\N{TANGSA LETTER GA};
 extern int a\N{TANGSALETTERGA}; // expected-error {{'TANGSALETTERGA' is not a 
valid Unicode character name}} \
                                 // expected-error {{expected ';' after top 
level declarator}} \
-                                // expected-note {{characters names in Unicode 
escape sequences are sensitive to case and whitespace}}
+                                // expected-note {{character names in Unicode 
escape sequences are sensitive to case and whitespace}}
 
 extern int 𝛛; // expected-warning {{mathematical notation character <U+1D6DB> 
in an identifier is a Clang extension}}
 extern int ₉; // expected-error {{character <U+2089> not allowed at the start 
of an identifier}} \\
diff --git a/clang/test/Preprocessor/ucn-pp-identifier.c 
b/clang/test/Preprocessor/ucn-pp-identifier.c
index 5efcfe48f638a..ee008a73eb882 100644
--- a/clang/test/Preprocessor/ucn-pp-identifier.c
+++ b/clang/test/Preprocessor/ucn-pp-identifier.c
@@ -131,7 +131,7 @@ C 1
                        // expected-error {{macro name must be an identifier}}
 #define \NN            // expected-warning {{incomplete universal character 
name; treating as '\' followed by identifier}} expected-error {{macro name must 
be an identifier}}
 #define \N{GREEK_SMALL-LETTERALPHA}  // expected-error 
{{'GREEK_SMALL-LETTERALPHA' is not a valid Unicode character name}} \
-                                     // expected-note {{characters names in 
Unicode escape sequences are sensitive to case and whitespaces}}
+                                     // expected-note {{character names in 
Unicode escape sequences are sensitive to case and whitespaces}}
 #define \N{🤡}  // expected-error {{'🤡' is not a valid Unicode character name}} 
\
                 // expected-error {{macro name must be an identifier}}
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to