https://github.com/Michael137 created 
https://github.com/llvm/llvm-project/pull/168533

Alternative to https://github.com/llvm/llvm-project/pull/159592

>From f2469abd1634b498cb07710e70a83b9d8a72c385 Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Fri, 14 Nov 2025 11:17:33 +0000
Subject: [PATCH 1/4] [clang][TypePrinter] Remove AppendScope in favour of
 printNestedNameSpecifier

---
 clang/lib/AST/Decl.cpp                        |  3 +
 clang/lib/AST/TypePrinter.cpp                 | 67 ++-----------------
 clang/test/ASTMerge/struct/test.c             |  4 +-
 clang/test/CXX/drs/cwg6xx.cpp                 |  2 +-
 clang/test/Index/print-type.c                 |  2 +-
 .../Layout/ms-x86-alias-avoidance-padding.cpp |  4 +-
 clang/test/Modules/compare-record.c           |  3 +-
 clang/test/SemaObjCXX/arc-0x.mm               |  8 +--
 8 files changed, 21 insertions(+), 72 deletions(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 8579e51e45697..b771faa4581ec 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1742,6 +1742,9 @@ void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
   // Collect named contexts.
   DeclarationName NameInScope = getDeclName();
   for (; Ctx; Ctx = Ctx->getParent()) {
+    if (P.Callbacks && P.Callbacks->isScopeVisible(Ctx))
+      continue;
+
     // Suppress anonymous namespace if requested.
     if (P.SuppressUnwrittenScope && isa<NamespaceDecl>(Ctx) &&
         cast<NamespaceDecl>(Ctx)->isAnonymousNamespace())
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index c18b2eafc722c..d2881d5ac518a 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -131,8 +131,6 @@ class TypePrinter {
 
   void printBefore(QualType T, raw_ostream &OS);
   void printAfter(QualType T, raw_ostream &OS);
-  void AppendScope(DeclContext *DC, raw_ostream &OS,
-                   DeclarationName NameInScope);
   void printTagType(const TagType *T, raw_ostream &OS);
   void printFunctionAfter(const FunctionType::ExtInfo &Info, raw_ostream &OS);
 #define ABSTRACT_TYPE(CLASS, PARENT)
@@ -1226,7 +1224,7 @@ void TypePrinter::printTypeSpec(NamedDecl *D, raw_ostream 
&OS) {
   // In C, this will always be empty except when the type
   // being printed is anonymous within other Record.
   if (!Policy.SuppressScope)
-    AppendScope(D->getDeclContext(), OS, D->getDeclName());
+    D->printNestedNameSpecifier(OS, Policy);
 
   IdentifierInfo *II = D->getIdentifier();
   OS << II->getName();
@@ -1240,7 +1238,7 @@ void TypePrinter::printUnresolvedUsingBefore(const 
UnresolvedUsingType *T,
     OS << ' ';
   auto *D = T->getDecl();
   if (Policy.FullyQualifiedName || T->isCanonicalUnqualified()) {
-    AppendScope(D->getDeclContext(), OS, D->getDeclName());
+    D->printNestedNameSpecifier(OS, Policy);
   } else {
     T->getQualifier().print(OS, Policy);
   }
@@ -1257,7 +1255,7 @@ void TypePrinter::printUsingBefore(const UsingType *T, 
raw_ostream &OS) {
     OS << ' ';
   auto *D = T->getDecl();
   if (Policy.FullyQualifiedName) {
-    AppendScope(D->getDeclContext(), OS, D->getDeclName());
+    D->printNestedNameSpecifier(OS, Policy);
   } else {
     T->getQualifier().print(OS, Policy);
   }
@@ -1273,7 +1271,7 @@ void TypePrinter::printTypedefBefore(const TypedefType 
*T, raw_ostream &OS) {
     OS << ' ';
   auto *D = T->getDecl();
   if (Policy.FullyQualifiedName) {
-    AppendScope(D->getDeclContext(), OS, D->getDeclName());
+    D->printNestedNameSpecifier(OS, Policy);
   } else {
     T->getQualifier().print(OS, Policy);
   }
@@ -1511,59 +1509,6 @@ void TypePrinter::printPredefinedSugarBefore(const 
PredefinedSugarType *T,
 void TypePrinter::printPredefinedSugarAfter(const PredefinedSugarType *T,
                                             raw_ostream &OS) {}
 
-/// Appends the given scope to the end of a string.
-void TypePrinter::AppendScope(DeclContext *DC, raw_ostream &OS,
-                              DeclarationName NameInScope) {
-  if (DC->isTranslationUnit())
-    return;
-
-  // FIXME: Consider replacing this with NamedDecl::printNestedNameSpecifier,
-  // which can also print names for function and method scopes.
-  if (DC->isFunctionOrMethod())
-    return;
-
-  if (Policy.Callbacks && Policy.Callbacks->isScopeVisible(DC))
-    return;
-
-  if (const auto *NS = dyn_cast<NamespaceDecl>(DC)) {
-    if (Policy.SuppressUnwrittenScope && NS->isAnonymousNamespace())
-      return AppendScope(DC->getParent(), OS, NameInScope);
-
-    // Only suppress an inline namespace if the name has the same lookup
-    // results in the enclosing namespace.
-    if (Policy.SuppressInlineNamespace !=
-            PrintingPolicy::SuppressInlineNamespaceMode::None &&
-        NS->isInline() && NameInScope &&
-        NS->isRedundantInlineQualifierFor(NameInScope))
-      return AppendScope(DC->getParent(), OS, NameInScope);
-
-    AppendScope(DC->getParent(), OS, NS->getDeclName());
-    if (NS->getIdentifier())
-      OS << NS->getName() << "::";
-    else
-      OS << "(anonymous namespace)::";
-  } else if (const auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(DC)) 
{
-    AppendScope(DC->getParent(), OS, Spec->getDeclName());
-    IncludeStrongLifetimeRAII Strong(Policy);
-    OS << Spec->getIdentifier()->getName();
-    const TemplateArgumentList &TemplateArgs = Spec->getTemplateArgs();
-    printTemplateArgumentList(
-        OS, TemplateArgs.asArray(), Policy,
-        Spec->getSpecializedTemplate()->getTemplateParameters());
-    OS << "::";
-  } else if (const auto *Tag = dyn_cast<TagDecl>(DC)) {
-    AppendScope(DC->getParent(), OS, Tag->getDeclName());
-    if (TypedefNameDecl *Typedef = Tag->getTypedefNameForAnonDecl())
-      OS << Typedef->getIdentifier()->getName() << "::";
-    else if (Tag->getIdentifier())
-      OS << Tag->getIdentifier()->getName() << "::";
-    else
-      return;
-  } else {
-    AppendScope(DC->getParent(), OS, NameInScope);
-  }
-}
-
 void TypePrinter::printTagType(const TagType *T, raw_ostream &OS) {
   TagDecl *D = T->getDecl();
 
@@ -1593,7 +1538,7 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
     // Compute the full nested-name-specifier for this type.
     // In C, this will always be empty except when the type
     // being printed is anonymous within other Record.
-    AppendScope(D->getDeclContext(), OS, D->getDeclName());
+    D->printNestedNameSpecifier(OS, Policy);
   }
 
   if (const IdentifierInfo *II = D->getIdentifier())
@@ -1809,7 +1754,7 @@ void TypePrinter::printTemplateId(const 
TemplateSpecializationType *T,
   // FIXME: Null TD never exercised in test suite.
   if (FullyQualify && TD) {
     if (!Policy.SuppressScope)
-      AppendScope(TD->getDeclContext(), OS, TD->getDeclName());
+      TD->printNestedNameSpecifier(OS, Policy);
 
     OS << TD->getName();
   } else {
diff --git a/clang/test/ASTMerge/struct/test.c 
b/clang/test/ASTMerge/struct/test.c
index 10ea753b142bd..d11234472ef29 100644
--- a/clang/test/ASTMerge/struct/test.c
+++ b/clang/test/ASTMerge/struct/test.c
@@ -44,10 +44,10 @@
 // CHECK: struct2.c:72:7: note: field 'i' has type 'int' here
 // CHECK: struct2.c:76:5: warning: external variable 'x13' declared with 
incompatible types in different translation units ('S13' vs. 'S13')
 // CHECK: struct1.c:79:5: note: declared here with type 'S13'
-// CHECK: struct1.c:130:7: warning: type 'struct DeepUnnamedError::(unnamed at 
[[PATH_TO_INPUTS:.+]]struct1.c:130:7)' has incompatible definitions in 
different translation units
+// CHECK: struct1.c:130:7: warning: type 'struct DeepUnnamedError::(anonymous 
union)::(anonymous union)::(unnamed at [[PATH_TO_INPUTS:.+]]struct1.c:130:7)' 
has incompatible definitions in different translation units
 // CHECK: struct1.c:131:14: note: field 'i' has type 'long' here
 // CHECK: struct2.c:128:15: note: field 'i' has type 'float' here
-// CHECK: struct1.c:129:5: warning: type 'union DeepUnnamedError::(unnamed at 
[[PATH_TO_INPUTS]]struct1.c:129:5)' has incompatible definitions in different 
translation units
+// CHECK: struct1.c:129:5: warning: type 'union DeepUnnamedError::(anonymous 
union)::(unnamed at [[PATH_TO_INPUTS]]struct1.c:129:5)' has incompatible 
definitions in different translation units
 // CHECK: struct1.c:132:9: note: field 'S' has type 'struct (unnamed struct at 
[[PATH_TO_INPUTS]]struct1.c:130:7)' here
 // CHECK: struct2.c:129:9: note: field 'S' has type 'struct (unnamed struct at 
[[PATH_TO_INPUTS]]struct2.c:127:7)' here
 // CHECK: struct2.c:138:3: warning: external variable 'x16' declared with 
incompatible types in different translation units ('struct DeepUnnamedError' 
vs. 'struct DeepUnnamedError')
diff --git a/clang/test/CXX/drs/cwg6xx.cpp b/clang/test/CXX/drs/cwg6xx.cpp
index 11eb0bf3a27b2..8eac049211193 100644
--- a/clang/test/CXX/drs/cwg6xx.cpp
+++ b/clang/test/CXX/drs/cwg6xx.cpp
@@ -1241,7 +1241,7 @@ namespace cwg686 { // cwg686: 3.0
 #endif
     struct N {
       operator struct O{}(){};
-      // expected-error@-1 {{'N::O' cannot be defined in a type specifier}}
+      // expected-error@-1 {{'cwg686::f()::N::O' cannot be defined in a type 
specifier}}
     };
     try {}
     catch (struct P *) {}
diff --git a/clang/test/Index/print-type.c b/clang/test/Index/print-type.c
index d30f4bed246c5..9fb85f8da4e66 100644
--- a/clang/test/Index/print-type.c
+++ b/clang/test/Index/print-type.c
@@ -71,7 +71,7 @@ _Atomic(unsigned long) aul;
 // CHECK: TypeRef=struct Struct:16:8 [type=struct Struct] [typekind=Record] 
[isPOD=1]
 // CHECK: StructDecl=struct (unnamed at {{.*}}):18:1 (Definition) [type=struct 
(unnamed at {{.*}}print-type.c:18:1)] [typekind=Record] [isPOD=1] [nbFields=2] 
[isAnon=1] [isAnonRecDecl=0]
 // CHECK: StructDecl=struct (unnamed at {{.*}}):23:1 (Definition) [type=struct 
(unnamed at {{.*}}print-type.c:23:1)] [typekind=Record] [isPOD=1] [nbFields=1] 
[isAnon=1] [isAnonRecDecl=0]
-// CHECK: StructDecl=struct (anonymous at {{.*}}):24:3 (Definition) 
[type=struct (anonymous at {{.*}}print-type.c:24:3)] [typekind=Record] 
[isPOD=1] [nbFields=2] [isAnon=1] [isAnonRecDecl=1]
+// CHECK: StructDecl=struct (anonymous at {{.*}}):24:3 (Definition) 
[type=struct (anonymous struct)::(anonymous at {{.*}}print-type.c:24:3)] 
[typekind=Record] [isPOD=1] [nbFields=2] [isAnon=1] [isAnonRecDecl=1]
 // CHECK: FieldDecl=x:25:17 (Definition) [type=_Atomic(int)] [typekind=Atomic] 
[valuetype=int] [valuetypekind=Int] [isPOD=0] [isAnonRecDecl=0]
 // CHECK: FieldDecl=y:26:9 (Definition) [type=int] [typekind=Int] [isPOD=1] 
[isAnonRecDecl=0]
 // CHECK: StructDecl=struct (unnamed at {{.*}}):30:10 (Definition) 
[type=struct (unnamed at {{.*}}print-type.c:30:10)] [typekind=Record] [isPOD=1] 
[nbFields=2] [isAnon=1] [isAnonRecDecl=0]
diff --git a/clang/test/Layout/ms-x86-alias-avoidance-padding.cpp 
b/clang/test/Layout/ms-x86-alias-avoidance-padding.cpp
index 678537bb514f5..bc6a56ef37538 100644
--- a/clang/test/Layout/ms-x86-alias-avoidance-padding.cpp
+++ b/clang/test/Layout/ms-x86-alias-avoidance-padding.cpp
@@ -49,7 +49,7 @@ struct AT3 : AT2, AT1 {
 // CHECK-NEXT:    0 |   struct AT2 (base)
 // CHECK-NEXT:    0 |     struct AT0 t
 // CHECK-NEXT:    0 |       union AT0::(unnamed at {{.*}} x
-// CHECK-NEXT:    0 |         struct AT0::(unnamed at {{.*}} y
+// CHECK-NEXT:    0 |         struct AT0::(anonymous union)::(unnamed at 
{{.*}} y
 // CHECK-NEXT:    0 |           int a
 // CHECK-NEXT:    4 |           struct AT t (empty)
 // CHECK:         0 |         int b
@@ -66,7 +66,7 @@ struct AT3 : AT2, AT1 {
 // CHECK-X64-NEXT:    0 |   struct AT2 (base)
 // CHECK-X64-NEXT:    0 |     struct AT0 t
 // CHECK-X64-NEXT:    0 |       union AT0::(unnamed at {{.*}} x
-// CHECK-X64-NEXT:    0 |         struct AT0::(unnamed at {{.*}} y
+// CHECK-X64-NEXT:    0 |         struct AT0::(anonymous union)::(unnamed at 
{{.*}} y
 // CHECK-X64-NEXT:    0 |           int a
 // CHECK-X64-NEXT:    4 |           struct AT t (empty)
 // CHECK-X64:         0 |         int b
diff --git a/clang/test/Modules/compare-record.c 
b/clang/test/Modules/compare-record.c
index ef4a3a5b0e90d..97caabd55fe33 100644
--- a/clang/test/Modules/compare-record.c
+++ b/clang/test/Modules/compare-record.c
@@ -496,6 +496,7 @@ struct CompareAnonymousNestedStruct 
compareAnonymousNestedStruct;
 // [email protected]:* {{declaration of 
'anonymousNestedStructField' does not match}}
 #elif defined(CASE3)
 struct CompareDeeplyNestedAnonymousUnionsAndStructs compareDeeplyNested;
-// [email protected]:* 
{{'CompareDeeplyNestedAnonymousUnionsAndStructs::(anonymous union)::(anonymous 
union)::(anonymous struct)::z' from module 'Second' is not present in 
definition of 'struct CompareDeeplyNestedAnonymousUnionsAndStructs::(anonymous 
at {{.*}})' in module 'First.Hidden'}}
+// [email protected]:* 
{{'CompareDeeplyNestedAnonymousUnionsAndStructs::(anonymous union)::(anonymous 
union)::(anonymous struct)::z' from module 'Second' is not present in 
definition of 'struct CompareDeeplyNestedAnonymousUnionsAndStructs::(anonymous 
union)::(anonymous union)::(anonymous at {{.*}})' in module 'First.Hidden'}}
+
 // [email protected]:* {{declaration of 'z' does not match}}
 #endif
diff --git a/clang/test/SemaObjCXX/arc-0x.mm b/clang/test/SemaObjCXX/arc-0x.mm
index bcaa5da6b9283..a7418eceb244b 100644
--- a/clang/test/SemaObjCXX/arc-0x.mm
+++ b/clang/test/SemaObjCXX/arc-0x.mm
@@ -162,7 +162,7 @@ void test() {
 
   struct S1 {
     union {
-      union { // expected-note-re {{copy constructor of 'S1' is implicitly 
deleted because field 'test_union::S1::(anonymous union at {{.*}})' has a 
deleted copy constructor}} expected-note-re {{copy assignment operator of 'S1' 
is implicitly deleted because field 'test_union::S1::(anonymous union at 
{{.*}})' has a deleted copy assignment operator}} expected-note-re 4 {{'S1' is 
implicitly deleted because field 'test_union::S1::(anonymous union at {{.*}})' 
has a deleted}}
+      union { // expected-note-re {{copy constructor of 'S1' is implicitly 
deleted because field 'test_union::S1::(anonymous union)::(anonymous union at 
{{.*}})' has a deleted copy constructor}} expected-note-re {{copy assignment 
operator of 'S1' is implicitly deleted because field 
'test_union::S1::(anonymous union)::(anonymous union at {{.*}})' has a deleted 
copy assignment operator}} expected-note-re 4 {{'S1' is implicitly deleted 
because field 'test_union::S1::(anonymous union)::(anonymous union at {{.*}})' 
has a deleted}}
         id f0; // expected-note-re 2 {{{{.*}} of '(anonymous union at {{.*}}' 
is implicitly deleted because variant field 'f0' is an ObjC pointer}}
         char f1;
       };
@@ -173,7 +173,7 @@ void test() {
   struct S2 {
     union {
       // FIXME: the note should say 'f0' is causing the special functions to 
be deleted.
-      struct { // expected-note-re 6 {{'S2' is implicitly deleted because 
variant field 'test_union::S2::(anonymous struct at {{.*}})' has a non-trivial}}
+      struct { // expected-note-re 6 {{'S2' is implicitly deleted because 
variant field 'test_union::S2::(anonymous union)::(anonymous struct at {{.*}})' 
has a non-trivial}}
         id f0;
         int f1;
       };
@@ -195,8 +195,8 @@ void test() {
   };
 
   static union { // expected-error {{call to implicitly-deleted default 
constructor of}}
-    union { // expected-note-re {{default constructor of '(unnamed union at 
{{.*}}' is implicitly deleted because field 'test_union::(anonymous union at 
{{.*}})' has a deleted default constructor}}
-      union { // expected-note-re {{default constructor of '(anonymous union 
at {{.*}}' is implicitly deleted because field 'test_union::(anonymous union at 
{{.*}})' has a deleted default constructor}}
+    union { // expected-note-re {{default constructor of '(unnamed union at 
{{.*}}' is implicitly deleted because field 'test_union::(anonymous 
union)::(anonymous union at {{.*}})' has a deleted default constructor}}
+      union { // expected-note-re {{default constructor of '(anonymous union 
at {{.*}}' is implicitly deleted because field 'test_union::(anonymous 
union)::(anonymous union)::(anonymous union at {{.*}})' has a deleted default 
constructor}}
         __weak id g1; // expected-note-re {{default constructor of '(anonymous 
union at {{.*}}' is implicitly deleted because variant field 'g1' is an ObjC 
pointer}}
         int g2;
       };

