diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -442,6 +442,28 @@ )cpp"; EXPECT_DECLS("ConceptSpecializationExpr", {"template concept Fooable = true;"}); + + // constrained-parameter + Code = R"cpp( + template + concept Fooable = true; + + template <[[Fooable]] T> + void bar(T t); + )cpp"; + EXPECT_DECLS("ConceptSpecializationExpr", + {"template concept Fooable = true;"}); + + // partial-concept-id + Code = R"cpp( + template + concept Fooable = true; + + template <[[Fooable]] T> + void bar(T t); + )cpp"; + EXPECT_DECLS("ConceptSpecializationExpr", + {"template concept Fooable = true;"}); } TEST_F(TargetDeclTest, FunctionTemplate) { diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -1777,8 +1777,17 @@ // D is the "T" in something like "template class vector;" if (D->getTypeForDecl()) TRY_TO(TraverseType(QualType(D->getTypeForDecl(), 0))); - if (const auto *TC = D->getTypeConstraint()) - TRY_TO(TraverseConceptReference(*TC)); + 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 (D->hasDefaultArgument() && !D->defaultArgumentWasInherited()) TRY_TO(TraverseTypeLoc(D->getDefaultArgumentInfo()->getTypeLoc())); }) diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/clang/unittests/Tooling/CMakeLists.txt @@ -22,6 +22,7 @@ RecursiveASTVisitorTests/Attr.cpp RecursiveASTVisitorTests/Callbacks.cpp RecursiveASTVisitorTests/Class.cpp + RecursiveASTVisitorTests/Concept.cpp RecursiveASTVisitorTests/ConstructExpr.cpp RecursiveASTVisitorTests/CXXBoolLiteralExpr.cpp RecursiveASTVisitorTests/CXXMemberCall.cpp diff --git a/clang/unittests/Tooling/RecursiveASTVisitorTests/Concept.cpp b/clang/unittests/Tooling/RecursiveASTVisitorTests/Concept.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Tooling/RecursiveASTVisitorTests/Concept.cpp @@ -0,0 +1,45 @@ +//===- unittest/Tooling/RecursiveASTVisitorTests/Concept.cpp----------------==// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" +#include "clang/AST/ExprConcepts.h" + +using namespace clang; + +namespace { + +struct ConceptVisitor : ExpectedLocationVisitor { + bool VisitConceptSpecializationExpr(ConceptSpecializationExpr *E) { + ++ConceptSpecializationExprsVisited; + return true; + } + bool TraverseConceptReference(const ConceptReference &R) { + ++ConceptReferencesTraversed; + return true; + } + + int ConceptSpecializationExprsVisited = 0; + int ConceptReferencesTraversed = 0; +}; + +TEST(RecursiveASTVisitor, ConstrainedParameter) { + ConceptVisitor Visitor; + EXPECT_TRUE(Visitor.runOver("template concept Fooable = true;\n" + "template void bar(T);", + ConceptVisitor::Lang_CXX2a)); + // Check that we visit 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); +} + +} // end anonymous namespace