[AST] Improve traversal of concepts and concept requirements
authorIlya Biryukov <ibiryukov@google.com>
Thu, 28 Apr 2022 08:39:47 +0000 (08:39 +0000)
committerIlya Biryukov <ibiryukov@google.com>
Thu, 28 Apr 2022 09:33:26 +0000 (09:33 +0000)
- Do not traverse concept decl inside `AutoType`. We only traverse
  declaration and definitions, not references to a declaration.
- Do not visit implicit AST node the relevant traversal mode.
- Add traversal extension points for concept requirements.
- Renamed `TraverseConceptReference` to mark as helper to share
  the code. Having an extension point there seems confusing given that
  there are many concept refences in the AST that do not call the
  helper. Those are `AutoType`, `AutoTypeLoc` and constraint requirements.

Only clangd code requires an update.

There are no use-cases for concept requirement traversals yet, but
I added them in the earlier version of the patch and decided to keep
them for completeness.

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D124532

clang-tools-extra/clangd/Selection.cpp
clang-tools-extra/clangd/unittests/XRefsTests.cpp
clang/include/clang/AST/RecursiveASTVisitor.h
clang/lib/Index/IndexBody.cpp
clang/unittests/Tooling/RecursiveASTVisitorTests/Concept.cpp

index fa3e6ff..e11b430 100644 (file)
@@ -10,6 +10,7 @@
 #include "AST.h"
 #include "support/Logger.h"
 #include "support/Trace.h"
+#include "clang/AST/ASTConcept.h"
 #include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
@@ -709,6 +710,15 @@ public:
   bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
     return traverseNode(E, [&] { return TraverseStmt(E->getSyntacticForm()); });
   }
+  bool TraverseTypeConstraint(const TypeConstraint *C) {
+    if (auto *E = C->getImmediatelyDeclaredConstraint()) {
+      // Technically this expression is 'implicit' and not traversed by the RAV.
+      // However, the range is correct, so we visit expression to avoid adding
+      // an extra kind to 'DynTypeNode' that hold 'TypeConstraint'.
+      return TraverseStmt(E);
+    }
+    return Base::TraverseTypeConstraint(C);
+  }
 
 private:
   using Base = RecursiveASTVisitor<SelectionVisitor>;
index 98693a5..9721f30 100644 (file)
@@ -2085,6 +2085,19 @@ TEST(FindReferences, ConceptsWithinAST) {
   checkFindRefs(Code);
 }
 
