https://github.com/Michael137 updated 
https://github.com/llvm/llvm-project/pull/169445

>From 34c5c5188bb898255bbc16ba4d4dc23c2c62fd37 Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Tue, 25 Nov 2025 09:46:57 +0900
Subject: [PATCH 1/2] [clang][TypePrinter] Make printing of anonymous/unnamed
 tag types consistent

In https://github.com/llvm/llvm-project/pull/168534 we made the `TypePrinter` 
re-use `printNestedNameSpecifier` for printing scopes. However, the way that 
the names of anonymous/unnamed get printed by the two are slightly inconsistent 
with each other.

`printNestedNameSpecifier` calls all `TagType`s without an identifer 
`(anonymous)`. On the other hand, `TypePrinter` prints them slightly more 
accurate (it differentiates anonymous vs. unnamed decls) and allows for some 
additional customization points. E.g., with `MSVCFormatting`, it will print 
`\`'` instead of `()`. `printNestedNameSpecifier` already accounts for 
`MSVCFormatting` for namespaces, but doesn't for `TagType`s. This inconsistency 
means that if an unnamed tag is printed as part of a scope it's displayed as 
`(anonymous struct)`, but if it's the entity being whose scope is being 
printed, then it shows as `(unnamed struct)`.

This patch moves the printing of anonymous/unnamed tags into a common helper 
and re-uses it from `TypePrinter` and `printNestedNameSpecifier`.  We also do 
this from the AST matchers because they were aligned with how 
`printNestedNameSpecifier` represents the name.

I wasn't sure where to put the helper, so I just put it in `TypeBase.h` for 
now. Though I suspect there's a better home for it, possibly `DeclBase.h`?
---
 clang/include/clang/AST/TypeBase.h            |   4 +
 clang/lib/AST/Decl.cpp                        |   5 +-
 clang/lib/AST/TypePrinter.cpp                 | 106 ++++++++++--------
 clang/lib/ASTMatchers/ASTMatchersInternal.cpp |  10 +-
 clang/unittests/AST/NamedDeclPrinterTest.cpp  |  28 +++++
 clang/unittests/AST/TypePrinterTest.cpp       |   2 +-
 .../ASTMatchers/ASTMatchersNarrowingTest.cpp  |  33 +++++-
 7 files changed, 133 insertions(+), 55 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index f07861f50fe8c..2f27deda2cd66 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -7374,6 +7374,10 @@ bool isSubstitutedDefaultArgument(ASTContext &Ctx, 
TemplateArgument Arg,
                                   ArrayRef<TemplateArgument> Args,
                                   unsigned Depth);
 
+void printAnonymousTagDecl(llvm::raw_ostream &OS, const TagDecl *D,
+                           const PrintingPolicy &Policy,
+                           bool PrintKindDecoration, bool PrintTagLocations);
+
 /// Represents a qualified type name for which the type name is
 /// dependent.
 ///
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 591457b1d66b4..c9bb414a9a1de 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -35,6 +35,7 @@
 #include "clang/AST/Stmt.h"
 #include "clang/AST/TemplateBase.h"
 #include "clang/AST/Type.h"
+#include "clang/AST/TypeBase.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/IdentifierTable.h"
@@ -1793,7 +1794,9 @@ void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
       if (TypedefNameDecl *TD = RD->getTypedefNameForAnonDecl())
         OS << *TD;
       else if (!RD->getIdentifier())
-        OS << "(anonymous " << RD->getKindName() << ')';
+        printAnonymousTagDecl(OS, llvm::cast<TagDecl>(RD), P,
+                              /*PrintKindDecoration=*/true,
+                              /*AllowSourceLocations=*/false);
       else
         OS << *RD;
     } else if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..93bd068ccee08 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -23,6 +23,7 @@
 #include "clang/AST/TemplateBase.h"
 #include "clang/AST/TemplateName.h"
 #include "clang/AST/Type.h"
+#include "clang/AST/TypeBase.h"
 #include "clang/Basic/AddressSpaces.h"
 #include "clang/Basic/AttrKinds.h"
 #include "clang/Basic/ExceptionSpecificationType.h"
@@ -1518,11 +1519,11 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
     return;
   }
 
