Skip to content

Commit 34c5c51

Browse files
committed
[clang][TypePrinter] Make printing of anonymous/unnamed tag types consistent
In llvm#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`?
1 parent 4a0d485 commit 34c5c51

File tree

7 files changed

+133
-55
lines changed

7 files changed

+133
-55
lines changed

clang/include/clang/AST/TypeBase.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7374,6 +7374,10 @@ bool isSubstitutedDefaultArgument(ASTContext &Ctx, TemplateArgument Arg,
73747374
ArrayRef<TemplateArgument> Args,
73757375
unsigned Depth);
73767376

7377+
void printAnonymousTagDecl(llvm::raw_ostream &OS, const TagDecl *D,
7378+
const PrintingPolicy &Policy,
7379+
bool PrintKindDecoration, bool PrintTagLocations);
7380+
73777381
/// Represents a qualified type name for which the type name is
73787382
/// dependent.
73797383
///

clang/lib/AST/Decl.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "clang/AST/Stmt.h"
3636
#include "clang/AST/TemplateBase.h"
3737
#include "clang/AST/Type.h"
38+
#include "clang/AST/TypeBase.h"
3839
#include "clang/AST/TypeLoc.h"
3940
#include "clang/Basic/Builtins.h"
4041
#include "clang/Basic/IdentifierTable.h"
@@ -1793,7 +1794,9 @@ void NamedDecl::printNestedNameSpecifier(raw_ostream &OS,
17931794
if (TypedefNameDecl *TD = RD->getTypedefNameForAnonDecl())
17941795
OS << *TD;
17951796
else if (!RD->getIdentifier())
1796-
OS << "(anonymous " << RD->getKindName() << ')';
1797+
printAnonymousTagDecl(OS, llvm::cast<TagDecl>(RD), P,
1798+
/*PrintKindDecoration=*/true,
1799+
/*AllowSourceLocations=*/false);
17971800
else
17981801
OS << *RD;
17991802
} else if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {

clang/lib/AST/TypePrinter.cpp

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "clang/AST/TemplateBase.h"
2424
#include "clang/AST/TemplateName.h"
2525
#include "clang/AST/Type.h"
26+
#include "clang/AST/TypeBase.h"
2627
#include "clang/Basic/AddressSpaces.h"
2728
#include "clang/Basic/AttrKinds.h"
2829
#include "clang/Basic/ExceptionSpecificationType.h"
@@ -1518,11 +1519,11 @@ void TypePrinter::printTagType(const TagType *T, raw_ostream &OS) {
15181519
return;
15191520
}
15201521

1521-
bool HasKindDecoration = false;
1522+
bool PrintKindDecoration = Policy.AnonymousTagLocations;
15221523

15231524
if (T->isCanonicalUnqualified()) {
15241525
if (!Policy.SuppressTagKeyword && !D->getTypedefNameForAnonDecl()) {
1525-
HasKindDecoration = true;
1526+
PrintKindDecoration = false;
15261527
OS << D->getKindName();
15271528
OS << ' ';
15281529
}
@@ -1546,51 +1547,10 @@ void TypePrinter::printTagType(const TagType *T, raw_ostream &OS) {
15461547
else if (TypedefNameDecl *Typedef = D->getTypedefNameForAnonDecl()) {
15471548
assert(Typedef->getIdentifier() && "Typedef without identifier?");
15481549
OS << Typedef->getIdentifier()->getName();
1549-
} else {
1550-
// Make an unambiguous representation for anonymous types, e.g.
1551-
// (anonymous enum at /usr/include/string.h:120:9)
1552-
OS << (Policy.MSVCFormatting ? '`' : '(');
1553-
1554-
if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
1555-
OS << "lambda";
1556-
HasKindDecoration = true;
1557-
} else if ((isa<RecordDecl>(D) && cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
1558-
OS << "anonymous";
1559-
} else {
1560-
OS << "unnamed";
1561-
}
1562-
1563-
if (Policy.AnonymousTagLocations) {
1564-
// Suppress the redundant tag keyword if we just printed one.
1565-
// We don't have to worry about ElaboratedTypes here because you can't
1566-
// refer to an anonymous type with one.
1567-
if (!HasKindDecoration)
1568-
OS << " " << D->getKindName();
1569-
1570-
PresumedLoc PLoc = D->getASTContext().getSourceManager().getPresumedLoc(
1571-
D->getLocation());
1572-
if (PLoc.isValid()) {
1573-
OS << " at ";
1574-
StringRef File = PLoc.getFilename();
1575-
llvm::SmallString<1024> WrittenFile(File);
1576-
if (auto *Callbacks = Policy.Callbacks)
1577-
WrittenFile = Callbacks->remapPath(File);
1578-
// Fix inconsistent path separator created by
1579-
// clang::DirectoryLookup::LookupFile when the file path is relative
1580-
// path.
1581-
llvm::sys::path::Style Style =
1582-
llvm::sys::path::is_absolute(WrittenFile)
1583-
? llvm::sys::path::Style::native
1584-
: (Policy.MSVCFormatting
1585-
? llvm::sys::path::Style::windows_backslash
1586-
: llvm::sys::path::Style::posix);
1587-
llvm::sys::path::native(WrittenFile, Style);
1588-
OS << WrittenFile << ':' << PLoc.getLine() << ':' << PLoc.getColumn();
1589-
}
1590-
}
1591-
1592-
OS << (Policy.MSVCFormatting ? '\'' : ')');
1593-
}
1550+
} else
1551+
printAnonymousTagDecl(OS, D, Policy,
1552+
/*PrintKindDecoration=*/PrintKindDecoration,
1553+
/*PrintTagLocations=*/Policy.AnonymousTagLocations);
15941554

15951555
// If this is a class template specialization, print the template
15961556
// arguments.
@@ -2469,6 +2429,58 @@ static bool isSubstitutedTemplateArgument(ASTContext &Ctx, TemplateArgument Arg,
24692429
return false;
24702430
}
24712431

2432+
void clang::printAnonymousTagDecl(llvm::raw_ostream &OS, const TagDecl *D,
2433+
const PrintingPolicy &Policy,
2434+
bool PrintKindDecoration,
2435+
bool PrintTagLocations) {
2436+
assert(D);
2437+
2438+
// Make an unambiguous representation for anonymous types, e.g.
2439+
// (anonymous enum at /usr/include/string.h:120:9)
2440+
OS << (Policy.MSVCFormatting ? '`' : '(');
2441+
2442+
if (isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda()) {
2443+
PrintKindDecoration = false;
2444+
OS << "lambda";
2445+
} else if ((isa<RecordDecl>(D) &&
2446+
cast<RecordDecl>(D)->isAnonymousStructOrUnion())) {
2447+
OS << "anonymous";
2448+
} else {
2449+
OS << "unnamed";
2450+
}
2451+
2452+
// Suppress the redundant tag keyword if we just printed one.
2453+
// We don't have to worry about ElaboratedTypes here because you can't
2454+
// refer to an anonymous type with one.
2455+
if (PrintKindDecoration)
2456+
OS << " " << D->getKindName();
2457+
2458+
if (PrintTagLocations) {
2459+
PresumedLoc PLoc =
2460+
D->getASTContext().getSourceManager().getPresumedLoc(D->getLocation());
2461+
if (PLoc.isValid()) {
2462+
OS << " at ";
2463+
StringRef File = PLoc.getFilename();
2464+
llvm::SmallString<1024> WrittenFile(File);
2465+
if (auto *Callbacks = Policy.Callbacks)
2466+
WrittenFile = Callbacks->remapPath(File);
2467+
// Fix inconsistent path separator created by
2468+
// clang::DirectoryLookup::LookupFile when the file path is relative
2469+
// path.
2470+
llvm::sys::path::Style Style =
2471+
llvm::sys::path::is_absolute(WrittenFile)
2472+
? llvm::sys::path::Style::native
2473+
: (Policy.MSVCFormatting
2474+
? llvm::sys::path::Style::windows_backslash
2475+
: llvm::sys::path::Style::posix);
2476+
llvm::sys::path::native(WrittenFile, Style);
2477+
OS << WrittenFile << ':' << PLoc.getLine() << ':' << PLoc.getColumn();
2478+
}
2479+
}
2480+
2481+
OS << (Policy.MSVCFormatting ? '\'' : ')');
2482+
}
2483+
24722484
bool clang::isSubstitutedDefaultArgument(ASTContext &Ctx, TemplateArgument Arg,
24732485
const NamedDecl *Param,
24742486
ArrayRef<TemplateArgument> Args,

clang/lib/ASTMatchers/ASTMatchersInternal.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/AST/ExprConcepts.h"
1919
#include "clang/AST/ParentMapContext.h"
2020
#include "clang/AST/PrettyPrinter.h"
21+
#include "clang/AST/TypeBase.h"
2122
#include "clang/ASTMatchers/ASTMatchers.h"
2223
#include "clang/Basic/LLVM.h"
2324
#include "clang/Lex/Lexer.h"
@@ -514,7 +515,14 @@ static StringRef getNodeName(const RecordDecl &Node,
514515
return Node.getName();
515516
}
516517
Scratch.clear();
517-
return ("(anonymous " + Node.getKindName() + ")").toStringRef(Scratch);
518+
519+
llvm::raw_svector_ostream OS(Scratch);
520+
521+
printAnonymousTagDecl(
522+
OS, llvm::cast<TagDecl>(&Node), Node.getASTContext().getPrintingPolicy(),
523+
/*PrintKindDecoration=*/true, /*PrintTagLocations=*/false);
524+
525+
return OS.str();
518526
}
519527

520528
static StringRef getNodeName(const NamespaceDecl &Node,

clang/unittests/AST/NamedDeclPrinterTest.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,31 @@ TEST(NamedDeclPrinter, NestedNameSpecifierTemplateArgs) {
265265
ASSERT_TRUE(
266266
PrintedNestedNameSpecifierMatches(Code, "method", "vector<int>::"));
267267
}
268+
269+
TEST(NamedDeclPrinter, NestedNameSpecifierLambda) {
270+
const char *Code =
271+
R"(
272+
auto l = [] {
273+
struct Foo {
274+
void method();
275+
};
276+
};
277+
)";
278+
ASSERT_TRUE(PrintedNestedNameSpecifierMatches(
279+
Code, "method", "(lambda)::operator()()::Foo::"));
280+
}
281+
282+
TEST(NamedDeclPrinter, NestedNameSpecifierAnonymousTags) {
283+
const char *Code =
284+
R"(
285+
struct Foo {
286+
struct {
287+
struct {
288+
void method();
289+
} i;
290+
};
291+
};
292+
)";
293+
ASSERT_TRUE(PrintedNestedNameSpecifierMatches(
294+
Code, "method", "Foo::(anonymous)::(unnamed)::"));
295+
}

clang/unittests/AST/TypePrinterTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ TEST(TypePrinter, NestedNameSpecifiers) {
328328
// Further levels of nesting print the entire scope.
329329
ASSERT_TRUE(PrintedTypeMatches(
330330
Code, {}, fieldDecl(hasName("u"), hasType(qualType().bind("id"))),
331-
"union level1()::Inner::Inner(int)::(anonymous struct)::(unnamed)",
331+
"union level1()::Inner::Inner(int)::(unnamed struct)::(unnamed)",
332332
[](PrintingPolicy &Policy) {
333333
Policy.FullyQualifiedName = true;
334334
Policy.AnonymousTagLocations = false;

clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2709,25 +2709,48 @@ TEST_P(ASTMatchersTest, HasName_MatchesAnonymousNamespaces) {
27092709
EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
27102710
}
27112711

2712-
TEST_P(ASTMatchersTest, HasName_MatchesAnonymousOuterClasses) {
2712+
TEST_P(ASTMatchersTest, HasName_MatchesUnnamedOuterClasses) {
27132713
if (!GetParam().isCXX()) {
27142714
return;
27152715
}
27162716

27172717
EXPECT_TRUE(matches("class A { class { class C; } x; };",
2718-
recordDecl(hasName("A::(anonymous class)::C"))));
2718+
recordDecl(hasName("A::(unnamed class)::C"))));
27192719
EXPECT_TRUE(matches("class A { class { class C; } x; };",
2720-
recordDecl(hasName("::A::(anonymous class)::C"))));
2720+
recordDecl(hasName("::A::(unnamed class)::C"))));
27212721
EXPECT_FALSE(matches("class A { class { class C; } x; };",
27222722
recordDecl(hasName("::A::C"))));
27232723
EXPECT_TRUE(matches("class A { struct { class C; } x; };",
2724-
recordDecl(hasName("A::(anonymous struct)::C"))));
2724+
recordDecl(hasName("A::(unnamed struct)::C"))));
27252725
EXPECT_TRUE(matches("class A { struct { class C; } x; };",
2726-
recordDecl(hasName("::A::(anonymous struct)::C"))));
2726+
recordDecl(hasName("::A::(unnamed struct)::C"))));
27272727
EXPECT_FALSE(matches("class A { struct { class C; } x; };",
27282728
recordDecl(hasName("::A::C"))));
27292729
}
27302730

