[clang] [ASTMatchers] Extend hasName matcher when matching templates (PR #100349)

Nathan James via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 26 23:11:19 PDT 2024


https://github.com/njames93 updated https://github.com/llvm/llvm-project/pull/100349

>From a81c1b2ee0bd0d0622abf1d5ec06846a2e2e3a27 Mon Sep 17 00:00:00 2001
From: Nathan James <n.james93 at hotmail.co.uk>
Date: Wed, 24 Jul 2024 12:43:39 +0100
Subject: [PATCH] [ASMMatchers] Extend hasName matcher when matching templates

Allow users to match all record instantiations by using <> as a wildcard
---
 clang/include/clang/ASTMatchers/ASTMatchers.h | 15 +++++++
 clang/lib/ASTMatchers/ASTMatchersInternal.cpp | 41 +++++++++++++++++--
 .../ASTMatchers/ASTMatchersNarrowingTest.cpp  | 35 ++++++++++++++++
 3 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index ca44c3ee08565..4ef64f1215426 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -3076,6 +3076,21 @@ inline internal::BindableMatcher<Stmt> sizeOfExpr(
 /// \code
 ///   namespace a { namespace b { class X; } }
 /// \endcode
+///
+/// Qualified names in templated classes can be matched explicitly or implicity
+/// by specifying the template type or using `<*>` to match any template.
+///
+/// Example matches:
+///   - callExpr(callee(functionDecl(hasName("Foo<int>::Bar"))))
+///   - callExpr(callee(functionDecl(hasName("Foo<>::Bar"))))
+/// \code
+///   template<typename T> class Foo{
+///     static void Bar();
+///   };
+///   void Func() {
+///     Foo<int>::Bar();
+///   }
+/// \endcode
 inline internal::Matcher<NamedDecl> hasName(StringRef Name) {
   return internal::Matcher<NamedDecl>(
       new internal::HasNameMatcher({std::string(Name)}));
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index bf87b1aa0992a..5316a947d3bf5 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -638,6 +638,39 @@ bool HasNameMatcher::matchesNodeFullFast(const NamedDecl &Node) const {
   return Patterns.foundMatch(/*AllowFullyQualified=*/true);
 }
 
+static std::optional<StringRef> consumePatternBack(StringRef Pattern,
+                                                   StringRef Target) {
+  while (!Pattern.empty()) {
+    auto Index = Pattern.rfind("<*>");
+    if (Index == StringRef::npos) {
+      if (Target.consume_back(Pattern))
+        return Target;
+      return {};
+    }
+    auto Suffix = Pattern.substr(Index + 2);
+    if (!Target.consume_back(Suffix))
+      return {};
+    auto BracketCount = 1;
+    while (BracketCount) {
+      if (Target.empty())
+        return {};
+      switch (Target.back()) {
+      case '<':
+        --BracketCount;
+        break;
+      case '>':
+        ++BracketCount;
+        break;
+      default:
+        break;
+      }
+      Target = Target.drop_back();
+    }
+    Pattern = Pattern.take_front(Index);
+  }
+  return Target;
+}
+
 bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
   const bool SkipUnwrittenCases[] = {false, true};
   for (bool SkipUnwritten : SkipUnwrittenCases) {
@@ -653,10 +686,12 @@ bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
 
     for (const StringRef Pattern : Names) {
       if (Pattern.starts_with("::")) {
-        if (FullName == Pattern)
+        if (auto Result = consumePatternBack(Pattern, FullName);
+            Result && Result->empty()) {
           return true;
-      } else if (FullName.ends_with(Pattern) &&
-                 FullName.drop_back(Pattern.size()).ends_with("::")) {
+        }
+      } else if (auto Result = consumePatternBack(Pattern, FullName);
+                 Result && Result->ends_with("::")) {
         return true;
       }
     }
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index f26140675fd46..0cda846d51f7b 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -2664,6 +2664,41 @@ TEST_P(ASTMatchersTest, HasName_QualifiedStringMatchesThroughLinkage) {
   EXPECT_TRUE(notMatches(code, functionDecl(hasName("::test"))));
 }
 
+TEST_P(ASTMatchersTest, HasName_TemplateStrip) {
+  if (!GetParam().isCXX()) {
+    return;
+  }
+
+  StringRef Code = "template<typename T> struct Foo{void Bar() const;}; void "
+                   "Func() { Foo<int> Item; Item.Bar(); }";
+
+  EXPECT_TRUE(matches(Code, callExpr(callee(cxxMethodDecl(hasName("Bar"))))));
+  EXPECT_TRUE(
+      matches(Code, callExpr(callee(cxxMethodDecl(hasName("Foo<int>::Bar"))))));
+  EXPECT_TRUE(matches(
+      Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<int>::Bar"))))));
+  EXPECT_TRUE(
+      matches(Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<*>::Bar"))))));
+  EXPECT_TRUE(notMatches(
+      Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<::Bar"))))));
+  EXPECT_TRUE(notMatches(
+      Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<long>::Bar"))))));
+
+  if (GetParam().isCXX11OrLater()) {
+    Code = "template<typename T> struct Foo{void Bar() const;}; void Func() { "
+           "Foo<Foo<int>> Item; Item.Bar(); }";
+    EXPECT_TRUE(matches(Code, callExpr(callee(cxxMethodDecl(hasName("Bar"))))));
+    EXPECT_TRUE(
+        matches(Code, callExpr(callee(cxxMethodDecl(hasName("Foo<*>::Bar"))))));
+    EXPECT_TRUE(matches(
+        Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<*>::Bar"))))));
+    EXPECT_TRUE(matches(
+        Code, callExpr(callee(cxxMethodDecl(hasName("Foo<Foo<*>>::Bar"))))));
+    EXPECT_TRUE(matches(
+        Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<Foo<*>>::Bar"))))));
+  }
+}
+
 TEST_P(ASTMatchersTest, HasAnyName) {
   if (!GetParam().isCXX()) {
     // FIXME: Add a test for `hasAnyName()` that does not depend on C++.



More information about the cfe-commits mailing list