>From 902cee5f4475f50533fecc2cfa813db061a3fb06 Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Fri, 14 Nov 2025 16:28:19 +0000
Subject: [PATCH 2/4] [clang][TypePrinter] Add CanonicalAnonymousLambdaName

---
 clang/include/clang/AST/Decl.h          |  5 +++--
 clang/include/clang/AST/PrettyPrinter.h |  8 +++++++-
 clang/lib/AST/Decl.cpp                  |  8 +++++---
 clang/lib/AST/TypePrinter.cpp           | 23 +++++++++++++++++------
 4 files changed, 32 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 406d79ebd6641..995a2a0720870 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -357,9 +357,10 @@ class NamedDecl : public Decl {
   /// including the '::' at the end. E.g.
   ///    when `printQualifiedName(D)` prints "A::B::i",
   ///    this function prints "A::B::".
-  void printNestedNameSpecifier(raw_ostream &OS) const;
   void printNestedNameSpecifier(raw_ostream &OS,
-                                const PrintingPolicy &Policy) const;
+                                bool AllowFunctionContext = false) const;
+  void printNestedNameSpecifier(raw_ostream &OS, const PrintingPolicy &Policy,
+                                bool AllowFunctionContext = false) const;
 
   // FIXME: Remove string version.
   std::string getQualifiedNameAsString() const;
