diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h index 1151a756ff377..ed11b92b78791 100644 --- a/clang/include/clang/AST/ASTNodeTraverser.h +++ b/clang/include/clang/AST/ASTNodeTraverser.h @@ -23,7 +23,9 @@ #include "clang/AST/StmtVisitor.h" #include "clang/AST/TemplateArgumentVisitor.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLocVisitor.h" #include "clang/AST/TypeVisitor.h" +#include "llvm/Support/SaveAndRestore.h" namespace clang { @@ -48,6 +50,7 @@ struct { void Visit(const Stmt *Node); void Visit(const Type *T); void Visit(QualType T); + void Visit(TypeLoc); void Visit(const Decl *D); void Visit(const CXXCtorInitializer *Init); void Visit(const OMPClause *C); @@ -64,6 +67,7 @@ class ASTNodeTraverser public comments::ConstCommentVisitor, public TypeVisitor, + public TypeLocVisitor, public ConstAttrVisitor, public ConstTemplateArgumentVisitor { @@ -71,6 +75,14 @@ class ASTNodeTraverser /// not already been loaded. bool Deserialize = false; + /// Tracks whether we should dump TypeLocs etc. + /// + /// Detailed location information such as TypeLoc nodes is not usually + /// included in the dump (too verbose). + /// But when explicitly asked to dump a Loc node, we do so recursively, + /// including e.g. FunctionTypeLoc => ParmVarDecl => TypeLoc. + bool VisitLocs = false; + TraversalKind Traversal = TraversalKind::TK_AsIs; NodeDelegateType &getNodeDelegate() { @@ -85,7 +97,7 @@ class ASTNodeTraverser void SetTraversalKind(TraversalKind TK) { Traversal = TK; } TraversalKind GetTraversalKind() const { return Traversal; } - void Visit(const Decl *D) { + void Visit(const Decl *D, bool VisitLocs = false) { if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isImplicit()) return; @@ -94,7 +106,10 @@ class ASTNodeTraverser if (!D) return; - ConstDeclVisitor::Visit(D); + { + llvm::SaveAndRestore RestoreVisitLocs(this->VisitLocs, VisitLocs); + ConstDeclVisitor::Visit(D); + } for (const auto &A : D->attrs()) Visit(A); @@ -181,6 +196,17 @@ class ASTNodeTraverser }); } + void Visit(TypeLoc T) { + getNodeDelegate().AddChild([=] { + getNodeDelegate().Visit(T); + if (T.isNull()) + return; + TypeLocVisitor::Visit(T); + if (auto Inner = T.getNextTypeLoc()) + Visit(Inner); + }); + } + void Visit(const Attr *A) { getNodeDelegate().AddChild([=] { getNodeDelegate().Visit(A); @@ -286,6 +312,8 @@ class ASTNodeTraverser Visit(*QT); else if (const auto *T = N.get()) Visit(T); + else if (const auto *TL = N.get()) + Visit(*TL); else if (const auto *C = N.get()) Visit(C); else if (const auto *C = N.get()) @@ -346,7 +374,7 @@ class ASTNodeTraverser void VisitComplexType(const ComplexType *T) { Visit(T->getElementType()); } void VisitLocInfoType(const LocInfoType *T) { - Visit(T->getTypeSourceInfo()->getType()); + Visit(T->getTypeSourceInfo()->getTypeLoc()); } void VisitPointerType(const PointerType *T) { Visit(T->getPointeeType()); } void VisitBlockPointerType(const BlockPointerType *T) { @@ -415,9 +443,55 @@ class ASTNodeTraverser if (!T->isSugared()) Visit(T->getPattern()); } + void VisitAutoType(const AutoType *T) { + for (const auto &Arg : T->getTypeConstraintArguments()) + Visit(Arg); + } // FIXME: ElaboratedType, DependentNameType, // DependentTemplateSpecializationType, ObjCObjectType + // For TypeLocs, we automatically visit the inner type loc (pointee type etc). + // We must explicitly visit other lexically-nested nodes. + void VisitFunctionProtoTypeLoc(FunctionProtoTypeLoc TL) { + TypeLocVisitor::VisitFunctionTypeLoc(TL); + for (const auto *Param : TL.getParams()) + Visit(Param, /*VisitTypeLocs=*/true); + } + void VisitAutoTypeLoc(AutoTypeLoc TL) { + if (const auto *CR = TL.getConceptReference()) { + if (auto *Args = CR->getTemplateArgsAsWritten()) + for (const auto &Arg : Args->arguments()) + dumpTemplateArgumentLoc(Arg); + } + } + void VisitMemberPointerTypeLoc(MemberPointerTypeLoc TL) { + Visit(TL.getClassTInfo()->getTypeLoc()); + } + void VisitVariableArrayTypeLoc(VariableArrayTypeLoc TL) { + Visit(TL.getSizeExpr()); + } + void VisitDependentSizedArrayTypeLoc(DependentSizedArrayTypeLoc TL) { + Visit(TL.getSizeExpr()); + } + void VisitDependentSizedExtVectorTypeLoc(DependentSizedExtVectorTypeLoc TL) { + Visit(cast(TL.getType())->getSizeExpr()); + } + void VisitTypeOfExprTypeLoc(TypeOfExprTypeLoc TL) { + Visit(TL.getUnderlyingExpr()); + } + void VisitDecltypeType(DecltypeType TL) { + Visit(TL.getUnderlyingExpr()); + } + void VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TL) { + for (unsigned I=0, N=TL.getNumArgs(); I < N; ++I) + dumpTemplateArgumentLoc(TL.getArgLoc(I)); + } + void VisitDependentTemplateSpecializationTypeLoc( + DependentTemplateSpecializationTypeLoc TL) { + for (unsigned I=0, N=TL.getNumArgs(); I < N; ++I) + dumpTemplateArgumentLoc(TL.getArgLoc(I)); + } + void VisitTypedefDecl(const TypedefDecl *D) { Visit(D->getUnderlyingType()); } void VisitEnumConstantDecl(const EnumConstantDecl *D) { @@ -458,6 +532,8 @@ class ASTNodeTraverser if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isCXXForRangeDecl()) return; + if (const auto *TSI = D->getTypeSourceInfo(); VisitLocs && TSI) + Visit(TSI->getTypeLoc()); if (D->hasInit()) Visit(D->getInit()); } diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h index 4def5389137fa..dde70dde2fa2b 100644 --- a/clang/include/clang/AST/JSONNodeDumper.h +++ b/clang/include/clang/AST/JSONNodeDumper.h @@ -197,6 +197,7 @@ class JSONNodeDumper void Visit(const Type *T); void Visit(QualType T); void Visit(const Decl *D); + void Visit(TypeLoc TL); void Visit(const comments::Comment *C, const comments::FullComment *FC); void Visit(const TemplateArgument &TA, SourceRange R = {}, @@ -207,6 +208,7 @@ class JSONNodeDumper void Visit(const GenericSelectionExpr::ConstAssociation &A); void Visit(const concepts::Requirement *R); void Visit(const APValue &Value, QualType Ty); + void Visit(const ConceptReference *); void VisitAliasAttr(const AliasAttr *AA); void VisitCleanupAttr(const CleanupAttr *CA); diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h index 2f4ed082a0c7a..9f1a4e0b2c38e 100644 --- a/clang/include/clang/AST/TextNodeDumper.h +++ b/clang/include/clang/AST/TextNodeDumper.h @@ -24,6 +24,7 @@ #include "clang/AST/StmtVisitor.h" #include "clang/AST/TemplateArgumentVisitor.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLocVisitor.h" #include "clang/AST/TypeVisitor.h" namespace clang { @@ -132,6 +133,7 @@ class TextNodeDumper public ConstTemplateArgumentVisitor, public ConstStmtVisitor, public TypeVisitor, + public TypeLocVisitor, public ConstDeclVisitor { raw_ostream &OS; const bool ShowColors; @@ -179,6 +181,8 @@ class TextNodeDumper void Visit(QualType T); + void Visit(TypeLoc); + void Visit(const Decl *D); void Visit(const CXXCtorInitializer *Init); @@ -335,6 +339,8 @@ class TextNodeDumper void VisitObjCInterfaceType(const ObjCInterfaceType *T); void VisitPackExpansionType(const PackExpansionType *T); + void VisitTypeLoc(TypeLoc TL); + void VisitLabelDecl(const LabelDecl *D); void VisitTypedefDecl(const TypedefDecl *D); void VisitEnumDecl(const EnumDecl *D); diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h index 98427a8dcbfe6..78f665da7897c 100644 --- a/clang/include/clang/AST/TypeLoc.h +++ b/clang/include/clang/AST/TypeLoc.h @@ -229,6 +229,9 @@ class TypeLoc { /// __nullable, or __null_unspecifier), if there is one. SourceLocation findNullabilityLoc() const; + void dump() const; + void dump(llvm::raw_ostream &, const ASTContext &) const; + private: static bool isKind(const TypeLoc&) { return true; diff --git a/clang/lib/AST/ASTDumper.cpp b/clang/lib/AST/ASTDumper.cpp index cc9a84eecaadb..6efc5bb92e28d 100644 --- a/clang/lib/AST/ASTDumper.cpp +++ b/clang/lib/AST/ASTDumper.cpp @@ -200,6 +200,19 @@ LLVM_DUMP_METHOD void Type::dump(llvm::raw_ostream &OS, QualType(this, 0).dump(OS, Context); } +//===----------------------------------------------------------------------===// +// TypeLoc method implementations +//===----------------------------------------------------------------------===// + +LLVM_DUMP_METHOD void TypeLoc::dump() const { + ASTDumper(llvm::errs(), /*ShowColors=*/false).Visit(*this); +} + +LLVM_DUMP_METHOD void TypeLoc::dump(llvm::raw_ostream &OS, + const ASTContext &Context) const { + ASTDumper(OS, Context, Context.getDiagnostics().getShowColors()).Visit(*this); +} + //===----------------------------------------------------------------------===// // Decl method implementations //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ASTTypeTraits.cpp b/clang/lib/AST/ASTTypeTraits.cpp index 4c7496c699bef..99916f523aa95 100644 --- a/clang/lib/AST/ASTTypeTraits.cpp +++ b/clang/lib/AST/ASTTypeTraits.cpp @@ -228,6 +228,8 @@ void DynTypedNode::dump(llvm::raw_ostream &OS, T->dump(OS, Context); else if (const ConceptReference *C = get()) C->dump(OS); + else if (const TypeLoc *TL = get()) + TL->dump(OS, Context); else OS << "Unable to dump values of type " << NodeKind.asStringRef() << "\n"; } diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp index e67c2c7e216dc..095c80eaf1f2d 100644 --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -77,7 +77,7 @@ void JSONNodeDumper::Visit(const Type *T) { return; JOS.attribute("kind", (llvm::Twine(T->getTypeClassName()) + "Type").str()); - JOS.attribute("type", createQualType(QualType(T, 0), /*Desugar*/ false)); + JOS.attribute("type", createQualType(QualType(T, 0), /*Desugar=*/false)); attributeOnlyIfTrue("containsErrors", T->containsErrors()); attributeOnlyIfTrue("isDependent", T->isDependentType()); attributeOnlyIfTrue("isInstantiationDependent", @@ -96,6 +96,21 @@ void JSONNodeDumper::Visit(QualType T) { JOS.attribute("qualifiers", T.split().Quals.getAsString()); } +void JSONNodeDumper::Visit(TypeLoc TL) { + if (TL.isNull()) + return; + JOS.attribute("kind", + (llvm::Twine(TL.getTypeLocClass() == TypeLoc::Qualified + ? "Qualified" + : TL.getTypePtr()->getTypeClassName()) + + "TypeLoc") + .str()); + JOS.attribute("type", + createQualType(QualType(TL.getType()), /*Desugar=*/false)); + JOS.attributeObject("range", + [TL, this] { writeSourceRange(TL.getSourceRange()); }); +} + void JSONNodeDumper::Visit(const Decl *D) { JOS.attribute("id", createPointerRepresentation(D)); @@ -223,6 +238,22 @@ void JSONNodeDumper::Visit(const APValue &Value, QualType Ty) { JOS.attribute("value", OS.str()); } +void JSONNodeDumper::Visit(const ConceptReference *CR) { + JOS.attribute("kind", "ConceptReference"); + JOS.attribute("id", createPointerRepresentation(CR->getNamedConcept())); + if (const auto *Args = CR->getTemplateArgsAsWritten()) { + JOS.attributeArray("templateArgsAsWritten", [Args, this] { + for (const TemplateArgumentLoc &TAL : Args->arguments()) + JOS.object( + [&TAL, this] { Visit(TAL.getArgument(), TAL.getSourceRange()); }); + }); + } + JOS.attributeObject("loc", + [CR, this] { writeSourceLocation(CR->getLocation()); }); + JOS.attributeObject("range", + [CR, this] { writeSourceRange(CR->getSourceRange()); }); +} + void JSONNodeDumper::writeIncludeStack(PresumedLoc Loc, bool JustFirst) { if (Loc.isInvalid()) return; diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index 5c8600035638b..714a05fee7439 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -18,6 +18,7 @@ #include "clang/AST/LocInfoType.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLocVisitor.h" #include "clang/Basic/Module.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" @@ -240,6 +241,27 @@ void TextNodeDumper::Visit(QualType T) { OS << " " << T.split().Quals.getAsString(); } +void TextNodeDumper::Visit(TypeLoc TL) { + if (!TL) { + ColorScope Color(OS, ShowColors, NullColor); + OS << "<<>>"; + return; + } + + { + ColorScope Color(OS, ShowColors, TypeColor); + OS << (TL.getTypeLocClass() == TypeLoc::Qualified + ? "Qualified" + : TL.getType()->getTypeClassName()) + << "TypeLoc"; + } + dumpSourceRange(TL.getSourceRange()); + OS << ' '; + dumpBareType(TL.getType(), /*Desugar=*/false); + + TypeLocVisitor::Visit(TL); +} + void TextNodeDumper::Visit(const Decl *D) { if (!D) { ColorScope Color(OS, ShowColors, NullColor); @@ -1763,11 +1785,8 @@ void TextNodeDumper::VisitAutoType(const AutoType *T) { OS << " decltype(auto)"; if (!T->isDeduced()) OS << " undeduced"; - if (T->isConstrained()) { + if (T->isConstrained()) dumpDeclRef(T->getTypeConstraintConcept()); - for (const auto &Arg : T->getTypeConstraintArguments()) - VisitTemplateArgument(Arg); - } } void TextNodeDumper::VisitDeducedTemplateSpecializationType( @@ -1800,6 +1819,13 @@ void TextNodeDumper::VisitPackExpansionType(const PackExpansionType *T) { OS << " expansions " << *N; } +void TextNodeDumper::VisitTypeLoc(TypeLoc TL) { + // By default, add extra Type details with no extra loc info. + TypeVisitor::Visit(TL.getTypePtr()); +} +// FIXME: override behavior for TypeLocs that have interesting location +// information, such as the qualifier in ElaboratedTypeLoc. + void TextNodeDumper::VisitLabelDecl(const LabelDecl *D) { dumpName(D); } void TextNodeDumper::VisitTypedefDecl(const TypedefDecl *D) { diff --git a/clang/unittests/AST/ASTDumperTest.cpp b/clang/unittests/AST/ASTDumperTest.cpp new file mode 100644 index 0000000000000..f9ca059fc13c9 --- /dev/null +++ b/clang/unittests/AST/ASTDumperTest.cpp @@ -0,0 +1,146 @@ +//===- unittests/AST/ASTDumperTest.cpp --- Test of AST node dump() methods ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains tests for TypeLoc::dump() and related methods. +// Most of these are lit tests via clang -ast-dump. However some nodes are not +// included in dumps of (TranslationUnit)Decl, but still relevant when dumped +// directly. +// +//===----------------------------------------------------------------------===// + +#include "ASTPrint.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Testing/TestAST.h" +#include "llvm/ADT/StringRef.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace ast_matchers; + +namespace { +using testing::ElementsAre; +using testing::StartsWith; + +std::vector dumpTypeLoc(llvm::StringRef Name, ASTContext &Ctx) { + auto Lookup = Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get(Name)); + DeclaratorDecl *D = nullptr; + if ((D = Lookup.find_first())) + ; + else if (auto *TD = Lookup.find_first()) + D = TD->getTemplatedDecl(); + EXPECT_NE(D, nullptr) << Name; + if (!D) + return {}; + EXPECT_NE(D->getTypeSourceInfo(), nullptr); + if (!D->getTypeSourceInfo()) + return {}; + std::string S; + { + llvm::raw_string_ostream OS(S); + D->getTypeSourceInfo()->getTypeLoc().dump(OS, Ctx); + } + // Split result into lines. + std::vector Result; + auto Remaining = llvm::StringRef(S).trim("\n"); + while (!Remaining.empty()) { + auto [First, Rest] = Remaining.split('\n'); + Result.push_back(First.str()); + Remaining = Rest; + } + return Result; +} + +TEST(ASTDumper, TypeLocChain) { + TestAST AST(R"cc( + const int **x; + )cc"); + EXPECT_THAT( + dumpTypeLoc("x", AST.context()), + ElementsAre("" + "PointerTypeLoc 'const int **'", + "`-PointerTypeLoc 'const int *'", + " `-QualifiedTypeLoc 'const int'", + " `-BuiltinTypeLoc 'int'")); +} + +TEST(ASTDumper, AutoType) { + TestInputs Inputs(R"cc( + template concept C = true; + C auto str1 = "hello"; + auto str2 = "hello"; + )cc"); + Inputs.ExtraArgs.push_back("-std=c++20"); + TestAST AST(Inputs); + EXPECT_THAT( + dumpTypeLoc("str1", AST.context()), + ElementsAre("" + "AutoTypeLoc 'C auto' undeduced", + StartsWith("|-Concept"), // + "`-TemplateArgument type 'int'", + StartsWith(" `-BuiltinType"))); + EXPECT_THAT(dumpTypeLoc("str2", AST.context()), + ElementsAre("" + "AutoTypeLoc 'auto' undeduced")); +} + + +TEST(ASTDumper, FunctionTypeLoc) { + TestAST AST(R"cc( + void x(int, double *y); + + auto trailing() -> int; + + template int tmpl(T&&); + )cc"); + EXPECT_THAT( + dumpTypeLoc("x", AST.context()), + ElementsAre("" + "FunctionProtoTypeLoc 'void (int, " + "double *)' cdecl", + StartsWith("|-ParmVarDecl"), + "| `-BuiltinTypeLoc 'int'", + StartsWith("|-ParmVarDecl"), + "| `-PointerTypeLoc 'double *'", + "| `-BuiltinTypeLoc 'double'", + "`-BuiltinTypeLoc 'void'")); + + EXPECT_THAT(dumpTypeLoc("trailing", AST.context()), + ElementsAre("" + "FunctionProtoTypeLoc " + "'auto () -> int' trailing_return cdecl", + "`-BuiltinTypeLoc 'int'")); + + EXPECT_THAT( + dumpTypeLoc("tmpl", AST.context()), + ElementsAre("" + "FunctionProtoTypeLoc " + "'int (T &&)' cdecl", + StartsWith("|-ParmVarDecl"), + "| `-RValueReferenceTypeLoc 'T &&'", + "| `-TemplateTypeParmTypeLoc 'T' depth 0 index 0", + StartsWith("| `-TemplateTypeParm"), + "`-BuiltinTypeLoc 'int'")); + + // Dynamic-exception-spec needs C++14 or earlier. + TestInputs Throws(R"cc( + void throws() throw(int); + )cc"); + Throws.ExtraArgs.push_back("-std=c++14"); + AST = TestAST(Throws); + EXPECT_THAT(dumpTypeLoc("throws", AST.context()), + ElementsAre("" + "FunctionProtoTypeLoc " + "'void () throw(int)' exceptionspec_dynamic cdecl", + // FIXME: include TypeLoc for int + "|-Exceptions: 'int'", + "`-BuiltinTypeLoc 'void'")); +} + +} // namespace diff --git a/clang/unittests/AST/CMakeLists.txt b/clang/unittests/AST/CMakeLists.txt index 12484be9206e2..52ad440d9ad0e 100644 --- a/clang/unittests/AST/CMakeLists.txt +++ b/clang/unittests/AST/CMakeLists.txt @@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS add_clang_unittest(ASTTests ASTContextParentMapTest.cpp + ASTDumperTest.cpp ASTExprTest.cpp ASTImporterFixtures.cpp ASTImporterTest.cpp