Index: include/clang/ASTMatchers/ASTMatchers.h =================================================================== --- include/clang/ASTMatchers/ASTMatchers.h +++ include/clang/ASTMatchers/ASTMatchers.h @@ -2711,7 +2711,7 @@ const QualType TypeDecl = Node.getReceiverType(); return InnerMatcher.matches(TypeDecl, Finder, Builder); } - + /// \brief Matches when BaseName == Selector.getAsString() /// /// matcher = objCMessageExpr(hasSelector("loadHTMLString:baseURL:")); @@ -2725,7 +2725,40 @@ return BaseName.compare(Sel.getAsString()) == 0; } - +/// \brief Matches when Selector.getAsString() is inside of the supplied +/// vector of strings. +/// +/// matcher = objCMessageExpr(hasSelectorIn({"methodA:", "methodB:"})); +/// matches both of the expressions below: +/// \code +/// [myObj methodA:argA]; +/// [myObj methodB:argB]; +/// \endcode +AST_MATCHER_P(ObjCMessageExpr, hasAnySelectorIn, std::vector, + Matches) { + std::string SelString = Node.getSelector().getAsString(); + for (std::string S : Matches) + if (S.compare(SelString) == 0) + return true; + return false; +} + +/// \brief A variant of `hasAnySelectorIn` accepting a variadic number of +/// arguments, useful for dynamic matching. +/// Matches when at least one of the supplied string equals to the selector +/// name. +/// +/// matcher = objCMessageExpr(hasSelector("methodA:", "methodB:")); +/// matches both of the expressions below: +/// \code +/// [myObj methodA:argA]; +/// [myObj methodB:argB]; +/// \endcode +extern const internal::VariadicFunction, + StringRef, + internal::hasAnySelectorFunc> + hasAnySelector; + /// \brief Matches ObjC selectors whose name contains /// a substring matched by the given RegExp. /// matcher = objCMessageExpr(matchesSelector("loadHTMLString\:baseURL?")); Index: include/clang/ASTMatchers/ASTMatchersInternal.h =================================================================== --- include/clang/ASTMatchers/ASTMatchersInternal.h +++ include/clang/ASTMatchers/ASTMatchersInternal.h @@ -731,6 +731,11 @@ /// HasNameMatcher. Matcher hasAnyNameFunc(ArrayRef NameRefs); +/// \brief Trampoline function to use VariadicFunction<> to construct a +/// hasAnySelector matcher. +Matcher hasAnySelectorFunc( + ArrayRef NameRefs); + /// \brief Matches declarations for QualType and CallExpr. /// /// Type argument DeclMatcherT is required by PolymorphicMatcherWithParam1 but Index: lib/ASTMatchers/ASTMatchersInternal.cpp =================================================================== --- lib/ASTMatchers/ASTMatchersInternal.cpp +++ lib/ASTMatchers/ASTMatchersInternal.cpp @@ -315,14 +315,26 @@ return false; } -Matcher hasAnyNameFunc(ArrayRef NameRefs) { +inline static +std::vector vectorFromRefs(ArrayRef NameRefs) { std::vector Names; for (auto *Name : NameRefs) Names.emplace_back(*Name); + return Names; +} + +Matcher hasAnyNameFunc(ArrayRef NameRefs) { + std::vector Names = vectorFromRefs(NameRefs); return internal::Matcher( new internal::HasNameMatcher(std::move(Names))); } +Matcher hasAnySelectorFunc( + ArrayRef NameRefs) { + std::vector Names = vectorFromRefs(NameRefs); + return hasAnySelectorIn(Names); +} + HasNameMatcher::HasNameMatcher(std::vector N) : UseUnqualifiedMatch(std::all_of( N.begin(), N.end(), Index: lib/ASTMatchers/Dynamic/Registry.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Registry.cpp +++ lib/ASTMatchers/Dynamic/Registry.cpp @@ -100,6 +100,9 @@ // Polymorphic + argument overload: // findAll // + // Requires an array reference: + // hasAnySelectorIn (hasAnySelector still available) + // // Other: // equalsNode @@ -286,6 +289,7 @@ REGISTER_MATCHER(hasReturnValue); REGISTER_MATCHER(hasRHS); REGISTER_MATCHER(hasSelector); + REGISTER_MATCHER(hasAnySelector); REGISTER_MATCHER(hasSingleDecl); REGISTER_MATCHER(hasSize); REGISTER_MATCHER(hasSizeExpr); Index: unittests/ASTMatchers/ASTMatchersNodeTest.cpp =================================================================== --- unittests/ASTMatchers/ASTMatchersNodeTest.cpp +++ unittests/ASTMatchers/ASTMatchersNodeTest.cpp @@ -1565,9 +1565,26 @@ EXPECT_TRUE(matchesObjC( Objc1String, objcMessageExpr(anything()))); + EXPECT_TRUE(matchesObjC(Objc1String, + objcMessageExpr(hasAnySelector({ + "contents", "meth:"})) + + )); EXPECT_TRUE(matchesObjC( Objc1String, objcMessageExpr(hasSelector("contents")))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(hasAnySelector("contents", "contentsA")))); + EXPECT_FALSE(matchesObjC( + Objc1String, + objcMessageExpr(hasAnySelector("contentsB", "contentsC")))); + EXPECT_TRUE(matchesObjC( + Objc1String, + objcMessageExpr(hasAnySelectorIn({"contents", "contentsA"})))); + EXPECT_FALSE(matchesObjC( + Objc1String, + objcMessageExpr(hasAnySelectorIn({"contentsB", "contentsC"})))); EXPECT_TRUE(matchesObjC( Objc1String, objcMessageExpr(matchesSelector("cont*")))); Index: unittests/ASTMatchers/Dynamic/ParserTest.cpp =================================================================== --- unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -236,6 +236,17 @@ Error.toStringFull()); } +TEST(ParserTest, VariadicMatchTest) { + Diagnostics Error; + llvm::Optional OM(Parser::parseMatcherExpression( + "stmt(objcMessageExpr(hasAnySelector(\"methodA\", \"methodB:\")))", + &Error)); + EXPECT_EQ("", Error.toStringFull()); + auto M = OM->unconditionalConvertTo(); + EXPECT_TRUE(matchesObjC("@interface I @end " + "void foo(I* i) { [i methodA]; }", M)); +} + std::string ParseWithError(StringRef Code) { Diagnostics Error; VariantValue Value;