diff --git a/clang/include/clang/AST/PrettyPrinter.h 
b/clang/include/clang/AST/PrettyPrinter.h
index fd995a653d167..9bb6a7f203cf5 100644
--- a/clang/include/clang/AST/PrettyPrinter.h
+++ b/clang/include/clang/AST/PrettyPrinter.h
@@ -79,7 +79,8 @@ struct PrintingPolicy {
         PrintAsCanonical(false), PrintInjectedClassNameWithArguments(true),
         UsePreferredNames(true), AlwaysIncludeTypeForTemplateArgument(false),
         CleanUglifiedParameters(false), EntireContentsOfLargeArray(true),
-        UseEnumerators(true), UseHLSLTypes(LO.HLSL) {}
+        UseEnumerators(true), UseHLSLTypes(LO.HLSL),
+        CanonicalAnonymousLambdaName(false) {}
 
   /// Adjust this printing policy for cases where it's known that we're
   /// printing C++ code (for instance, if AST dumping reaches a C++-only
@@ -346,6 +347,11 @@ struct PrintingPolicy {
   LLVM_PREFERRED_TYPE(bool)
   unsigned UseHLSLTypes : 1;
 
+  // TODO: this shouldn't be specific to unnamed lambdas, but also unnamed
+  // structures/unions/enums (?)
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned CanonicalAnonymousLambdaName : 1;
+
   /// Callbacks to use to allow the behavior of printing to be customized.
   const PrintingCallbacks *Callbacks = nullptr;
 };
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b771faa4581ec..0effc7d90fb11 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1711,12 +1711,14 @@ void NamedDecl::printQualifiedName(raw_ostream &OS,
   }
 }
 
