Index: include/clang/ASTMatchers/Dynamic/Diagnostics.h =================================================================== --- include/clang/ASTMatchers/Dynamic/Diagnostics.h +++ include/clang/ASTMatchers/Dynamic/Diagnostics.h @@ -60,11 +60,12 @@ enum ErrorType { ET_None = 0, - ET_RegistryNotFound = 1, + ET_RegistryMatcherNotFound = 1, ET_RegistryWrongArgCount = 2, ET_RegistryWrongArgType = 3, ET_RegistryNotBindable = 4, ET_RegistryAmbiguousOverload = 5, + ET_RegistryValueNotFound = 6, ET_ParserStringError = 100, ET_ParserNoOpenParen = 101, Index: include/clang/ASTMatchers/Dynamic/Parser.h =================================================================== --- include/clang/ASTMatchers/Dynamic/Parser.h +++ include/clang/ASTMatchers/Dynamic/Parser.h @@ -18,13 +18,14 @@ /// /// \code /// Grammar for the expressions supported: -/// := | +/// := | | /// := | /// := "quoted string" /// := [0-9]+ -/// := () | -/// ().bind() -/// := [a-zA-Z]+ +/// := +/// := () | +/// ().bind() +/// := [a-zA-Z]+ /// := | , /// \endcode /// @@ -62,6 +63,19 @@ public: virtual ~Sema(); + /// \brief Lookup a value by name. + /// + /// This can be used in the Sema layer to declare known constants or to + /// allow to split an expression in pieces. + /// + /// \param Name The name of the value to lookup. + /// + /// \return The named value. It could be any type that VariantValue + /// supports. A 'nothing' value means that the name is not recognized. + virtual VariantValue getNamedValue(StringRef Name) { + return VariantValue(); + } + /// \brief Process a matcher expression. /// /// All the arguments passed here have already been processed. @@ -100,6 +114,21 @@ Diagnostics *Error) = 0; }; + /// \brief Sema implementation that uses the matcher registry to process the + /// tokens. + class RegistrySema : public Parser::Sema { + public: + virtual ~RegistrySema(); + llvm::Optional lookupMatcherCtor(StringRef MatcherName, + const SourceRange &NameRange, + Diagnostics *Error) override; + VariantMatcher actOnMatcherExpression(MatcherCtor Ctor, + const SourceRange &NameRange, + StringRef BindID, + ArrayRef Args, + Diagnostics *Error) override; + }; + /// \brief Parse a matcher expression, creating matchers from the registry. /// /// This overload creates matchers calling directly into the registry. If the @@ -160,7 +189,9 @@ Diagnostics *Error); bool parseExpressionImpl(VariantValue *Value); - bool parseMatcherExpressionImpl(VariantValue *Value); + bool parseMatcherExpressionImpl(const TokenInfo &NameToken, + VariantValue *Value); + bool parseIdentifierPrefixImpl(VariantValue *Value); void addCompletion(const TokenInfo &CompToken, StringRef TypedText, StringRef Decl); Index: include/clang/ASTMatchers/Dynamic/VariantValue.h =================================================================== --- include/clang/ASTMatchers/Dynamic/VariantValue.h +++ include/clang/ASTMatchers/Dynamic/VariantValue.h @@ -78,7 +78,8 @@ /// \brief Clones the provided matchers. /// /// They should be the result of a polymorphic matcher. - static VariantMatcher PolymorphicMatcher(std::vector Matchers); + static VariantMatcher + PolymorphicMatcher(std::vector Matchers); /// \brief Creates a 'variadic' operator matcher. /// @@ -208,6 +209,10 @@ VariantValue(const std::string &String); VariantValue(const VariantMatcher &Matchers); + /// \brief Returns true iff this is not an empty value. + LLVM_EXPLICIT operator bool() const { return hasValue(); } + bool hasValue() const { return Type != VT_Nothing; } + /// \brief Unsigned value functions. bool isUnsigned() const; unsigned getUnsigned() const; Index: lib/ASTMatchers/Dynamic/Diagnostics.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Diagnostics.cpp +++ lib/ASTMatchers/Dynamic/Diagnostics.cpp @@ -87,7 +87,7 @@ StringRef errorTypeToFormatString(Diagnostics::ErrorType Type) { switch (Type) { - case Diagnostics::ET_RegistryNotFound: + case Diagnostics::ET_RegistryMatcherNotFound: return "Matcher not found: $0"; case Diagnostics::ET_RegistryWrongArgCount: return "Incorrect argument count. (Expected = $0) != (Actual = $1)"; @@ -98,6 +98,8 @@ case Diagnostics::ET_RegistryAmbiguousOverload: // TODO: Add type info about the overload error. return "Ambiguous matcher overload."; + case Diagnostics::ET_RegistryValueNotFound: + return "Value not found: $0"; case Diagnostics::ET_ParserStringError: return "Error parsing string token: <$0>"; Index: lib/ASTMatchers/Dynamic/Parser.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Parser.cpp +++ lib/ASTMatchers/Dynamic/Parser.cpp @@ -274,12 +274,43 @@ } }; +/// \brief Parse expressions that start with an identifier. +/// +/// This function can parse named values and matchers. +/// In case of failure it will try to determine the user's intent to give +/// an appropriate error message. +bool Parser::parseIdentifierPrefixImpl(VariantValue *Value) { + const TokenInfo NameToken = Tokenizer->consumeNextToken(); + + if (Tokenizer->nextTokenKind() != TokenInfo::TK_OpenParen) { + // Parse as a named value. + if (const VariantValue NamedValue = S->getNamedValue(NameToken.Text)) { + *Value = NamedValue; + return true; + } + // If the syntax is correct and the name is not a matcher either, report + // unknown named value. + if ((Tokenizer->nextTokenKind() == TokenInfo::TK_Comma || + Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen || + Tokenizer->nextTokenKind() == TokenInfo::TK_Eof) && + !S->lookupMatcherCtor(NameToken.Text, NameToken.Range, nullptr)) { + Error->addError(NameToken.Range, Error->ET_RegistryValueNotFound) + << NameToken.Text; + return false; + } + // Otherwise, fallback to the matcher parser. + } + + // Parse as a matcher expressoin. + return parseMatcherExpressionImpl(NameToken, Value); +} + /// \brief Parse and validate a matcher expression. /// \return \c true on success, in which case \c Value has the matcher parsed. /// If the input is malformed, or some argument has an error, it /// returns \c false. -bool Parser::parseMatcherExpressionImpl(VariantValue *Value) { - const TokenInfo NameToken = Tokenizer->consumeNextToken(); +bool Parser::parseMatcherExpressionImpl(const TokenInfo &NameToken, + VariantValue *Value) { assert(NameToken.Kind == TokenInfo::TK_Ident); const TokenInfo OpenToken = Tokenizer->consumeNextToken(); if (OpenToken.Kind != TokenInfo::TK_OpenParen) { @@ -425,7 +456,7 @@ return true; case TokenInfo::TK_Ident: - return parseMatcherExpressionImpl(Value); + return parseIdentifierPrefixImpl(Value); case TokenInfo::TK_CodeCompletion: addExpressionCompletions(); @@ -457,27 +488,23 @@ Diagnostics *Error) : Tokenizer(Tokenizer), S(S), Error(Error) {} -class RegistrySema : public Parser::Sema { -public: - virtual ~RegistrySema() {} - llvm::Optional lookupMatcherCtor(StringRef MatcherName, - const SourceRange &NameRange, - Diagnostics *Error) { - return Registry::lookupMatcherCtor(MatcherName, NameRange, Error); - } - VariantMatcher actOnMatcherExpression(MatcherCtor Ctor, - const SourceRange &NameRange, - StringRef BindID, - ArrayRef Args, - Diagnostics *Error) { - if (BindID.empty()) { - return Registry::constructMatcher(Ctor, NameRange, Args, Error); - } else { - return Registry::constructBoundMatcher(Ctor, NameRange, BindID, Args, - Error); - } +Parser::RegistrySema::~RegistrySema() {} + +llvm::Optional Parser::RegistrySema::lookupMatcherCtor( + StringRef MatcherName, const SourceRange &NameRange, Diagnostics *Error) { + return Registry::lookupMatcherCtor(MatcherName, NameRange, Error); +} + +VariantMatcher Parser::RegistrySema::actOnMatcherExpression( + MatcherCtor Ctor, const SourceRange &NameRange, StringRef BindID, + ArrayRef Args, Diagnostics *Error) { + if (BindID.empty()) { + return Registry::constructMatcher(Ctor, NameRange, Args, Error); + } else { + return Registry::constructBoundMatcher(Ctor, NameRange, BindID, Args, + Error); } -}; +} bool Parser::parseExpression(StringRef Code, VariantValue *Value, Diagnostics *Error) { Index: lib/ASTMatchers/Dynamic/Registry.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Registry.cpp +++ lib/ASTMatchers/Dynamic/Registry.cpp @@ -331,7 +331,10 @@ ConstructorMap::const_iterator it = RegistryData->constructors().find(MatcherName); if (it == RegistryData->constructors().end()) { - Error->addError(NameRange, Error->ET_RegistryNotFound) << MatcherName; + if (Error) { + Error->addError(NameRange, Error->ET_RegistryMatcherNotFound) + << MatcherName; + } return llvm::Optional(); } Index: unittests/ASTMatchers/Dynamic/ParserTest.cpp =================================================================== --- unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -175,6 +175,29 @@ EXPECT_TRUE(matches("void f(int a, int x);", M)); EXPECT_FALSE(matches("void f(int x, int a);", M)); + // Test named values. + struct NamedSema : public Parser::RegistrySema { + public: + virtual VariantValue getNamedValue(StringRef Name) { + if (Name == "nameX") + return std::string("x"); + if (Name == "param0") + return VariantMatcher::SingleMatcher(hasParameter(0, hasName("a"))); + return VariantValue(); + } + }; + NamedSema Sema; + llvm::Optional HasParameterWithNamedValues( + Parser::parseMatcherExpression( + "functionDecl(param0, hasParameter(1, hasName(nameX)))", &Sema, + &Error)); + EXPECT_EQ("", Error.toStringFull()); + M = HasParameterWithNamedValues->unconditionalConvertTo(); + + EXPECT_TRUE(matches("void f(int a, int x);", M)); + EXPECT_FALSE(matches("void f(int x, int a);", M)); + + EXPECT_TRUE(!Parser::parseMatcherExpression( "hasInitializer(\n binaryOperator(hasLHS(\"A\")))", &Error).hasValue()); @@ -208,6 +231,10 @@ "1:9: Error parsing matcher. Found token <123> while looking for ','.", ParseWithError("Foo(\"A\" 123)")); EXPECT_EQ( + "1:1: Error parsing argument 1 for matcher stmt.\n" + "1:6: Value not found: someValue", + ParseWithError("stmt(someValue)")); + EXPECT_EQ( "1:1: Matcher not found: Foo\n" "1:4: Error parsing matcher. Found end-of-code while looking for ')'.", ParseWithError("Foo(")); Index: unittests/ASTMatchers/Dynamic/VariantValueTest.cpp =================================================================== --- unittests/ASTMatchers/Dynamic/VariantValueTest.cpp +++ unittests/ASTMatchers/Dynamic/VariantValueTest.cpp @@ -26,6 +26,7 @@ EXPECT_TRUE(Value.isUnsigned()); EXPECT_EQ(kUnsigned, Value.getUnsigned()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isString()); EXPECT_FALSE(Value.isMatcher()); } @@ -38,6 +39,7 @@ EXPECT_EQ(kString, Value.getString()); EXPECT_EQ("String", Value.getTypeAsString()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isUnsigned()); EXPECT_FALSE(Value.isMatcher()); } @@ -45,6 +47,7 @@ TEST(VariantValueTest, DynTypedMatcher) { VariantValue Value = VariantMatcher::SingleMatcher(stmt()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isUnsigned()); EXPECT_FALSE(Value.isString()); @@ -74,11 +77,13 @@ VariantValue Value = std::string("A"); EXPECT_TRUE(Value.isString()); EXPECT_EQ("A", Value.getString()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isUnsigned()); EXPECT_FALSE(Value.isMatcher()); EXPECT_EQ("String", Value.getTypeAsString()); Value = VariantMatcher::SingleMatcher(recordDecl()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isUnsigned()); EXPECT_FALSE(Value.isString()); EXPECT_TRUE(Value.isMatcher()); @@ -89,16 +94,36 @@ Value = 17; EXPECT_TRUE(Value.isUnsigned()); EXPECT_EQ(17U, Value.getUnsigned()); + EXPECT_TRUE(Value.hasValue()); EXPECT_FALSE(Value.isMatcher()); EXPECT_FALSE(Value.isString()); Value = VariantValue(); + EXPECT_FALSE(Value.hasValue()); EXPECT_FALSE(Value.isUnsigned()); EXPECT_FALSE(Value.isString()); EXPECT_FALSE(Value.isMatcher()); EXPECT_EQ("Nothing", Value.getTypeAsString()); } +TEST(VariantValueTest, ImplicitBool) { + VariantValue Value; + bool IfTrue = false; + if (Value) { + IfTrue = true; + } + EXPECT_FALSE(IfTrue); + EXPECT_TRUE(!Value); + + Value = std::string(); + IfTrue = false; + if (Value) { + IfTrue = true; + } + EXPECT_TRUE(IfTrue); + EXPECT_FALSE(!Value); +} + TEST(VariantValueTest, Matcher) { EXPECT_TRUE(matches("class X {};", VariantValue(VariantMatcher::SingleMatcher( recordDecl(hasName("X"))))