2731+
TEST_P(ASTMatchersTest, HasName_MatchesAnonymousOuterClasses) {
2732+
if (!GetParam().isCXX()) {
2733+
return;
2734+
}
2735+
2736+
EXPECT_TRUE(matches(
2737+
"class A { struct { struct { class C; } x; }; };",
2738+
recordDecl(hasName("A::(anonymous struct)::(unnamed struct)::C"))));
2739+
EXPECT_TRUE(matches(
2740+
"class A { struct { struct { class C; } x; }; };",
2741+
recordDecl(hasName("::A::(anonymous struct)::(unnamed struct)::C"))));
2742+
EXPECT_FALSE(matches("class A { struct { struct { class C; } x; }; };",
2743+
recordDecl(hasName("A::(unnamed struct)::C"))));
2744+
EXPECT_TRUE(matches(
2745+
"class A { class { public: struct { class C; } x; }; };",
2746+
recordDecl(hasName("A::(anonymous class)::(unnamed struct)::C"))));
2747+
EXPECT_TRUE(matches(
2748+
"class A { class { public: struct { class C; } x; }; };",
2749+
recordDecl(hasName("::A::(anonymous class)::(unnamed struct)::C"))));
2750+
EXPECT_FALSE(matches("class A { class { public: struct { class C; } x; }; };",
2751+
recordDecl(hasName("A::(unnamed struct)::C"))));
2752+
}
2753+
27312754
TEST_P(ASTMatchersTest, HasName_MatchesFunctionScope) {
27322755
if (!GetParam().isCXX()) {
27332756
return;

0 commit comments

Comments
 (0)