[clang] [WIP] [clang][DebugInfo] Use mangling number and scope to create unambiguous names for anonymous structures (PR #168533)
Michael Buch via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 18 05:30:33 PST 2025
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 <michaelbuch12 at gmail.com>
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 at -1 {{'N::O' cannot be defined in a type specifier}}
+ // expected-error at -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;
// expected-note at first-anonymous.h:* {{declaration of 'anonymousNestedStructField' does not match}}
#elif defined(CASE3)
struct CompareDeeplyNestedAnonymousUnionsAndStructs compareDeeplyNested;
-// expected-error-re at second-anonymous.h:* {{'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'}}
+// expected-error-re at second-anonymous.h:* {{'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'}}
+
// expected-note at first-anonymous.h:* {{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 <michaelbuch12 at gmail.com>
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 <michaelbuch12 at gmail.com>
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 <michaelbuch12 at gmail.com>
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>"
}
More information about the cfe-commits
mailing list