-  bool HasKindDecoration = false;
+  bool PrintKindDecoration = Policy.AnonymousTagLocations;
 
   if (T->isCanonicalUnqualified()) {
     if (!Policy.SuppressTagKeyword && !D->getTypedefNameForAnonDecl()) {
-      HasKindDecoration = true;
+      PrintKindDecoration = false;
       OS << D->getKindName();
       OS << ' ';
     }
@@ -1546,51 +1547,10 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
   else if (TypedefNameDecl *Typedef = D->getTypedefNameForAnonDecl()) {
     assert(Typedef->getIdentifier() && "Typedef without identifier?");
     OS << Typedef->getIdentifier()->getName();
-  } else {
-    // Make an unambiguous representation for anonymous types, e.g.
-    //   (anonymous enum at /usr/include/string.h:120:9)
-    OS << (Policy.MSVCFormatting ? '`' : '(');
-
-    if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
-      OS << "lambda";
-      HasKindDecoration = true;
-    } else if ((isa<RecordDecl>(D) && 
cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
-      OS << "anonymous";
-    } else {
-      OS << "unnamed";
-    }
-
-    if (Policy.AnonymousTagLocations) {
-      // Suppress the redundant tag keyword if we just printed one.
-      // We don't have to worry about ElaboratedTypes here because you can't
-      // refer to an anonymous type with one.
-      if (!HasKindDecoration)
-        OS << " " << D->getKindName();
-
-      PresumedLoc PLoc = D->getASTContext().getSourceManager().getPresumedLoc(
-          D->getLocation());
-      if (PLoc.isValid()) {
-        OS << " at ";
-        StringRef File = PLoc.getFilename();
-        llvm::SmallString<1024> WrittenFile(File);
-        if (auto *Callbacks = Policy.Callbacks)
-          WrittenFile = Callbacks->remapPath(File);
-        // Fix inconsistent path separator created by
-        // clang::DirectoryLookup::LookupFile when the file path is relative
-        // path.
-        llvm::sys::path::Style Style =
-            llvm::sys::path::is_absolute(WrittenFile)
-                ? llvm::sys::path::Style::native
-                : (Policy.MSVCFormatting
-                       ? llvm::sys::path::Style::windows_backslash
-                       : llvm::sys::path::Style::posix);
-        llvm::sys::path::native(WrittenFile, Style);
-        OS << WrittenFile << ':' << PLoc.getLine() << ':' << PLoc.getColumn();
-      }
-    }
-
-    OS << (Policy.MSVCFormatting ? '\'' : ')');
-  }
+  } else
+    printAnonymousTagDecl(OS, D, Policy,
+                          /*PrintKindDecoration=*/PrintKindDecoration,
+                          /*PrintTagLocations=*/Policy.AnonymousTagLocations);
 
   // If this is a class template specialization, print the template
   // arguments.
@@ -2469,6 +2429,58 @@ static bool isSubstitutedTemplateArgument(ASTContext 
&Ctx, TemplateArgument Arg,
   return false;
 }
 
