diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h index bc4ddce36fc9..6428ad85a723 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -232,7 +232,7 @@ private: /// on the actual node, or return false if it is not convertible. class DynTypedMatcher { public: - virtual ~DynTypedMatcher() {} + virtual ~DynTypedMatcher(); /// \brief Returns true if the matcher matches the given \c DynNode. virtual bool matches(const ast_type_traits::DynTypedNode DynNode, @@ -244,6 +244,11 @@ public: /// \brief Returns a unique ID for the matcher. virtual uint64_t getID() const = 0; + + /// \brief Bind the specified \p ID to the matcher. + /// \return A new matcher with the \p ID bound to it if this matcher supports + /// binding. Otherwise, returns NULL. Returns NULL by default. + virtual DynTypedMatcher* tryBind(StringRef ID) const; }; /// \brief Wrapper of a MatcherInterface *that allows copying. @@ -806,6 +811,16 @@ public: Matcher bind(StringRef ID) const { return Matcher(new IdMatcher(ID, *this)); } + + /// \brief Makes a copy of this matcher object. + virtual BindableMatcher* clone() const { + return new BindableMatcher(*this); + } + + /// \brief Bind the specified \c ID to the matcher. + virtual Matcher* tryBind(StringRef ID) const { + return new Matcher(bind(ID)); + } }; /// \brief Matches nodes of type T that have child nodes of type ChildT for diff --git a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h index 417bc67967a1..38e87ce37acb 100644 --- a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h @@ -57,6 +57,7 @@ class Diagnostics { ET_RegistryNotFound = 1, ET_RegistryWrongArgCount = 2, ET_RegistryWrongArgType = 3, + ET_RegistryNotBindable = 4, ET_ParserStringError = 100, ET_ParserMatcherArgFailure = 101, @@ -66,7 +67,9 @@ class Diagnostics { ET_ParserNoComma = 105, ET_ParserNoCode = 106, ET_ParserNotAMatcher = 107, - ET_ParserInvalidToken = 108 + ET_ParserInvalidToken = 108, + ET_ParserMalformedBindExpr = 109, + ET_ParserTrailingCode = 110 }; /// \brief Helper stream class. diff --git a/clang/include/clang/ASTMatchers/Dynamic/Parser.h b/clang/include/clang/ASTMatchers/Dynamic/Parser.h index f981c6055e55..b6cd4afebe9a 100644 --- a/clang/include/clang/ASTMatchers/Dynamic/Parser.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Parser.h @@ -20,7 +20,8 @@ /// Grammar for the expressions supported: /// := | /// := "quoted string" -/// := () +/// := () | +/// ().bind() /// := [a-zA-Z]+ /// := | , /// \endcode @@ -66,15 +67,18 @@ public: /// \param NameRange The location of the name in the matcher source. /// Useful for error reporting. /// + /// \param BindID The ID to use to bind the matcher, or a null \c StringRef + /// if no ID is specified. + /// /// \param Args The argument list for the matcher. /// /// \return The matcher object constructed by the processor, or NULL /// if an error occurred. In that case, \c Error will contain a /// description of the error. /// The caller takes ownership of the DynTypedMatcher object returned. - virtual DynTypedMatcher * - actOnMatcherExpression(StringRef MatcherName, const SourceRange &NameRange, - ArrayRef Args, Diagnostics *Error) = 0; + virtual DynTypedMatcher *actOnMatcherExpression( + StringRef MatcherName, const SourceRange &NameRange, StringRef BindID, + ArrayRef Args, Diagnostics *Error) = 0; }; /// \brief Parse a matcher expression, creating matchers from the registry. diff --git a/clang/include/clang/ASTMatchers/Dynamic/Registry.h b/clang/include/clang/ASTMatchers/Dynamic/Registry.h index b092ed8fe150..dede8df776fd 100644 --- a/clang/include/clang/ASTMatchers/Dynamic/Registry.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Registry.h @@ -52,6 +52,18 @@ public: ArrayRef Args, Diagnostics *Error); + /// \brief Construct a matcher from the registry and bind it. + /// + /// Similar the \c constructMatcher() above, but it then tries to bind the + /// matcher to the specified \c BindID. + /// If the matcher is not bindable, it sets an error in \c Error and returns + /// \c NULL. + static DynTypedMatcher *constructBoundMatcher(StringRef MatcherName, + const SourceRange &NameRange, + StringRef BindID, + ArrayRef Args, + Diagnostics *Error); + private: Registry() LLVM_DELETED_FUNCTION; }; diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp index f1a9ff2e09cb..3144261612cc 100644 --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -82,6 +82,10 @@ BoundNodesTree BoundNodesTreeBuilder::build() const { return BoundNodesTree(Bindings, RecursiveBindings); } +DynTypedMatcher::~DynTypedMatcher() {} + +DynTypedMatcher *DynTypedMatcher::tryBind(StringRef ID) const { return NULL; } + } // end namespace internal } // end namespace ast_matchers } // end namespace clang diff --git a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp index fb3cac370f53..4b01b997a178 100644 --- a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp @@ -37,6 +37,8 @@ StringRef ErrorTypeToString(Diagnostics::ErrorType Type) { return "Incorrect argument count. (Expected = $0) != (Actual = $1)"; case Diagnostics::ET_RegistryWrongArgType: return "Incorrect type on function $0 for arg $1."; + case Diagnostics::ET_RegistryNotBindable: + return "Matcher does not support binding."; case Diagnostics::ET_ParserStringError: return "Error parsing string token: <$0>"; @@ -56,6 +58,10 @@ StringRef ErrorTypeToString(Diagnostics::ErrorType Type) { return "Input value is not a matcher expression."; case Diagnostics::ET_ParserInvalidToken: return "Invalid token <$0> found when looking for a value."; + case Diagnostics::ET_ParserMalformedBindExpr: + return "Malformed bind() expression."; + case Diagnostics::ET_ParserTrailingCode: + return "Expected end of code."; case Diagnostics::ET_None: return ""; diff --git a/clang/lib/ASTMatchers/Dynamic/Parser.cpp b/clang/lib/ASTMatchers/Dynamic/Parser.cpp index 1678820da015..1ed40f3050fe 100644 --- a/clang/lib/ASTMatchers/Dynamic/Parser.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Parser.cpp @@ -32,12 +32,16 @@ struct Parser::TokenInfo { TK_OpenParen = 1, TK_CloseParen = 2, TK_Comma = 3, - TK_Literal = 4, - TK_Ident = 5, - TK_InvalidChar = 6, - TK_Error = 7 + TK_Period = 4, + TK_Literal = 5, + TK_Ident = 6, + TK_InvalidChar = 7, + TK_Error = 8 }; + /// \brief Some known identifiers. + static const char* const ID_Bind; + TokenInfo() : Text(), Kind(TK_Eof), Range(), Value() {} StringRef Text; @@ -46,6 +50,8 @@ struct Parser::TokenInfo { VariantValue Value; }; +const char* const Parser::TokenInfo::ID_Bind = "bind"; + /// \brief Simple tokenizer for the parser. class Parser::CodeTokenizer { public: @@ -84,6 +90,11 @@ private: Result.Text = Code.substr(0, 1); Code = Code.drop_front(); break; + case '.': + Result.Kind = TokenInfo::TK_Period; + Result.Text = Code.substr(0, 1); + Code = Code.drop_front(); + break; case '(': Result.Kind = TokenInfo::TK_OpenParen; Result.Text = Code.substr(0, 1); @@ -234,11 +245,43 @@ bool Parser::parseMatcherExpressionImpl(VariantValue *Value) { return false; } + std::string BindID; + if (Tokenizer->peekNextToken().Kind == TokenInfo::TK_Period) { + // Parse .bind("foo") + Tokenizer->consumeNextToken(); // consume the period. + const TokenInfo BindToken = Tokenizer->consumeNextToken(); + const TokenInfo OpenToken = Tokenizer->consumeNextToken(); + const TokenInfo IDToken = Tokenizer->consumeNextToken(); + const TokenInfo CloseToken = Tokenizer->consumeNextToken(); + + // TODO: We could use different error codes for each/some to be more + // explicit about the syntax error. + if (BindToken.Kind != TokenInfo::TK_Ident || + BindToken.Text != TokenInfo::ID_Bind) { + Error->pushErrorFrame(BindToken.Range, Error->ET_ParserMalformedBindExpr); + return false; + } + if (OpenToken.Kind != TokenInfo::TK_OpenParen) { + Error->pushErrorFrame(OpenToken.Range, Error->ET_ParserMalformedBindExpr); + return false; + } + if (IDToken.Kind != TokenInfo::TK_Literal || !IDToken.Value.isString()) { + Error->pushErrorFrame(IDToken.Range, Error->ET_ParserMalformedBindExpr); + return false; + } + if (CloseToken.Kind != TokenInfo::TK_CloseParen) { + Error->pushErrorFrame(CloseToken.Range, + Error->ET_ParserMalformedBindExpr); + return false; + } + BindID = IDToken.Value.getString(); + } + // Merge the start and end infos. SourceRange MatcherRange = NameToken.Range; MatcherRange.End = EndToken.Range.End; - DynTypedMatcher *Result = - S->actOnMatcherExpression(NameToken.Text, MatcherRange, Args, Error); + DynTypedMatcher *Result = S->actOnMatcherExpression( + NameToken.Text, MatcherRange, BindID, Args, Error); if (Result == NULL) { Error->pushErrorFrame(NameToken.Range, Error->ET_ParserMatcherFailure) << NameToken.Text; @@ -271,6 +314,7 @@ bool Parser::parseExpressionImpl(VariantValue *Value) { case TokenInfo::TK_OpenParen: case TokenInfo::TK_CloseParen: case TokenInfo::TK_Comma: + case TokenInfo::TK_Period: case TokenInfo::TK_InvalidChar: const TokenInfo Token = Tokenizer->consumeNextToken(); Error->pushErrorFrame(Token.Range, Error->ET_ParserInvalidToken) @@ -290,9 +334,15 @@ public: virtual ~RegistrySema() {} DynTypedMatcher *actOnMatcherExpression(StringRef MatcherName, const SourceRange &NameRange, + StringRef BindID, ArrayRef Args, Diagnostics *Error) { - return Registry::constructMatcher(MatcherName, NameRange, Args, Error); + if (BindID.empty()) { + return Registry::constructMatcher(MatcherName, NameRange, Args, Error); + } else { + return Registry::constructBoundMatcher(MatcherName, NameRange, BindID, + Args, Error); + } } }; @@ -305,7 +355,13 @@ bool Parser::parseExpression(StringRef Code, VariantValue *Value, bool Parser::parseExpression(StringRef Code, Sema *S, VariantValue *Value, Diagnostics *Error) { CodeTokenizer Tokenizer(Code, Error); - return Parser(&Tokenizer, S, Error).parseExpressionImpl(Value); + if (!Parser(&Tokenizer, S, Error).parseExpressionImpl(Value)) return false; + if (Tokenizer.peekNextToken().Kind != TokenInfo::TK_Eof) { + Error->pushErrorFrame(Tokenizer.peekNextToken().Range, + Error->ET_ParserTrailingCode); + return false; + } + return true; } DynTypedMatcher *Parser::parseMatcherExpression(StringRef Code, diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp index 34a230b8728d..6e543fc85045 100644 --- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp @@ -148,6 +148,23 @@ DynTypedMatcher *Registry::constructMatcher(StringRef MatcherName, return it->second->run(NameRange, Args, Error); } +// static +DynTypedMatcher *Registry::constructBoundMatcher(StringRef MatcherName, + const SourceRange &NameRange, + StringRef BindID, + ArrayRef Args, + Diagnostics *Error) { + OwningPtr Out( + constructMatcher(MatcherName, NameRange, Args, Error)); + if (!Out) return NULL; + DynTypedMatcher *Bound = Out->tryBind(BindID); + if (!Bound) { + Error->pushErrorFrame(NameRange, Error->ET_RegistryNotBindable); + return NULL; + } + return Bound; +} + } // namespace dynamic } // namespace ast_matchers } // namespace clang diff --git a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp index 41f522856df2..310108a38ce4 100644 --- a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -24,6 +24,8 @@ namespace { class DummyDynTypedMatcher : public DynTypedMatcher { public: DummyDynTypedMatcher(uint64_t ID) : ID(ID) {} + DummyDynTypedMatcher(uint64_t ID, StringRef BoundID) + : ID(ID), BoundID(BoundID) {} typedef ast_matchers::internal::ASTMatchFinder ASTMatchFinder; typedef ast_matchers::internal::BoundNodesTreeBuilder BoundNodesTreeBuilder; @@ -35,14 +37,21 @@ public: /// \brief Makes a copy of this matcher object. virtual DynTypedMatcher *clone() const { - return new DummyDynTypedMatcher(ID); + return new DummyDynTypedMatcher(*this); } /// \brief Returns a unique ID for the matcher. virtual uint64_t getID() const { return ID; } + virtual DynTypedMatcher* tryBind(StringRef BoundID) const { + return new DummyDynTypedMatcher(ID, BoundID); + } + + StringRef boundID() const { return BoundID; } + private: uint64_t ID; + std::string BoundID; }; class MockSema : public Parser::Sema { @@ -65,17 +74,20 @@ public: DynTypedMatcher *actOnMatcherExpression(StringRef MatcherName, const SourceRange &NameRange, + StringRef BindID, ArrayRef Args, Diagnostics *Error) { - MatcherInfo ToStore = { MatcherName, NameRange, Args }; + MatcherInfo ToStore = { MatcherName, NameRange, Args, BindID }; Matchers.push_back(ToStore); - return new DummyDynTypedMatcher(ExpectedMatchers[MatcherName]); + DummyDynTypedMatcher Matcher(ExpectedMatchers[MatcherName]); + return Matcher.tryBind(BindID); } struct MatcherInfo { StringRef MatcherName; SourceRange NameRange; std::vector Args; + std::string BoundID; }; std::vector Errors; @@ -110,13 +122,15 @@ TEST(ParserTest, ParseMatcher) { const uint64_t ExpectedFoo = Sema.expectMatcher("Foo"); const uint64_t ExpectedBar = Sema.expectMatcher("Bar"); const uint64_t ExpectedBaz = Sema.expectMatcher("Baz"); - Sema.parse(" Foo ( Bar (), Baz( \n \"B A,Z\") ) "); + Sema.parse(" Foo ( Bar (), Baz( \n \"B A,Z\") ) .bind( \"Yo!\") "); for (size_t i = 0, e = Sema.Errors.size(); i != e; ++i) { EXPECT_EQ("", Sema.Errors[i]); } EXPECT_EQ(1ULL, Sema.Values.size()); EXPECT_EQ(ExpectedFoo, Sema.Values[0].getMatcher().getID()); + EXPECT_EQ("Yo!", static_cast( + Sema.Values[0].getMatcher()).boundID()); EXPECT_EQ(3ULL, Sema.Matchers.size()); const MockSema::MatcherInfo Bar = Sema.Matchers[0]; @@ -136,6 +150,7 @@ TEST(ParserTest, ParseMatcher) { EXPECT_EQ(2ULL, Foo.Args.size()); EXPECT_EQ(ExpectedBar, Foo.Args[0].Value.getMatcher().getID()); EXPECT_EQ(ExpectedBaz, Foo.Args[1].Value.getMatcher().getID()); + EXPECT_EQ("Yo!", Foo.BoundID); } using ast_matchers::internal::Matcher; @@ -186,6 +201,18 @@ TEST(ParserTest, Errors) { EXPECT_EQ("1:1: Error parsing argument 1 for matcher Foo.\n" "1:5: Invalid token <(> found when looking for a value.", ParseWithError("Foo((")); + EXPECT_EQ("1:7: Expected end of code.", ParseWithError("expr()a")); + EXPECT_EQ("1:11: Malformed bind() expression.", + ParseWithError("isArrow().biind")); + EXPECT_EQ("1:15: Malformed bind() expression.", + ParseWithError("isArrow().bind")); + EXPECT_EQ("1:16: Malformed bind() expression.", + ParseWithError("isArrow().bind(foo")); + EXPECT_EQ("1:21: Malformed bind() expression.", + ParseWithError("isArrow().bind(\"foo\"")); + EXPECT_EQ("1:1: Error building matcher isArrow.\n" + "1:1: Matcher does not support binding.", + ParseWithError("isArrow().bind(\"foo\")")); } } // end anonymous namespace