- Notifications
You must be signed in to change notification settings - Fork 15.3k
[ASTMatchers] Extend hasName matcher when matching templates #100349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| @llvm/pr-subscribers-clang Author: Nathan James (njames93) ChangesAllow users to match all record instantiations by using <> as a wildcard. With template <typename T> struct Foo { void Bar(); };The following code: Foo<int>{}.Bar();Will match against: Full diff: https://github.com/llvm/llvm-project/pull/100349.diff 3 Files Affected:
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++. |
ee05cd6 to d623ba5 Compare
I think this will change the behavior of existing matchers, consider: https://godbolt.org/z/Gr439c9v4 WDYT? |
| @AaronBallman That's a good point, I didn't account for how defaulted template arguments aren't printed. |
I was thinking about that, but I keep coming around to wondering whether it might make sense to support a regex there instead of a custom syntax. That seems like it gives users a bit more power without requiring them to write additional matchers, but perhaps that's a bad idea for performance reasons... |
We already have a |
Okay, that's a good point! Hmm, then yeah, I think |
Allow users to match all record instantiations by using <> as a wildcard
d623ba5 to a81c1b2 Compare
AaronBallman left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes should also come with a release note in clang/docs/ReleaseNotes.rst.
| /// \endcode | ||
| /// | ||
| /// Qualified names in templated classes can be matched explicitly or implicity | ||
| /// by specifying the template type or using `<*>` to match any template. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be clear that <*> is the only syntax we support. e.g., we don't let you search based on arity, as in hasName("Foo<int, *>::Bar"). Might be worth an example as well, definitely worth some test coverage.
5chmidti left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it does make sense to add this wildcard. The motivation to skip regex sounds like a good one, and it does make sense to special case templates specifically. The implementation looks correct to me. Just some small things to point out regarding the code, and the missing release notes, of course.
Your pr comment should also reflect the change to <*> from <>
| return {}; | ||
| } | ||
| auto Suffix = Pattern.substr(Index + 2); | ||
| if (!Target.consume_back(Suffix)) | ||
| return {}; | ||
| auto BracketCount = 1; | ||
| while (BracketCount) { | ||
| if (Target.empty()) | ||
| return {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO returning std::nullopt is better here.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're using auto three times here without the type mentioned on the RHS (not sure how strict clang/ is w.r.t. this)
Suffix and Index can be const
| auto Suffix = Pattern.substr(Index + 2); | ||
| if (!Target.consume_back(Suffix)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I had to check this, could you please add a comment why only + 2 is used?
Something like:
We consume pattern suffix that includes only the
>of our<*>, and drop every character from the back until we encounter the corresponding<character to ensure balanced angle brackets.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could write this loop body as
const std::optional<StringRef> Result = consumePatternBack(Pattern, FullName); if (!Result) continue; if ((Pattern.starts_with("::") && Result->empty()) || Result->ends_with("::")) return true;which is more readable IMO
Allow users to match all record instantiations by using <> as a wildcard.
With
The following code:
Foo<int>{}.Bar();Will match against: