[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