-void NamedDecl::printNestedNameSpecifier(raw_ostream &OS) const {
+void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
+                                         bool AllowFunctionContext) const {
   printNestedNameSpecifier(OS, getASTContext().getPrintingPolicy());
 }
 
 void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
-                                         const PrintingPolicy &P) const {
+                                         const PrintingPolicy &P,
+                                         bool AllowFunctionContext) const {
   const DeclContext *Ctx = getDeclContext();
 
   // For ObjC methods and properties, look through categories and use the
@@ -1733,7 +1735,7 @@ void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
       Ctx = CI;
   }
 
-  if (Ctx->isFunctionOrMethod())
+  if (Ctx->isFunctionOrMethod() && !AllowFunctionContext)
     return;
 
   using ContextsTy = SmallVector<const DeclContext *, 8>;
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..ed50fe71e0537 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1532,16 +1532,23 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
       OS << ' ';
   }
 
+  const IdentifierInfo *II = D->getIdentifier();
+  const bool IsLambda =
+      isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda();
+  const bool PrintingCanonicalLambdaName =
+      !II && IsLambda && Policy.CanonicalAnonymousLambdaName;
+
   if (!Policy.FullyQualifiedName && !T->isCanonicalUnqualified()) {
     T->getQualifier().print(OS, Policy);
   } else if (!Policy.SuppressScope) {
     // Compute the full nested-name-specifier for this type.
     // In C, this will always be empty except when the type
     // being printed is anonymous within other Record.
-    D->printNestedNameSpecifier(OS, Policy);
+    D->printNestedNameSpecifier(
+        OS, Policy, /*AllowFunctionContext=*/PrintingCanonicalLambdaName);
   }
 
-  if (const IdentifierInfo *II = D->getIdentifier())
+  if (II)
     OS << II->getName();
   else if (TypedefNameDecl *Typedef = D->getTypedefNameForAnonDecl()) {
     assert(Typedef->getIdentifier() && "Typedef without identifier?");
@@ -1549,12 +1556,15 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
   } else {
     // Make an unambiguous representation for anonymous types, e.g.
     //   (anonymous enum at /usr/include/string.h:120:9)
-    OS << (Policy.MSVCFormatting ? '`' : '(');
+    const bool AddParen = !PrintingCanonicalLambdaName || 
Policy.AnonymousTagLocations;
+    if (AddParen)
+      OS << (Policy.MSVCFormatting ? '`' : '(');
 
-    if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
+    if (IsLambda) {
       OS << "lambda";
       HasKindDecoration = true;
-    } else if ((isa<RecordDecl>(D) && 
cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
+    } else if ((isa<RecordDecl>(D) &&
+                cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
       OS << "anonymous";
     } else {
       OS << "unnamed";
@@ -1589,7 +1599,8 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
       }
     }
 
-    OS << (Policy.MSVCFormatting ? '\'' : ')');
+    if (AddParen)
+      OS << (Policy.MSVCFormatting ? '\'' : ')');
   }
 
   // If this is a class template specialization, print the template

>From 83db21dbb644d4d6bc5028e4b462e1e540676b91 Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Fri, 14 Nov 2025 16:28:39 +0000
Subject: [PATCH 3/4] [clang][DebugInfo] Canonical lambda names in debug-info

---
 clang/lib/AST/TypePrinter.cpp                         |  7 +++++--
 clang/lib/CodeGen/CGDebugInfo.cpp                     |  1 +
 clang/test/DebugInfo/CXX/prefix-map-lambda.cpp        | 10 ----------
 clang/test/DebugInfo/CXX/simple-template-names.cpp    |  4 ++--
 .../DebugInfo/Generic/Inputs/debug-info-slash.cpp     |  2 --
 .../test/DebugInfo/Generic/Inputs/debug-info-slash.h  |  6 ------
 clang/test/DebugInfo/Generic/debug-prefix-map.cpp     | 11 -----------
 clang/test/DebugInfo/Generic/slash.test               | 10 ----------
 8 files changed, 8 insertions(+), 43 deletions(-)
 delete mode 100644 clang/test/DebugInfo/CXX/prefix-map-lambda.cpp
 delete mode 100644 clang/test/DebugInfo/Generic/Inputs/debug-info-slash.cpp
 delete mode 100644 clang/test/DebugInfo/Generic/Inputs/debug-info-slash.h
 delete mode 100644 clang/test/DebugInfo/Generic/debug-prefix-map.cpp
 delete mode 100644 clang/test/DebugInfo/Generic/slash.test

diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ed50fe71e0537..ec65a7d6f438d 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1556,12 +1556,15 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
   } else {
     // Make an unambiguous representation for anonymous types, e.g.
     //   (anonymous enum at /usr/include/string.h:120:9)
-    const bool AddParen = !PrintingCanonicalLambdaName || 
Policy.AnonymousTagLocations;
+    const bool AddParen = !PrintingCanonicalLambdaName;
     if (AddParen)
       OS << (Policy.MSVCFormatting ? '`' : '(');
 
     if (IsLambda) {
       OS << "lambda";
+      if (PrintingCanonicalLambdaName)
+        OS << D->getASTContext().getManglingNumber(D);
+
       HasKindDecoration = true;
     } else if ((isa<RecordDecl>(D) &&
                 cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
@@ -1570,7 +1573,7 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
       OS << "unnamed";
     }
 
-    if (Policy.AnonymousTagLocations) {
+    if (Policy.AnonymousTagLocations && !PrintingCanonicalLambdaName) {
       // 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.
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp 
b/clang/lib/CodeGen/CGDebugInfo.cpp
index bda7b7487f59b..054b274b5a44a 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -422,6 +422,7 @@ PrintingPolicy CGDebugInfo::getPrintingPolicy() const {
   PP.UsePreferredNames = false;
   PP.AlwaysIncludeTypeForTemplateArgument = true;
   PP.UseEnumerators = false;
+  PP.CanonicalAnonymousLambdaName = true;
 
   // Apply -fdebug-prefix-map.
   PP.Callbacks = &PrintCB;
diff --git a/clang/test/DebugInfo/CXX/prefix-map-lambda.cpp 
b/clang/test/DebugInfo/CXX/prefix-map-lambda.cpp
deleted file mode 100644
index f0fb1a312c8be..0000000000000
--- a/clang/test/DebugInfo/CXX/prefix-map-lambda.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
-// RUN:   -fdebug-prefix-map=%S=/SOURCE_ROOT %s -emit-llvm -o - | FileCheck %s
-
-template <typename T> void b(T) {}
-void c() {
-  // CHECK: !DISubprogram(name: "b<(lambda at
-  // CHECK-SAME:          SOURCE_ROOT
-  // CHECK-SAME:          [[@LINE+1]]:{{[0-9]+}})>"
-  b([]{});
-}
diff --git a/clang/test/DebugInfo/CXX/simple-template-names.cpp 
b/clang/test/DebugInfo/CXX/simple-template-names.cpp
index 5a5d706e81972..8b02303e76355 100644
--- a/clang/test/DebugInfo/CXX/simple-template-names.cpp
+++ b/clang/test/DebugInfo/CXX/simple-template-names.cpp
@@ -70,9 +70,9 @@ void f() {
   // anything other than another unnamed class/struct.
   auto Lambda = [] {};
   f1<decltype(Lambda)>();
-  // CHECK: !DISubprogram(name: "f1<(lambda at 
{{.*}}simple-template-names.cpp:[[# @LINE - 2]]:17)>",
+  // CHECK: !DISubprogram(name: "f1<f()::lambda1>",
   f1<t1<t1<decltype(Lambda)>>>();
-  // CHECK: !DISubprogram(name: "f1<t1<t1<(lambda at {{.*}}> > >",
+  // CHECK: !DISubprogram(name: "f1<t1<t1<f()::lambda1> > >",
   struct {
   } unnamed_struct;
   f1<decltype(unnamed_struct)>();
diff --git a/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.cpp 
b/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.cpp
deleted file mode 100644
index 563077ed342a1..0000000000000
--- a/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Inputs/debug-info-slash.h"
-int main() { a(); return 0; }
diff --git a/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.h 
b/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.h
deleted file mode 100644
index 9092f4a5e8170..0000000000000
--- a/clang/test/DebugInfo/Generic/Inputs/debug-info-slash.h
+++ /dev/null
@@ -1,6 +0,0 @@
-template <typename... T>
-void f1() {}
-void a() {
-  auto Lambda = [] {};
-  f1<decltype(Lambda)>();
-}
diff --git a/clang/test/DebugInfo/Generic/debug-prefix-map.cpp 
b/clang/test/DebugInfo/Generic/debug-prefix-map.cpp
deleted file mode 100644
index 174bef5a07699..0000000000000
--- a/clang/test/DebugInfo/Generic/debug-prefix-map.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-// RUN: %clang_cc1 -debug-info-kind=standalone 
-fdebug-prefix-map=%p=./UNLIKELY_PATH/empty %s -emit-llvm -o - | FileCheck %s
-
-struct alignas(64) an {
-  struct {
-    unsigned char x{0};
-  } arr[64];
-};
-
-struct an *pan = new an;
-
-// CHECK: !DISubprogram(name: "(unnamed struct at 
./UNLIKELY_PATH/empty{{/|\\\\}}{{.*}}",
diff --git a/clang/test/DebugInfo/Generic/slash.test 
b/clang/test/DebugInfo/Generic/slash.test
deleted file mode 100644
index 0e42912c18d21..0000000000000
--- a/clang/test/DebugInfo/Generic/slash.test
+++ /dev/null
@@ -1,10 +0,0 @@
-RUN: rm -rf %t-dir
-RUN: mkdir -p %t-dir/header/Inputs
-RUN: cp %S/Inputs/debug-info-slash.cpp %t-dir/
-RUN: cp %S/Inputs/debug-info-slash.h %t-dir/header/Inputs
-RUN: cd %t-dir
-RUN: %clang -target x86_64-pc-win32 -emit-llvm -S -g  
%t-dir/debug-info-slash.cpp -Iheader -o - | FileCheck --check-prefix=WIN %s
-RUN: %clang -target x86_64-linux-gnu -emit-llvm -S -g  
%t-dir/debug-info-slash.cpp -Iheader -o - | FileCheck --check-prefix=LINUX %s
-
-WIN:   lambda at header\\Inputs\\debug-info-slash.h
-LINUX: lambda at header/Inputs/debug-info-slash.h

>From 4c075905f5d67d49419c442cf193a41d266373d4 Mon Sep 17 00:00:00 2001
From: Michael Buch <[email protected]>
Date: Fri, 14 Nov 2025 17:28:48 +0000
Subject: [PATCH 4/4] [clang][DebugInfo] Treat unnamed records the same as
 lambdas

---
 clang/include/clang/AST/PrettyPrinter.h       |  6 ++--
 clang/lib/AST/TypePrinter.cpp                 | 35 ++++++++-----------
 clang/lib/CodeGen/CGDebugInfo.cpp             |  2 +-
 .../DebugInfo/CXX/simple-template-names.cpp   | 12 +++----
 4 files changed, 24 insertions(+), 31 deletions(-)

diff --git a/clang/include/clang/AST/PrettyPrinter.h 
b/clang/include/clang/AST/PrettyPrinter.h
index 9bb6a7f203cf5..4c5a34b90fc72 100644
--- a/clang/include/clang/AST/PrettyPrinter.h
+++ b/clang/include/clang/AST/PrettyPrinter.h
@@ -80,7 +80,7 @@ struct PrintingPolicy {
         UsePreferredNames(true), AlwaysIncludeTypeForTemplateArgument(false),
         CleanUglifiedParameters(false), EntireContentsOfLargeArray(true),
         UseEnumerators(true), UseHLSLTypes(LO.HLSL),
-        CanonicalAnonymousLambdaName(false) {}
+        CanonicalAnonymousEntities(false) {}
 
   /// Adjust this printing policy for cases where it's known that we're
   /// printing C++ code (for instance, if AST dumping reaches a C++-only
@@ -347,10 +347,8 @@ struct PrintingPolicy {
   LLVM_PREFERRED_TYPE(bool)
   unsigned UseHLSLTypes : 1;
 
-  // TODO: this shouldn't be specific to unnamed lambdas, but also unnamed
-  // structures/unions/enums (?)
   LLVM_PREFERRED_TYPE(bool)
-  unsigned CanonicalAnonymousLambdaName : 1;
+  unsigned CanonicalAnonymousEntities : 1;
 
   /// Callbacks to use to allow the behavior of printing to be customized.
   const PrintingCallbacks *Callbacks = nullptr;
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ec65a7d6f438d..eeb565b921fad 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1533,10 +1533,8 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
   }
 
   const IdentifierInfo *II = D->getIdentifier();
-  const bool IsLambda =
-      isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda();
-  const bool PrintingCanonicalLambdaName =
-      !II && IsLambda && Policy.CanonicalAnonymousLambdaName;
+  const bool PrintingCanonicalAnonName =
+      !II && Policy.CanonicalAnonymousEntities;
 
   if (!Policy.FullyQualifiedName && !T->isCanonicalUnqualified()) {
     T->getQualifier().print(OS, Policy);
@@ -1545,7 +1543,7 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
     // In C, this will always be empty except when the type
     // being printed is anonymous within other Record.
     D->printNestedNameSpecifier(
-        OS, Policy, /*AllowFunctionContext=*/PrintingCanonicalLambdaName);
+        OS, Policy, /*AllowFunctionContext=*/PrintingCanonicalAnonName);
   }
 
   if (II)
@@ -1556,15 +1554,10 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
   } else {
     // Make an unambiguous representation for anonymous types, e.g.
     //   (anonymous enum at /usr/include/string.h:120:9)
-    const bool AddParen = !PrintingCanonicalLambdaName;
-    if (AddParen)
-      OS << (Policy.MSVCFormatting ? '`' : '(');
+    OS << (Policy.MSVCFormatting ? '`' : '(');
 
-    if (IsLambda) {
+    if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
       OS << "lambda";
-      if (PrintingCanonicalLambdaName)
-        OS << D->getASTContext().getManglingNumber(D);
-
       HasKindDecoration = true;
     } else if ((isa<RecordDecl>(D) &&
                 cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
@@ -1573,13 +1566,16 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
       OS << "unnamed";
     }
 
-    if (Policy.AnonymousTagLocations && !PrintingCanonicalLambdaName) {
-      // 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();
+    if (PrintingCanonicalAnonName)
+      OS << D->getASTContext().getManglingNumber(D);
+
+    // 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();
 
+    if (Policy.AnonymousTagLocations && !PrintingCanonicalAnonName) {
       PresumedLoc PLoc = D->getASTContext().getSourceManager().getPresumedLoc(
           D->getLocation());
       if (PLoc.isValid()) {
@@ -1602,8 +1598,7 @@ void TypePrinter::printTagType(const TagType *T, 
raw_ostream &OS) {
       }
     }
 
-    if (AddParen)
-      OS << (Policy.MSVCFormatting ? '\'' : ')');
+    OS << (Policy.MSVCFormatting ? '\'' : ')');
   }
 
   // If this is a class template specialization, print the template
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp 
b/clang/lib/CodeGen/CGDebugInfo.cpp
index 054b274b5a44a..ca63215f4b73a 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -422,7 +422,7 @@ PrintingPolicy CGDebugInfo::getPrintingPolicy() const {
   PP.UsePreferredNames = false;
   PP.AlwaysIncludeTypeForTemplateArgument = true;
   PP.UseEnumerators = false;
-  PP.CanonicalAnonymousLambdaName = true;
+  PP.CanonicalAnonymousEntities = true;
 
   // Apply -fdebug-prefix-map.
   PP.Callbacks = &PrintCB;
diff --git a/clang/test/DebugInfo/CXX/simple-template-names.cpp 
b/clang/test/DebugInfo/CXX/simple-template-names.cpp
index 8b02303e76355..2b6e9fd74130e 100644
--- a/clang/test/DebugInfo/CXX/simple-template-names.cpp
+++ b/clang/test/DebugInfo/CXX/simple-template-names.cpp
@@ -70,18 +70,18 @@ void f() {
   // anything other than another unnamed class/struct.
   auto Lambda = [] {};
   f1<decltype(Lambda)>();
-  // CHECK: !DISubprogram(name: "f1<f()::lambda1>",
+  // CHECK: !DISubprogram(name: "f1<f()::(lambda1)>",
   f1<t1<t1<decltype(Lambda)>>>();
-  // CHECK: !DISubprogram(name: "f1<t1<t1<f()::lambda1> > >",
+  // CHECK: !DISubprogram(name: "f1<t1<t1<f()::(lambda1)> > >",
   struct {
   } unnamed_struct;
   f1<decltype(unnamed_struct)>();
-  // CHECK: !DISubprogram(name: "f1<(unnamed struct at 
{{.*}}simple-template-names.cpp:[[# @LINE - 3]]:3)>",
+  // CHECK: !DISubprogram(name: "f1<f()::(unnamed1 struct)>",
   f1<void (decltype(unnamed_struct))>();
-  // CHECK: !DISubprogram(name: "f1<void ((unnamed struct at 
{{.*}}simple-template-names.cpp:[[# @LINE - 5]]:3))>",
+  // CHECK: !DISubprogram(name: "f1<void (f()::(unnamed1 struct))>",
   enum {} unnamed_enum;
   f1<decltype(unnamed_enum)>();
-  // CHECK: !DISubprogram(name: "f1<(unnamed enum at 
{{.*}}simple-template-names.cpp:[[# @LINE - 2]]:3)>",
+  // CHECK: !DISubprogram(name: "f1<f()::(unnamed1 enum)>",
 
   // Declarations can't readily be reversed as the value in the DWARF only
   // contains the address of the value - we'd have to do symbol lookup to find
@@ -128,5 +128,5 @@ void f() {
   // CHECK: !DISubprogram(name: "f1<int () __attribute__((noreturn))>",
   
   f4<UnnamedEnum1>();
-  // CHECK: !DISubprogram(name: "f4<((unnamed enum at {{.*}}))0>"
+  // CHECK: !DISubprogram(name: "f4<((unnamed1 enum))0>"
 }

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

Reply via email to