diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -3176,6 +3176,26 @@ internal::TypeList> hasParent; +/// Matches AST nodes that have an ancestor, reachable only through +/// implicit-expression nodes, that matches the provided matcher. +/// +/// Given +/// \code +/// float f() { return 3; } +/// \endcode +/// \c integerLiteral(hasParentIgnoringImplicit(returnStmt())) matches "3", +/// while \c integerLiteral(hasParent(returnStmt())) does not. +/// +/// Usable as: Any Matcher +template +internal::PolymorphicMatcherWithParam1< + internal::HasParentIgnoringImplicitMatcher, MatcherT> +hasParentIgnoringImplicit(MatcherT InnerMatcher) { + return internal::PolymorphicMatcherWithParam1< + internal::HasParentIgnoringImplicitMatcher, MatcherT>( + std::move(InnerMatcher)); +} + /// Matches AST nodes that have an ancestor that matches the provided /// matcher. /// diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -985,7 +985,11 @@ AMM_All, /// Direct parent only. - AMM_ParentOnly + AMM_ParentOnly, + + /// Considers the first non-implicit `Expr` ancestor. Intuitively, like + /// `ignoringImplicit` for matching parents. + AMM_FirstExplicitOnly }; virtual ~ASTMatchFinder() = default; @@ -1515,6 +1519,26 @@ } }; +/// Matches nodes of type \c T that have a parent node for which the given inner +/// matcher matches. If the parent is an implicit expression, considers the +/// parent's parent (and so on) instead, until an explicit parent is found. That +/// is, looks for an ancestor that is separated from `Node` only by implicit +/// expression nodes and matches `ParentMatcher`. +template +class HasParentIgnoringImplicitMatcher : public MatcherInterface { + const DynTypedMatcher ParentMatcher; + +public: + explicit HasParentIgnoringImplicitMatcher(const ArgT &ParentMatcher) + : ParentMatcher(ParentMatcher) {} + + bool matches(const T &Node, ASTMatchFinder *Finder, + BoundNodesTreeBuilder *Builder) const override { + return Finder->matchesAncestorOf(Node, this->ParentMatcher, Builder, + ASTMatchFinder::AMM_FirstExplicitOnly); + } +}; + /// Matches nodes of type \c T that have at least one ancestor node of /// type \c AncestorT for which the given inner matcher matches. /// diff --git a/clang/lib/ASTMatchers/ASTMatchFinder.cpp b/clang/lib/ASTMatchers/ASTMatchFinder.cpp --- a/clang/lib/ASTMatchers/ASTMatchFinder.cpp +++ b/clang/lib/ASTMatchers/ASTMatchFinder.cpp @@ -544,9 +544,14 @@ // don't invalidate any iterators. if (ResultCache.size() > MaxMemoizationEntries) ResultCache.clear(); - if (MatchMode == AncestorMatchMode::AMM_ParentOnly) + switch (MatchMode) { + case AncestorMatchMode::AMM_ParentOnly: return matchesParentOf(Node, Matcher, Builder); - return matchesAnyAncestorOf(Node, Ctx, Matcher, Builder); + case AncestorMatchMode::AMM_FirstExplicitOnly: + return matchesFirstExplicitAncestorOf(Node, Matcher, Builder); + case AncestorMatchMode::AMM_All: + return matchesAnyAncestorOf(Node, Ctx, Matcher, Builder); + } } // Matches all registered matchers on the given node and calls the @@ -714,6 +719,33 @@ return false; } + // Returns whether the first explicit (`Expr`) ancestor of \p Node matches \p + // Matcher. That is, like matchesParentOf but skipping implicit parents. + // Unlike matchesAnyAncestorOf there's no memoization: it doesn't save much. + bool matchesFirstExplicitAncestorOf(const DynTypedNode &Node, + const DynTypedMatcher &Matcher, + BoundNodesTreeBuilder *Builder) { + for (const auto &Parent : ActiveASTContext->getParents(Node)) { + if (const auto *E = Parent.get()) + // If the parent is an implicit node, match on *its* parents + // instead. Use DFS, since we expect that expressions are relatively + // shallow. + if (clang::isa(E) || clang::isa(E) || + clang::isa(E) || + clang::isa(E)) { + if (matchesFirstExplicitAncestorOf(Parent, Matcher, Builder)) + return true; + continue; + } + BoundNodesTreeBuilder BuilderCopy = *Builder; + if (Matcher.matches(Parent, this, &BuilderCopy)) { + *Builder = std::move(BuilderCopy); + return true; + } + } + return false; + } + // Returns whether an ancestor of \p Node matches \p Matcher. // // The order of matching (which can lead to different nodes being bound in diff --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp --- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp @@ -3190,6 +3190,26 @@ compoundStmt(hasParent(recordDecl())))); } +TEST(HasParentIgnoringImplicit, MatchesExplicitParents) { + std::string Input = R"cc( + float f() { + int x = 3; + int y = 3.0; + return y; + } + )cc"; + EXPECT_TRUE( + matches(Input, declRefExpr(hasParentIgnoringImplicit(returnStmt())))); + EXPECT_TRUE( + matches(Input, floatLiteral(hasParentIgnoringImplicit(varDecl())))); + EXPECT_TRUE( + matches(Input, integerLiteral(hasParentIgnoringImplicit(varDecl())))); + + // Make sure it only ignores implicit ancestors. + EXPECT_TRUE( + notMatches(Input, integerLiteral(hasParentIgnoringImplicit(declStmt())))); +} + TEST(HasParent, NoDuplicateParents) { class HasDuplicateParents : public BoundNodesCallback { public: