Skip to content

Commit d623ba5

Browse files
committed
[ASMMatchers] Extend hasName matcher when matching templates
Allow users to match all record instantiations by using <> as a wildcard
1 parent f18dd9e commit d623ba5

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

clang/include/clang/ASTMatchers/ASTMatchers.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,22 @@ inline internal::BindableMatcher<Stmt> sizeOfExpr(
30763076
/// \code
30773077
/// namespace a { namespace b { class X; } }
30783078
/// \endcode
3079+
///
3080+
/// Qualified names in templated classes can be matched explicitly or implicity
3081+
/// by specifying the template type or using empty angle brackets to match any
3082+
/// template.
3083+
///
3084+
/// Example matches:
3085+
/// - callExpr(callee(functionDecl(hasName("Foo<int>::Bar"))))
3086+
/// - callExpr(callee(functionDecl(hasName("Foo<>::Bar"))))
3087+
/// \code
3088+
/// template<typename T> class Foo{
3089+
/// static void Bar();
3090+
/// };
3091+
/// void Func() {
3092+
/// Foo<int>::Bar();
3093+
/// }
3094+
/// \endcode
30793095
inline internal::Matcher<NamedDecl> hasName(StringRef Name) {
30803096
return internal::Matcher<NamedDecl>(
30813097
new internal::HasNameMatcher({std::string(Name)}));

clang/lib/ASTMatchers/ASTMatchersInternal.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,39 @@ bool HasNameMatcher::matchesNodeFullFast(const NamedDecl &Node) const {
638638
return Patterns.foundMatch(/*AllowFullyQualified=*/true);
639639
}
640640

641+
static std::optional<StringRef> consumePatternBack(StringRef Pattern,
642+
StringRef Target) {
643+
while (!Pattern.empty()) {
644+
auto Index = Pattern.rfind("<>");
645+
if (Index == StringRef::npos) {
646+
if (Target.consume_back(Pattern))
647+
return Target;
648+
return {};
649+
}
650+
auto Suffix = Pattern.substr(Index + 1);
651+
if (!Target.consume_back(Suffix))
652+
return {};
653+
auto BracketCount = 1;
654+
while (BracketCount) {
655+
if (Target.empty())
656+
return {};
657+
switch (Target.back()) {
658+
case '<':
659+
--BracketCount;
660+
break;
661+
case '>':
662+
++BracketCount;
663+
break;
664+
default:
665+
break;
666+
}
667+
Target = Target.drop_back();
668+
}
669+
Pattern = Pattern.take_front(Index);
670+
}
671+
return Target;
672+
}
673+
641674
bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
642675
const bool SkipUnwrittenCases[] = {false, true};
643676
for (bool SkipUnwritten : SkipUnwrittenCases) {
@@ -653,10 +686,12 @@ bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
653686

654687
for (const StringRef Pattern : Names) {
655688
if (Pattern.starts_with("::")) {
656-
if (FullName == Pattern)
689+
if (auto Result = consumePatternBack(Pattern, FullName);
690+
Result && Result->empty()) {
657691
return true;
658-
} else if (FullName.ends_with(Pattern) &&
659-
FullName.drop_back(Pattern.size()).ends_with("::")) {
692+
}
693+
} else if (auto Result = consumePatternBack(Pattern, FullName);
694+
Result && Result->ends_with("::")) {
660695
return true;
661696
}
662697
}

clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,41 @@ TEST_P(ASTMatchersTest, HasName_QualifiedStringMatchesThroughLinkage) {
26642664
EXPECT_TRUE(notMatches(code, functionDecl(hasName("::test"))));
26652665
}
26662666

2667+
TEST_P(ASTMatchersTest, HasName_TemplateStrip) {
2668+
if (!GetParam().isCXX()) {
2669+
return;
2670+
}
2671+
2672+
StringRef Code = "template<typename T> struct Foo{void Bar() const;}; void "
2673+
"Func() { Foo<int> Item; Item.Bar(); }";
2674+
2675+
EXPECT_TRUE(matches(Code, callExpr(callee(cxxMethodDecl(hasName("Bar"))))));
2676+
EXPECT_TRUE(
2677+
matches(Code, callExpr(callee(cxxMethodDecl(hasName("Foo<int>::Bar"))))));
2678+
EXPECT_TRUE(matches(
2679+
Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<int>::Bar"))))));
2680+
EXPECT_TRUE(
2681+
matches(Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<>::Bar"))))));
2682+
EXPECT_TRUE(notMatches(
2683+
Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<::Bar"))))));
2684+
EXPECT_TRUE(notMatches(
2685+
Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<long>::Bar"))))));
2686+
2687+
if (GetParam().isCXX11OrLater()) {
2688+
Code = "template<typename T> struct Foo{void Bar() const;}; void Func() { "
2689+
"Foo<Foo<int>> Item; Item.Bar(); }";
2690+
EXPECT_TRUE(matches(Code, callExpr(callee(cxxMethodDecl(hasName("Bar"))))));
2691+
EXPECT_TRUE(
2692+
matches(Code, callExpr(callee(cxxMethodDecl(hasName("Foo<>::Bar"))))));
2693+
EXPECT_TRUE(matches(
2694+
Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<>::Bar"))))));
2695+
EXPECT_TRUE(matches(
2696+
Code, callExpr(callee(cxxMethodDecl(hasName("Foo<Foo<>>::Bar"))))));
2697+
EXPECT_TRUE(matches(
2698+
Code, callExpr(callee(cxxMethodDecl(hasName("::Foo<Foo<>>::Bar"))))));
2699+
}
2700+
}
2701+
26672702
TEST_P(ASTMatchersTest, HasAnyName) {
26682703
if (!GetParam().isCXX()) {
26692704
// FIXME: Add a test for `hasAnyName()` that does not depend on C++.

0 commit comments

Comments
 (0)