+TEST(FindReferences, ConceptReq) {
+  constexpr llvm::StringLiteral Code = R"cpp(
+    template <class T>
+    concept $def[[IsSmal^l]] = sizeof(T) <= 8;
+
+    template <class T>
+    concept IsSmallPtr = requires(T x) {
+      { *x } -> [[IsSmal^l]];
+    };
+  )cpp";
+  checkFindRefs(Code);
+}
+
 TEST(FindReferences, RequiresExprParameters) {
   constexpr llvm::StringLiteral Code = R"cpp(
     template <class T>
index f143df5..79e5294 100644 (file)
 #ifndef LLVM_CLANG_AST_RECURSIVEASTVISITOR_H
 #define LLVM_CLANG_AST_RECURSIVEASTVISITOR_H
 
+#include "clang/AST/ASTConcept.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/Decl.h"
-#include "clang/AST/DeclarationName.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclFriend.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclOpenMP.h"
 #include "clang/AST/DeclTemplate.h"
+#include "clang/AST/DeclarationName.h"
 #include "clang/AST/Expr.h"
-#include "clang/AST/ExprConcepts.h"
 #include "clang/AST/ExprCXX.h"
+#include "clang/AST/ExprConcepts.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/ExprOpenMP.h"
 #include "clang/AST/LambdaCapture.h"
@@ -319,11 +320,6 @@ public:
   bool TraverseSynOrSemInitListExpr(InitListExpr *S,
                                     DataRecursionQueue *Queue = nullptr);
 
-  /// Recursively visit a reference to a concept with potential arguments.
-  ///
-  /// \returns false if the visitation was terminated early, true otherwise.
-  bool TraverseConceptReference(const ConceptReference &C);
-
   /// Recursively visit an Objective-C protocol reference with location
   /// information.
   ///
@@ -475,11 +471,21 @@ public:
   DEF_TRAVERSE_TMPL_INST(Function)
 #undef DEF_TRAVERSE_TMPL_INST
 
+  bool TraverseTypeConstraint(const TypeConstraint *C);
+
+  bool TraverseConceptRequirement(concepts::Requirement *R);
+  bool TraverseConceptTypeRequirement(concepts::TypeRequirement *R);
+  bool TraverseConceptExprRequirement(concepts::ExprRequirement *R);
+  bool TraverseConceptNestedRequirement(concepts::NestedRequirement *R);
+
   bool dataTraverseNode(Stmt *S, DataRecursionQueue *Queue);
 
 private:
   // These are helper methods used by more than one Traverse* method.
   bool TraverseTemplateParameterListHelper(TemplateParameterList *TPL);
+  /// Traverses the qualifier, name and template arguments of a concept
+  /// reference.
+  bool TraverseConceptReferenceHelper(const ConceptReference &C);
 
   // Traverses template parameter lists of either a DeclaratorDecl or TagDecl.
   template <typename T>
@@ -512,6 +518,54 @@ private:
 };
 
 template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseTypeConstraint(
+    const TypeConstraint *C) {
+  if (!getDerived().shouldVisitImplicitCode()) {
+    TRY_TO(TraverseConceptReferenceHelper(*C));
+    return true;
+  }
+  if (Expr *IDC = C->getImmediatelyDeclaredConstraint()) {
+    TRY_TO(TraverseStmt(IDC));
+  } else {
+    // Avoid traversing the ConceptReference in the TypeConstraint
+    // if we have an immediately-declared-constraint, otherwise
+    // we'll end up visiting the concept and the arguments in
+    // the TC twice.
+    TRY_TO(TraverseConceptReferenceHelper(*C));
+  }
+  return true;
+}
+
+template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseConceptRequirement(
+    concepts::Requirement *R) {
+  switch (R->getKind()) {
+  case concepts::Requirement::RK_Type:
+    return getDerived().TraverseConceptTypeRequirement(
+        cast<concepts::TypeRequirement>(R));
+  case concepts::Requirement::RK_Simple:
+  case concepts::Requirement::RK_Compound:
+    return getDerived().TraverseConceptExprRequirement(
+        cast<concepts::ExprRequirement>(R));
+  case concepts::Requirement::RK_Nested:
+    return getDerived().TraverseConceptNestedRequirement(
+        cast<concepts::NestedRequirement>(R));
+  }
+}
+
+template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseConceptReferenceHelper(
+    const ConceptReference &C) {
+  TRY_TO(TraverseNestedNameSpecifierLoc(C.getNestedNameSpecifierLoc()));
+  TRY_TO(TraverseDeclarationNameInfo(C.getConceptNameInfo()));
+  if (C.hasExplicitTemplateArgs())
+    TRY_TO(TraverseTemplateArgumentLocsHelper(
+        C.getTemplateArgsAsWritten()->getTemplateArgs(),
+        C.getTemplateArgsAsWritten()->NumTemplateArgs));
+  return true;
+}
+
+template <typename Derived>
 bool RecursiveASTVisitor<Derived>::dataTraverseNode(Stmt *S,
                                                     DataRecursionQueue *Queue) {
   // Top switch stmt: dispatch to TraverseFooStmt for each concrete FooStmt.
@@ -531,6 +585,40 @@ bool RecursiveASTVisitor<Derived>::dataTraverseNode(Stmt *S,
 #undef DISPATCH_STMT
 
 template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseConceptTypeRequirement(
+    concepts::TypeRequirement *R) {
+  if (R->isSubstitutionFailure())
+    return true;
+  return getDerived().TraverseTypeLoc(R->getType()->getTypeLoc());
+}
+
+template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseConceptExprRequirement(
+    concepts::ExprRequirement *R) {
+  if (!R->isExprSubstitutionFailure())
+    TRY_TO(TraverseStmt(R->getExpr()));
+  auto &RetReq = R->getReturnTypeRequirement();
+  if (RetReq.isTypeConstraint()) {
+    if (getDerived().shouldVisitImplicitCode()) {
+      TRY_TO(TraverseTemplateParameterListHelper(
+          RetReq.getTypeConstraintTemplateParameterList()));
+    } else {
+      // Template parameter list is implicit, visit constraint directly.
+      TRY_TO(TraverseTypeConstraint(RetReq.getTypeConstraint()));
+    }
+  }
+  return true;
+}
+
+template <typename Derived>
+bool RecursiveASTVisitor<Derived>::TraverseConceptNestedRequirement(
+    concepts::NestedRequirement *R) {
+  if (!R->isSubstitutionFailure())
+    return getDerived().TraverseStmt(R->getConstraintExpr());
+  return true;
+}
+
+template <typename Derived>
 bool RecursiveASTVisitor<Derived>::PostVisitStmt(Stmt *S) {
   // In pre-order traversal mode, each Traverse##STMT method is responsible for
   // calling WalkUpFrom. Therefore, if the user overrides Traverse##STMT and
@@ -1007,7 +1095,6 @@ DEF_TRAVERSE_TYPE(UnaryTransformType, {
 DEF_TRAVERSE_TYPE(AutoType, {
   TRY_TO(TraverseType(T->getDeducedType()));
   if (T->isConstrained()) {
-    TRY_TO(TraverseDecl(T->getTypeConstraintConcept()));
     TRY_TO(TraverseTemplateArguments(T->getArgs(), T->getNumArgs()));
   }
 })
@@ -1838,17 +1925,8 @@ DEF_TRAVERSE_DECL(BuiltinTemplateDecl, {
 template <typename Derived>
 bool RecursiveASTVisitor<Derived>::TraverseTemplateTypeParamDeclConstraints(
     const TemplateTypeParmDecl *D) {
-  if (const auto *TC = D->getTypeConstraint()) {
-    if (Expr *IDC = TC->getImmediatelyDeclaredConstraint()) {
-      TRY_TO(TraverseStmt(IDC));
-    } else {
-      // Avoid traversing the ConceptReference in the TypeCosntraint
-      // if we have an immediately-declared-constraint, otherwise
-      // we'll end up visiting the concept and the arguments in
-      // the TC twice.
-      TRY_TO(TraverseConceptReference(*TC));
-    }
-  }
+  if (const auto *TC = D->getTypeConstraint())
+    TRY_TO(TraverseTypeConstraint(TC));
   return true;
 }
 
@@ -2435,18 +2513,6 @@ bool RecursiveASTVisitor<Derived>::TraverseSynOrSemInitListExpr(
   return true;
 }
 
-template<typename Derived>
-bool RecursiveASTVisitor<Derived>::TraverseConceptReference(
-    const ConceptReference &C) {
-  TRY_TO(TraverseNestedNameSpecifierLoc(C.getNestedNameSpecifierLoc()));
-  TRY_TO(TraverseDeclarationNameInfo(C.getConceptNameInfo()));
-  if (C.hasExplicitTemplateArgs())
-    TRY_TO(TraverseTemplateArgumentLocsHelper(
-        C.getTemplateArgsAsWritten()->getTemplateArgs(),
-        C.getTemplateArgsAsWritten()->NumTemplateArgs));
-  return true;
-}
-
 template <typename Derived>
 bool RecursiveASTVisitor<Derived>::TraverseObjCProtocolLoc(
     ObjCProtocolLoc ProtocolLoc) {
@@ -2825,31 +2891,15 @@ DEF_TRAVERSE_STMT(CoyieldExpr, {
   }
 })
 
-DEF_TRAVERSE_STMT(ConceptSpecializationExpr, {
-  TRY_TO(TraverseConceptReference(*S));
-})
+DEF_TRAVERSE_STMT(ConceptSpecializationExpr,
+                  { TRY_TO(TraverseConceptReferenceHelper(*S)); })
 
 DEF_TRAVERSE_STMT(RequiresExpr, {
   TRY_TO(TraverseDecl(S->getBody()));
   for (ParmVarDecl *Parm : S->getLocalParameters())
     TRY_TO(TraverseDecl(Parm));
   for (concepts::Requirement *Req : S->getRequirements())
-    if (auto *TypeReq = dyn_cast<concepts::TypeRequirement>(Req)) {
-      if (!TypeReq->isSubstitutionFailure())
-        TRY_TO(TraverseTypeLoc(TypeReq->getType()->getTypeLoc()));
-    } else if (auto *ExprReq = dyn_cast<concepts::ExprRequirement>(Req)) {
-      if (!ExprReq->isExprSubstitutionFailure())
-        TRY_TO(TraverseStmt(ExprReq->getExpr()));
-      auto &RetReq = ExprReq->getReturnTypeRequirement();
-      if (RetReq.isTypeConstraint()) {
-        TRY_TO(TraverseStmt(
-            RetReq.getTypeConstraint()->getImmediatelyDeclaredConstraint()));
-      }
-    } else {
-      auto *NestedReq = cast<concepts::NestedRequirement>(Req);
-      if (!NestedReq->isSubstitutionFailure())
-        TRY_TO(TraverseStmt(NestedReq->getConstraintExpr()));
-    }
+    TRY_TO(TraverseConceptRequirement(Req));
 })
 
 // These literals (all of them) do not need any action.
index ecf4b8b..eb8905a 100644 (file)
@@ -483,13 +483,10 @@ public:
     return true;
   }
 
-  bool VisitTemplateTypeParmDecl(TemplateTypeParmDecl *D) {
-    // This handles references in return type requirements of RequiresExpr.
-    // E.g. `requires (T x) { {*x} -> ConceptRef }`
-    if (auto *C = D->getTypeConstraint())
-      IndexCtx.handleReference(C->getNamedConcept(), C->getConceptNameLoc(),
-                               Parent, ParentDC);
-    return true;
+  bool TraverseTypeConstraint(const TypeConstraint *C) {
+    IndexCtx.handleReference(C->getNamedConcept(), C->getConceptNameLoc(),
+                             Parent, ParentDC);
+    return RecursiveASTVisitor::TraverseTypeConstraint(C);
   }
 };
 
index f0f7002..fc74e82 100644 (file)
@@ -7,7 +7,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "TestVisitor.h"
+#include "clang/AST/ASTConcept.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/AST/ExprConcepts.h"
+#include "clang/AST/Type.h"
 
 using namespace clang;
 
@@ -18,28 +21,96 @@ struct ConceptVisitor : ExpectedLocationVisitor<ConceptVisitor> {
     ++ConceptSpecializationExprsVisited;
     return true;
   }
-  bool TraverseConceptReference(const ConceptReference &R) {
-    ++ConceptReferencesTraversed;
-    return true;
+  bool TraverseTypeConstraint(const TypeConstraint *C) {
+    ++TypeConstraintsTraversed;
+    return ExpectedLocationVisitor::TraverseTypeConstraint(C);
+  }
+  bool TraverseConceptRequirement(concepts::Requirement *R) {
+    ++ConceptRequirementsTraversed;
+    return ExpectedLocationVisitor::TraverseConceptRequirement(R);
   }
 
+  bool shouldVisitImplicitCode() { return ShouldVisitImplicitCode; }
+
   int ConceptSpecializationExprsVisited = 0;
-  int ConceptReferencesTraversed = 0;
+  int TypeConstraintsTraversed = 0;
+  int ConceptRequirementsTraversed = 0;
+  bool ShouldVisitImplicitCode = false;
 };
 
-TEST(RecursiveASTVisitor, ConstrainedParameter) {
+TEST(RecursiveASTVisitor, Concepts) {
   ConceptVisitor Visitor;
+  Visitor.ShouldVisitImplicitCode = true;
   EXPECT_TRUE(Visitor.runOver("template <typename T> concept Fooable = true;\n"
                               "template <Fooable T> void bar(T);",
                               ConceptVisitor::Lang_CXX2a));
-  // Check that we visit the "Fooable T" template parameter's TypeConstraint's
-  // ImmediatelyDeclaredConstraint, which is a ConceptSpecializationExpr.
+  // Check that we traverse the "Fooable T" template parameter's
+  // TypeConstraint's ImmediatelyDeclaredConstraint, which is a
+  // ConceptSpecializationExpr.
   EXPECT_EQ(1, Visitor.ConceptSpecializationExprsVisited);
-  // There are two ConceptReference objects in the AST: the base subobject
-  // of the ConceptSpecializationExpr, and the base subobject of the
-  // TypeConstraint itself. To avoid traversing the concept and arguments
-  // multiple times, we only traverse one.
-  EXPECT_EQ(1, Visitor.ConceptReferencesTraversed);
+  // Also check we traversed the TypeConstraint that produced the expr.
+  EXPECT_EQ(1, Visitor.TypeConstraintsTraversed);
+
+  Visitor = {}; // Don't visit implicit code now.
+  EXPECT_TRUE(Visitor.runOver("template <typename T> concept Fooable = true;\n"
+                              "template <Fooable T> void bar(T);",
+                              ConceptVisitor::Lang_CXX2a));
+  // Check that we only visit the TypeConstraint, but not the implicitly
+  // generated immediately declared expression.
+  EXPECT_EQ(0, Visitor.ConceptSpecializationExprsVisited);
+  EXPECT_EQ(1, Visitor.TypeConstraintsTraversed);
+
+  Visitor = {};
+  EXPECT_TRUE(Visitor.runOver("template <class T> concept A = true;\n"
+                              "template <class T> struct vector {};\n"
+                              "template <class T> concept B = requires(T x) {\n"
+                              "  typename vector<T*>;\n"
+                              "  {x} -> A;\n"
+                              "  requires true;\n"
+                              "};",
+                              ConceptVisitor::Lang_CXX2a));
+  EXPECT_EQ(3, Visitor.ConceptRequirementsTraversed);
+}
+
+struct VisitDeclOnlyOnce : ExpectedLocationVisitor<VisitDeclOnlyOnce> {
+  bool VisitConceptDecl(ConceptDecl *D) {
+    ++ConceptDeclsVisited;
+    return true;
+  }
+
+  bool VisitAutoType(AutoType *) {
+    ++AutoTypeVisited;
+    return true;
+  }
+  bool VisitAutoTypeLoc(AutoTypeLoc) {
+    ++AutoTypeLocVisited;
+    return true;
+  }
+
+  bool TraverseVarDecl(VarDecl *V) {
+    // The base traversal visits only the `TypeLoc`.
+    // However, in the test we also validate the underlying `QualType`.
+    TraverseType(V->getType());
+    return ExpectedLocationVisitor::TraverseVarDecl(V);
+  }
+
+  bool shouldWalkTypesOfTypeLocs() { return false; }
+
+  int ConceptDeclsVisited = 0;
+  int AutoTypeVisited = 0;
+  int AutoTypeLocVisited = 0;
+};
+
+TEST(RecursiveASTVisitor, ConceptDeclInAutoType) {
+  // Check `AutoType` and `AutoTypeLoc` do not repeatedly traverse the
+  // underlying concept.
+  VisitDeclOnlyOnce Visitor;
+  Visitor.runOver("template <class T> concept A = true;\n"
+                  "A auto i = 0;\n",
+                  VisitDeclOnlyOnce::Lang_CXX2a);
+  EXPECT_EQ(1, Visitor.AutoTypeVisited);
+  EXPECT_EQ(1, Visitor.AutoTypeLocVisited);
+  EXPECT_EQ(1, Visitor.ConceptDeclsVisited);
 }
 
 } // end anonymous namespace