+void clang::printAnonymousTagDecl(llvm::raw_ostream &OS, const TagDecl *D,
+                                  const PrintingPolicy &Policy,
+                                  bool PrintKindDecoration,
+                                  bool PrintTagLocations) {
+  assert(D);
+
+  // Make an unambiguous representation for anonymous types, e.g.
+  //   (anonymous enum at /usr/include/string.h:120:9)
+  OS << (Policy.MSVCFormatting ? '`' : '(');
+
+  if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
+    PrintKindDecoration = false;
+    OS << "lambda";
+  } else if ((isa<RecordDecl>(D) &&
+              cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
+    OS << "anonymous";
+  } else {
+    OS << "unnamed";
+  }
+
+  // Suppress the redundant tag keyword if we just printed one.
+  // We don't have to worry about ElaboratedTypes here because you can't
+  // refer to an anonymous type with one.
+  if (PrintKindDecoration)
+    OS << " " << D->getKindName();
+
+  if (PrintTagLocations) {
+    PresumedLoc PLoc =
+        D->getASTContext().getSourceManager().getPresumedLoc(D->getLocation());
+    if (PLoc.isValid()) {
+      OS << " at ";
+      StringRef File = PLoc.getFilename();
+      llvm::SmallString<1024> WrittenFile(File);
+      if (auto *Callbacks = Policy.Callbacks)
+        WrittenFile = Callbacks->remapPath(File);
+      // Fix inconsistent path separator created by
+      // clang::DirectoryLookup::LookupFile when the file path is relative
+      // path.
+      llvm::sys::path::Style Style =
+          llvm::sys::path::is_absolute(WrittenFile)
+              ? llvm::sys::path::Style::native
+              : (Policy.MSVCFormatting
+                     ? llvm::sys::path::Style::windows_backslash
+                     : llvm::sys::path::Style::posix);
+      llvm::sys::path::native(WrittenFile, Style);
+      OS << WrittenFile << ':' << PLoc.getLine() << ':' << PLoc.getColumn();
+    }
+  }
+
+  OS << (Policy.MSVCFormatting ? '\'' : ')');
+}
+
 bool clang::isSubstitutedDefaultArgument(ASTContext &Ctx, TemplateArgument Arg,
                                          const NamedDecl *Param,
                                          ArrayRef<TemplateArgument> Args,
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp 
b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index 0874b3d0c45f5..e83a6e97ec638 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/ExprConcepts.h"
 #include "clang/AST/ParentMapContext.h"
 #include "clang/AST/PrettyPrinter.h"
+#include "clang/AST/TypeBase.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Lex/Lexer.h"
@@ -514,7 +515,14 @@ static StringRef getNodeName(const RecordDecl &Node,
     return Node.getName();
   }
   Scratch.clear();
-  return ("(anonymous " + Node.getKindName() + ")").toStringRef(Scratch);
+
+  llvm::raw_svector_ostream OS(Scratch);
+
+  printAnonymousTagDecl(
+      OS, llvm::cast<TagDecl>(&Node), Node.getASTContext().getPrintingPolicy(),
+      /*PrintKindDecoration=*/true, /*PrintTagLocations=*/false);
+
+  return OS.str();
 }
 
 static StringRef getNodeName(const NamespaceDecl &Node,
diff --git a/clang/unittests/AST/NamedDeclPrinterTest.cpp 
b/clang/unittests/AST/NamedDeclPrinterTest.cpp
index cd833725b448d..2fdda1929b2a3 100644
--- a/clang/unittests/AST/NamedDeclPrinterTest.cpp
+++ b/clang/unittests/AST/NamedDeclPrinterTest.cpp
@@ -265,3 +265,31 @@ TEST(NamedDeclPrinter, NestedNameSpecifierTemplateArgs) {
   ASSERT_TRUE(
       PrintedNestedNameSpecifierMatches(Code, "method", "vector<int>::"));
 }
+
+TEST(NamedDeclPrinter, NestedNameSpecifierLambda) {
+  const char *Code =
+      R"(
+        auto l = [] {
+          struct Foo {
+            void method();
+          };
+        };
+)";
+  ASSERT_TRUE(PrintedNestedNameSpecifierMatches(
+      Code, "method", "(lambda)::operator()()::Foo::"));
+}
+
+TEST(NamedDeclPrinter, NestedNameSpecifierAnonymousTags) {
+  const char *Code =
+      R"(
+        struct Foo {
+          struct {
+            struct {
+              void method();
+            } i;
+          };
+        };
+)";
+  ASSERT_TRUE(PrintedNestedNameSpecifierMatches(
+      Code, "method", "Foo::(anonymous)::(unnamed)::"));
+}
diff --git a/clang/unittests/AST/TypePrinterTest.cpp 
b/clang/unittests/AST/TypePrinterTest.cpp
index 3cadf9b265bd1..de4cfa4074eba 100644
--- a/clang/unittests/AST/TypePrinterTest.cpp
+++ b/clang/unittests/AST/TypePrinterTest.cpp
@@ -328,7 +328,7 @@ TEST(TypePrinter, NestedNameSpecifiers) {
   // Further levels of nesting print the entire scope.
   ASSERT_TRUE(PrintedTypeMatches(
       Code, {}, fieldDecl(hasName("u"), hasType(qualType().bind("id"))),
-      "union level1()::Inner::Inner(int)::(anonymous struct)::(unnamed)",
+      "union level1()::Inner::Inner(int)::(unnamed struct)::(unnamed)",
       [](PrintingPolicy &Policy) {
         Policy.FullyQualifiedName = true;
         Policy.AnonymousTagLocations = false;
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp 
b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index 5d452355d0e43..697623c8a48e8 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -2709,25 +2709,48 @@ TEST_P(ASTMatchersTest, 
HasName_MatchesAnonymousNamespaces) {
   EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
 }
 
-TEST_P(ASTMatchersTest, HasName_MatchesAnonymousOuterClasses) {
+TEST_P(ASTMatchersTest, HasName_MatchesUnnamedOuterClasses) {
   if (!GetParam().isCXX()) {
     return;
   }
 
   EXPECT_TRUE(matches("class A { class { class C; } x; };",
-                      recordDecl(hasName("A::(anonymous class)::C"))));
+                      recordDecl(hasName("A::(unnamed class)::C"))));
   EXPECT_TRUE(matches("class A { class { class C; } x; };",
-                      recordDecl(hasName("::A::(anonymous class)::C"))));
+                      recordDecl(hasName("::A::(unnamed class)::C"))));
   EXPECT_FALSE(matches("class A { class { class C; } x; };",
                        recordDecl(hasName("::A::C"))));
   EXPECT_TRUE(matches("class A { struct { class C; } x; };",
-                      recordDecl(hasName("A::(anonymous struct)::C"))));
+                      recordDecl(hasName("A::(unnamed struct)::C"))));
   EXPECT_TRUE(matches("class A { struct { class C; } x; };",
-                      recordDecl(hasName("::A::(anonymous struct)::C"))));
+                      recordDecl(hasName("::A::(unnamed struct)::C"))));
   EXPECT_FALSE(matches("class A { struct { class C; } x; };",
                        recordDecl(hasName("::A::C"))));
 }
 
