Author: Jan Schultke
Date: 2026-07-01T13:14:48+02:00
New Revision: b8e5ea1ab3e206d861b10d124b77e1026c2a2eb4

URL: 
https://github.com/llvm/llvm-project/commit/b8e5ea1ab3e206d861b10d124b77e1026c2a2eb4
DIFF: 
https://github.com/llvm/llvm-project/commit/b8e5ea1ab3e206d861b10d124b77e1026c2a2eb4.diff

LOG: [clang] Improve diagnostics for invalid named-universal-characters 
(#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.

Added: 
    

Modified: 
    clang/include/clang/Basic/Diagnostic.h
    clang/include/clang/Basic/DiagnosticLexKinds.td
    clang/lib/Basic/Diagnostic.cpp
    clang/lib/Lex/LiteralSupport.cpp
    clang/test/CXX/drs/cwg26xx.cpp
    clang/test/Lexer/char-escapes-delimited.c
    clang/test/Lexer/unicode.c
    clang/test/Preprocessor/ucn-pp-identifier.c
    clang/unittests/Basic/DiagnosticTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Diagnostic.h 
b/clang/include/clang/Basic/Diagnostic.h
index 826b747f2c751..c033320687078 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -28,6 +28,7 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/ConvertUTF.h"
 #include <cassert>
 #include <cstdint>
 #include <limits>
@@ -1876,6 +1877,7 @@ void ProcessWarningOptions(DiagnosticsEngine &Diags,
                            const DiagnosticOptions &Opts,
                            llvm::vfs::FileSystem &VFS, bool ReportDiags = 
true);
 void EscapeStringForDiagnostic(StringRef Str, SmallVectorImpl<char> &OutStr);
+llvm::SmallString<16> DisplayCodePointForDiagnostic(llvm::UTF32 CodePoint);
 } // namespace clang
 
 #endif // LLVM_CLANG_BASIC_DIAGNOSTIC_H

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/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index 4802478c379bb..57fa8a16d3a50 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -1085,6 +1085,26 @@ void clang::EscapeStringForDiagnostic(StringRef Str,
   }
 }
 
+/// Displays a single Unicode codepoint in U+NNNN notation, optionally
+/// prepending the quoted codepoint itself if printable.
+llvm::SmallString<16>
+clang::DisplayCodePointForDiagnostic(llvm::UTF32 CodePoint) {
+  llvm::SmallString<16> Result;
+  if (llvm::sys::unicode::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;
+}
+
 void Diagnostic::FormatDiagnostic(const char *DiagStr, const char *DiagEnd,
                                   SmallVectorImpl<char> &OutStr) const {
   // When the diagnostic string is only "%0", the entire string is being given

diff  --git a/clang/lib/Lex/LiteralSupport.cpp 
b/clang/lib/Lex/LiteralSupport.cpp
index 482146ccf8654..72a65dd156b19 100644
--- a/clang/lib/Lex/LiteralSupport.cpp
+++ b/clang/lib/Lex/LiteralSupport.cpp
@@ -539,6 +539,11 @@ static bool ProcessNumericUCNEscape(const char 
*ThisTokBegin,
   return !HasError;
 }
 
+static bool allowedInCharacterName(char C) {
+  return (C >= 'A' && C <= 'Z') || (C >= '0' && C <= '9') || C == '-' ||
+         C == ' ';
+}
+
 static void DiagnoseInvalidUnicodeCharacterName(
     DiagnosticsEngine *Diags, const LangOptions &Features, FullSourceLoc Loc,
     const char *TokBegin, const char *TokRangeBegin, const char *TokRangeEnd,
@@ -550,6 +555,27 @@ static void DiagnoseInvalidUnicodeCharacterName(
 
   namespace u = llvm::sys::unicode;
 
+  bool HasIllegalCharacter = false;
+  for (const char *P = Name.begin(), *E = Name.end(); P != E;) {
+    if (allowedInCharacterName(*P)) {
+      ++P;
+      continue;
+    }
+    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;
+    SourceLocation CharLoc = Lexer::AdvanceToTokenCharacter(
+        Loc, (TokRangeBegin - TokBegin) + (P - Name.begin()), Loc.getManager(),
+        Features);
+    Diags->Report(CharLoc, diag::note_invalid_ucn_name_character)
+        << DisplayCodePointForDiagnostic(CodePoint);
+    HasIllegalCharacter = true;
+    break;
+  }
+
   std::optional<u::LooseMatchingResult> Res =
       u::nameToCodepointLooseMatching(Name);
   if (Res) {
@@ -562,6 +588,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 +608,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 << DisplayCodePointForDiagnostic(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}}
 

diff  --git a/clang/unittests/Basic/DiagnosticTest.cpp 
b/clang/unittests/Basic/DiagnosticTest.cpp
index 4ced52c8f715f..5cc0c39e5cb51 100644
--- a/clang/unittests/Basic/DiagnosticTest.cpp
+++ b/clang/unittests/Basic/DiagnosticTest.cpp
@@ -449,4 +449,24 @@ TEST_F(SuppressionMappingTest, 
CanonicalizesSlashesOnWindows) {
 }
 #endif
 
+TEST(DisplayCodePointForDiagnosticTest, printableDisplaysQuoted) {
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U'A'), "'A' U+0041");
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U'🤡'), "'🤡' U+1F921");
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U' '), "' ' U+0020");
+}
+
+TEST(DisplayCodePointForDiagnosticTest, nonPrintableDisplaysNoQuoted) {
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U'\n'), "U+000A");
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U'\0'), "U+0000");
+  EXPECT_EQ(DisplayCodePointForDiagnostic(U'\x1B'), "U+001B");
+}
+
+TEST(DisplayCodePointForDiagnosticTest, nonScalarValues) {
+  // Low and high surrogates:
+  EXPECT_EQ(DisplayCodePointForDiagnostic(0xD800), "U+D800");
+  EXPECT_EQ(DisplayCodePointForDiagnostic(0xDFFF), "U+DFFF");
+  // Overly large values:
+  EXPECT_EQ(DisplayCodePointForDiagnostic(0x110000), "U+110000");
+}
+
 } // namespace


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

Reply via email to