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

Nathan James via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 24 04:42:16 PDT 2024


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

Allow users to match all record instantiations by using <> as a wildcard.

With
```cpp
template <typename T>
struct Foo {
  void Bar();
};
```

The following code:
```cpp
Foo<int>{}.Bar();
```
Will match against:
```
callExpr(callee(cxxMethodDecl(hasName("Bar"))))
callExpr(callee(cxxMethodDecl(hasName("Foo<int>::Bar"))))
callExpr(callee(cxxMethodDecl(hasName("Foo<>::Bar"))))
```

>From ee05cd6a775d1dce12036757d1f2c90c7baa65fc Mon Sep 17 00:00:00 2001
From: Nathan James <n.james93 at hotmail.co.uk>
Date: Wed, 24 Jul 2024 12:38:37 +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 | 16 ++++++++++
 clang/lib/ASTMatchers/ASTMatchersInternal.cpp | 30 +++++++++++++++++--
 .../ASTMatchers/ASTMatchersNarrowingTest.cpp  | 24 +++++++++++++++
 3 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index ca44c3ee08565..4fa04f3af5909 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -3076,6 +3076,22 @@ 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 empty angle brackets 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..6d1d2507de4b7 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -638,6 +638,30 @@ 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 + 1);
+    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 +677,10 @@ 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..03e5fb3562dca 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -2664,6 +2664,30 @@ 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