+TEST_P(ASTMatchersTest, HasName_MatchesAnonymousOuterClasses) {
+  if (!GetParam().isCXX()) {
+    return;
+  }
+
+  EXPECT_TRUE(matches(
+      "class A { struct { struct { class C; } x; }; };",
+      recordDecl(hasName("A::(anonymous struct)::(unnamed struct)::C"))));
+  EXPECT_TRUE(matches(
+      "class A { struct { struct { class C; } x; }; };",
+      recordDecl(hasName("::A::(anonymous struct)::(unnamed struct)::C"))));
+  EXPECT_FALSE(matches("class A { struct { struct { class C; } x; }; };",
+                       recordDecl(hasName("A::(unnamed struct)::C"))));
+  EXPECT_TRUE(matches(
+      "class A { class { public: struct { class C; } x; }; };",
+      recordDecl(hasName("A::(anonymous class)::(unnamed struct)::C"))));
+  EXPECT_TRUE(matches(
+      "class A { class { public: struct { class C; } x; }; };",
+      recordDecl(hasName("::A::(anonymous class)::(unnamed struct)::C"))));
+  EXPECT_FALSE(matches("class A { class { public: struct { class C; } x; }; 
};",
+                       recordDecl(hasName("A::(unnamed struct)::C"))));
+}
+
 TEST_P(ASTMatchersTest, HasName_MatchesFunctionScope) {
   if (!GetParam().isCXX()) {
     return;

>From 65f11ed0c51516708e33aab27777edc07c5f1d0b Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Tue, 25 Nov 2025 12:55:37 +0900
Subject: [PATCH 2/2] fixup! fix test

---
 clang/unittests/AST/NamedDeclPrinterTest.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/unittests/AST/NamedDeclPrinterTest.cpp 
b/clang/unittests/AST/NamedDeclPrinterTest.cpp
index 2fdda1929b2a3..599ccd7f06fc6 100644
--- a/clang/unittests/AST/NamedDeclPrinterTest.cpp
+++ b/clang/unittests/AST/NamedDeclPrinterTest.cpp
@@ -283,7 +283,8 @@ TEST(NamedDeclPrinter, NestedNameSpecifierAnonymousTags) {
   const char *Code =
       R"(
         struct Foo {
-          struct {
+          class {
+            public:
             struct {
               void method();
             } i;
@@ -291,5 +292,5 @@ TEST(NamedDeclPrinter, NestedNameSpecifierAnonymousTags) {
         };
 )";
   ASSERT_TRUE(PrintedNestedNameSpecifierMatches(
-      Code, "method", "Foo::(anonymous)::(unnamed)::"));
+      Code, "method", "Foo::(anonymous class)::(unnamed struct)::"));
 }

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

Reply via email to