[clang] [AST] Add dump() method to TypeLoc (PR #65484)

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 6 07:15:47 PDT 2023


https://github.com/sam-mccall created https://github.com/llvm/llvm-project/pull/65484:

The ability to dump AST nodes is important to ad-hoc debugging, and
the fact this doesn't work with TypeLoc nodes is an obvious missing
feature in e.g. clang-query (`set output dump` simply does nothing).

Having TypeLoc::dump(), and enabling DynTypedNode::dump() for such nodes
seems like a clear win.

It looks like this:
```
int main(int argc, char **argv);

FunctionProtoTypeLoc <test.cc:3:1, col:31> 'int (int, char **)' cdecl
|-ParmVarDecl 0x30071a8 <col:10, col:14> col:14 argc 'int'
| `-BuiltinTypeLoc <col:10> 'int'
|-ParmVarDecl 0x3007250 <col:20, col:27> col:27 argv 'char **'
| `-PointerTypeLoc <col:20, col:26> 'char **'
|   `-PointerTypeLoc <col:20, col:25> 'char *'
|     `-BuiltinTypeLoc <col:20> 'char'
`-BuiltinTypeLoc <col:1> 'int'
```

It dumps the lexically nested tree of type locs.
This often looks similar to how types are dumped, but unlike types
we don't look at desugaring e.g. typedefs, as their underlying types
are not lexically spelled here.

---

Less clear is exactly when to include these nodes in existing text AST
dumps rooted at (TranslationUnit)Decls.
These already omit supported nodes sometimes, e.g. NestedNameSpecifiers
are often mentioned but not recursively dumped.

TypeLocs are a more extreme case: they're ~always more verbose
than the current AST dump.
So this patch punts on that, TypeLocs are only ever printed recursively
as part of a TypeLoc::dump() call.

It would also be nice to be able to invoke `clang` to dump a typeloc
somehow, like `clang -cc1 -ast-dump`. But I don't know exactly what the
best verison of that is, so this patch doesn't do it.

---

There are similar (less critical!) nodes: TemplateArgumentLoc etc,
these also don't have dump() functions today and are obvious extensions.

I suspect that we should add these, and Loc nodes should dump each other
(e.g. the ElaboratedTypeLoc `vector<int>::iterator` should dump
the NestedNameSpecifierLoc `vector<int>::`, which dumps the
TemplateSpecializationTypeLoc `vector<int>::` etc).

Maybe this generalizes further to a "full syntactic dump" mode, where
even Decls and Stmts would print the TypeLocs they lexically contain.
But this may be more complex than useful.


>From f2269d93e313378581085bca418914229316bfc6 Mon Sep 17 00:00:00 2001
From: Sam McCall <sam.mccall at gmail.com>
Date: Mon, 4 Sep 2023 15:48:47 +0200
Subject: [PATCH] [AST] Add dump() method to TypeLoc

The ability to dump AST nodes is important to ad-hoc debugging, and
the fact this doesn't work with TypeLoc nodes is an obvious missing
feature in e.g. clang-query (`set output dump` simply does nothing).

Having TypeLoc::dump(), and enabling DynTypedNode::dump() for such nodes
seems like a clear win.

It looks like this:
```
int main(int argc, char **argv);

FunctionProtoTypeLoc <test.cc:3:1, col:31> 'int (int, char **)' cdecl
|-ParmVarDecl 0x30071a8 <col:10, col:14> col:14 argc 'int'
| `-BuiltinTypeLoc <col:10> 'int'
|-ParmVarDecl 0x3007250 <col:20, col:27> col:27 argv 'char **'
| `-PointerTypeLoc <col:20, col:26> 'char **'
|   `-PointerTypeLoc <col:20, col:25> 'char *'
|     `-BuiltinTypeLoc <col:20> 'char'
`-BuiltinTypeLoc <col:1> 'int'
```

It dumps the lexically nested tree of type locs.
This often looks similar to how types are dumped, but unlike types
we don't look at desugaring e.g. typedefs, as their underlying types
are not lexically spelled here.

---

Less clear is exactly when to include these nodes in existing text AST
dumps rooted at (TranslationUnit)Decls.
These already omit supported nodes sometimes, e.g. NestedNameSpecifiers
are often mentioned but not recursively dumped.

TypeLocs are a more extreme case: they're ~always more verbose
than the current AST dump.
So this patch punts on that, TypeLocs are only ever printed recursively
as part of a TypeLoc::dump() call.

It would also be nice to be able to invoke `clang` to dump a typeloc
somehow, like `clang -cc1 -ast-dump`. But I don't know exactly what the
best verison of that is, so this patch doesn't do it.

---

There are similar (less critical!) nodes: TemplateArgumentLoc etc,
these also don't have dump() functions today and are obvious extensions.

I suspect that we should add these, and Loc nodes should dump each other
(e.g. the ElaboratedTypeLoc `vector<int>::iterator` should dump
the NestedNameSpecifierLoc `vector<int>::`, which dumps the
TemplateSpecializationTypeLoc `vector<int>::` etc).

Maybe this generalizes further to a "full syntactic dump" mode, where
even Decls and Stmts would print the TypeLocs they lexically contain.
But this may be more complex than useful.
---
 clang/include/clang/AST/ASTNodeTraverser.h | 83 +++++++++++++++++++++-
 clang/include/clang/AST/JSONNodeDumper.h   |  2 +
 clang/include/clang/AST/TextNodeDumper.h   |  6 ++
 clang/include/clang/AST/TypeLoc.h          |  3 +
 clang/lib/AST/ASTDumper.cpp                | 13 ++++
 clang/lib/AST/ASTTypeTraits.cpp            |  2 +
 clang/lib/AST/JSONNodeDumper.cpp           | 31 ++++++++
 clang/lib/AST/TextNodeDumper.cpp           | 34 +++++++--
 clang/unittests/AST/CMakeLists.txt         |  1 +
 9 files changed, 168 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 1151a756ff377b6..a51b042d8f8bd5e 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<Derived, void,
                                            const comments::FullComment *>,
       public TypeVisitor<Derived>,
+      public TypeLocVisitor<Derived>,
       public ConstAttrVisitor<Derived>,
       public ConstTemplateArgumentVisitor<Derived> {
 
@@ -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<Derived>::Visit(D);
+      {
+        llvm::SaveAndRestore RestoreVisitLocs(this->VisitLocs, VisitLocs);
+        ConstDeclVisitor<Derived>::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<Derived>::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<Type>())
       Visit(T);
+    else if (const auto *TL = N.get<TypeLoc>())
+      Visit(*TL);
     else if (const auto *C = N.get<CXXCtorInitializer>())
       Visit(C);
     else if (const auto *C = N.get<OMPClause>())
@@ -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<Derived>::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<DependentSizedExtVectorType>(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,9 @@ class ASTNodeTraverser
     if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isCXXForRangeDecl())
       return;
 
+    if (VisitLocs)
+      if (const auto *TSI = D->getTypeSourceInfo())
+        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 4def5389137fa43..dde70dde2fa2be1 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 2f4ed082a0c7ad4..9f1a4e0b2c38e13 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<TextNodeDumper>,
       public ConstStmtVisitor<TextNodeDumper>,
       public TypeVisitor<TextNodeDumper>,
+      public TypeLocVisitor<TextNodeDumper>,
       public ConstDeclVisitor<TextNodeDumper> {
   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 98427a8dcbfe660..78f665da7897c8d 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 cc9a84eecaadba6..6efc5bb92e28d2b 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 4c7496c699beffd..99916f523aa95ef 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<ConceptReference>())
     C->dump(OS);
+  else if (const TypeLoc *TL = get<TypeLoc>())
+    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 e67c2c7e216dcea..ed7e79a4fce482d 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -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 5c8600035638b3b..714a05fee743973 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 << "<<<NULL>>>";
+    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<TextNodeDumper>::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<TextNodeDumper>::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/CMakeLists.txt b/clang/unittests/AST/CMakeLists.txt
index 12484be9206e23c..52ad440d9ad0e52 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



More information about the cfe-commits mailing list