diff --git a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h --- a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h @@ -66,6 +66,8 @@ ET_RegistryAmbiguousOverload = 5, ET_RegistryValueNotFound = 6, ET_RegistryUnknownEnumWithReplace = 7, + ET_RegistryNonNodeMatcher = 8, + ET_RegistryMatcherNoWithSupport = 9, ET_ParserStringError = 100, ET_ParserNoOpenParen = 101, @@ -77,7 +79,9 @@ ET_ParserMalformedBindExpr = 107, ET_ParserTrailingCode = 108, ET_ParserNumberError = 109, - ET_ParserOverloadedType = 110 + ET_ParserOverloadedType = 110, + ET_ParserMalformedChainedExpr = 111, + ET_ParserFailedToBuildMatcher = 112 }; /// Helper stream class. diff --git a/clang/include/clang/ASTMatchers/Dynamic/Parser.h b/clang/include/clang/ASTMatchers/Dynamic/Parser.h --- a/clang/include/clang/ASTMatchers/Dynamic/Parser.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Parser.h @@ -100,6 +100,14 @@ virtual llvm::Optional lookupMatcherCtor(StringRef MatcherName) = 0; + virtual bool isBuilderMatcher(MatcherCtor) const = 0; + + virtual ASTNodeKind nodeMatcherType(MatcherCtor) const = 0; + + virtual internal::MatcherDescriptorPtr + buildMatcherCtor(MatcherCtor, SourceRange NameRange, + ArrayRef Args, Diagnostics *Error) const = 0; + /// Compute the list of completion types for \p Context. /// /// Each element of \p Context represents a matcher invocation, going from @@ -142,6 +150,15 @@ std::vector getAcceptedCompletionTypes( llvm::ArrayRef> Context) override; + bool isBuilderMatcher(MatcherCtor Ctor) const override; + + ASTNodeKind nodeMatcherType(MatcherCtor) const override; + + internal::MatcherDescriptorPtr + buildMatcherCtor(MatcherCtor, SourceRange NameRange, + ArrayRef Args, + Diagnostics *Error) const override; + std::vector getMatcherCompletions(llvm::ArrayRef AcceptedTypes) override; }; @@ -233,6 +250,8 @@ bool parseBindID(std::string &BindID); bool parseExpressionImpl(VariantValue *Value); + bool parseMatcherBuilder(MatcherCtor Ctor, const TokenInfo &NameToken, + const TokenInfo &OpenToken, VariantValue *Value); bool parseMatcherExpressionImpl(const TokenInfo &NameToken, const TokenInfo &OpenToken, llvm::Optional Ctor, diff --git a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp --- a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp @@ -100,6 +100,10 @@ return "Value not found: $0"; case Diagnostics::ET_RegistryUnknownEnumWithReplace: return "Unknown value '$1' for arg $0; did you mean '$2'"; + case Diagnostics::ET_RegistryNonNodeMatcher: + return "Matcher not a node matcher: $0"; + case Diagnostics::ET_RegistryMatcherNoWithSupport: + return "Matcher does not support with call."; case Diagnostics::ET_ParserStringError: return "Error parsing string token: <$0>"; @@ -123,6 +127,10 @@ return "Error parsing numeric literal: <$0>"; case Diagnostics::ET_ParserOverloadedType: return "Input value has unresolved overloaded type: $0"; + case Diagnostics::ET_ParserMalformedChainedExpr: + return "Period not followed by valid chained call."; + case Diagnostics::ET_ParserFailedToBuildMatcher: + return "Failed to build matcher: $0."; case Diagnostics::ET_None: return ""; diff --git a/clang/lib/ASTMatchers/Dynamic/Parser.cpp b/clang/lib/ASTMatchers/Dynamic/Parser.cpp --- a/clang/lib/ASTMatchers/Dynamic/Parser.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Parser.cpp @@ -52,6 +52,7 @@ /// Some known identifiers. static const char* const ID_Bind; + static const char *const ID_With; TokenInfo() = default; @@ -62,6 +63,7 @@ }; const char* const Parser::TokenInfo::ID_Bind = "bind"; +const char *const Parser::TokenInfo::ID_With = "with"; /// Simple tokenizer for the parser. class Parser::CodeTokenizer { @@ -367,14 +369,26 @@ std::string BindID; Tokenizer->consumeNextToken(); - TokenInfo BindToken = Tokenizer->consumeNextToken(); - if (BindToken.Kind == TokenInfo::TK_CodeCompletion) { - addCompletion(BindToken, MatcherCompletion("bind(\"", "bind", 1)); + TokenInfo ChainCallToken = Tokenizer->consumeNextToken(); + if (ChainCallToken.Kind == TokenInfo::TK_CodeCompletion) { + addCompletion(ChainCallToken, MatcherCompletion("bind(\"", "bind", 1)); return false; } - if (BindToken.Kind != TokenInfo::TK_Ident || - BindToken.Text != TokenInfo::ID_Bind) { - Error->addError(BindToken.Range, Error->ET_ParserMalformedBindExpr); + + if (ChainCallToken.Kind != TokenInfo::TK_Ident || + (ChainCallToken.Text != TokenInfo::ID_Bind && + ChainCallToken.Text != TokenInfo::ID_With)) { + Error->addError(ChainCallToken.Range, + Error->ET_ParserMalformedChainedExpr); + return false; + } + if (ChainCallToken.Text == TokenInfo::ID_With) { + + Diagnostics::Context Ctx(Diagnostics::Context::ConstructMatcher, Error, + NameToken.Text, NameToken.Range); + + Error->addError(ChainCallToken.Range, + Error->ET_RegistryMatcherNoWithSupport); return false; } if (!parseBindID(BindID)) @@ -454,6 +468,160 @@ return true; } +bool Parser::parseMatcherBuilder(MatcherCtor Ctor, const TokenInfo &NameToken, + const TokenInfo &OpenToken, + VariantValue *Value) { + std::vector Args; + TokenInfo EndToken; + + Tokenizer->SkipNewlines(); + + { + ScopedContextEntry SCE(this, Ctor); + + while (Tokenizer->nextTokenKind() != TokenInfo::TK_Eof) { + if (Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen) { + // End of args. + EndToken = Tokenizer->consumeNextToken(); + break; + } + if (!Args.empty()) { + // We must find a , token to continue. + TokenInfo CommaToken = Tokenizer->consumeNextToken(); + if (CommaToken.Kind != TokenInfo::TK_Comma) { + Error->addError(CommaToken.Range, Error->ET_ParserNoComma) + << CommaToken.Text; + return false; + } + } + + Diagnostics::Context Ctx(Diagnostics::Context::MatcherArg, Error, + NameToken.Text, NameToken.Range, + Args.size() + 1); + ParserValue ArgValue; + Tokenizer->SkipNewlines(); + + if (Tokenizer->peekNextToken().Kind == TokenInfo::TK_CodeCompletion) { + addExpressionCompletions(); + return false; + } + + TokenInfo NodeMatcherToken = Tokenizer->consumeNextToken(); + + if (NodeMatcherToken.Kind != TokenInfo::TK_Ident) { + Error->addError(NameToken.Range, Error->ET_ParserFailedToBuildMatcher) + << NameToken.Text; + return false; + } + + ArgValue.Text = NodeMatcherToken.Text; + ArgValue.Range = NodeMatcherToken.Range; + + llvm::Optional MappedMatcher = + S->lookupMatcherCtor(ArgValue.Text); + + if (!MappedMatcher) { + Error->addError(NodeMatcherToken.Range, + Error->ET_RegistryMatcherNotFound) + << NodeMatcherToken.Text; + return false; + } + + ASTNodeKind NK = S->nodeMatcherType(*MappedMatcher); + + if (NK.isNone()) { + Error->addError(NodeMatcherToken.Range, + Error->ET_RegistryNonNodeMatcher) + << NodeMatcherToken.Text; + return false; + } + + ArgValue.Value = NK; + + Tokenizer->SkipNewlines(); + Args.push_back(ArgValue); + + SCE.nextArg(); + } + } + + if (EndToken.Kind == TokenInfo::TK_Eof) { + Error->addError(OpenToken.Range, Error->ET_ParserNoCloseParen); + return false; + } + + internal::MatcherDescriptorPtr BuiltCtor = + S->buildMatcherCtor(Ctor, NameToken.Range, Args, Error); + + if (!BuiltCtor.get()) { + Error->addError(NameToken.Range, Error->ET_ParserFailedToBuildMatcher) + << NameToken.Text; + return false; + } + + std::string BindID; + if (Tokenizer->peekNextToken().Kind == TokenInfo::TK_Period) { + Tokenizer->consumeNextToken(); + TokenInfo ChainCallToken = Tokenizer->consumeNextToken(); + if (ChainCallToken.Kind == TokenInfo::TK_CodeCompletion) { + addCompletion(ChainCallToken, MatcherCompletion("bind(\"", "bind", 1)); + addCompletion(ChainCallToken, MatcherCompletion("with(", "with", 1)); + return false; + } + if (ChainCallToken.Kind != TokenInfo::TK_Ident || + (ChainCallToken.Text != TokenInfo::ID_Bind && + ChainCallToken.Text != TokenInfo::ID_With)) { + Error->addError(ChainCallToken.Range, + Error->ET_ParserMalformedChainedExpr); + return false; + } + if (ChainCallToken.Text == TokenInfo::ID_Bind) { + if (!parseBindID(BindID)) + return false; + Diagnostics::Context Ctx(Diagnostics::Context::ConstructMatcher, Error, + NameToken.Text, NameToken.Range); + SourceRange MatcherRange = NameToken.Range; + MatcherRange.End = ChainCallToken.Range.End; + VariantMatcher Result = S->actOnMatcherExpression( + BuiltCtor.get(), MatcherRange, BindID, {}, Error); + if (Result.isNull()) + return false; + + *Value = Result; + return true; + } else if (ChainCallToken.Text == TokenInfo::ID_With) { + Tokenizer->SkipNewlines(); + + if (Tokenizer->nextTokenKind() != TokenInfo::TK_OpenParen) { + StringRef ErrTxt = Tokenizer->nextTokenKind() == TokenInfo::TK_Eof + ? StringRef("EOF") + : Tokenizer->peekNextToken().Text; + Error->addError(Tokenizer->peekNextToken().Range, + Error->ET_ParserNoOpenParen) + << ErrTxt; + return false; + } + + TokenInfo WithOpenToken = Tokenizer->consumeNextToken(); + + return parseMatcherExpressionImpl(NameToken, WithOpenToken, + BuiltCtor.get(), Value); + } + } + + Diagnostics::Context Ctx(Diagnostics::Context::ConstructMatcher, Error, + NameToken.Text, NameToken.Range); + SourceRange MatcherRange = NameToken.Range; + MatcherRange.End = EndToken.Range.End; + VariantMatcher Result = S->actOnMatcherExpression( + BuiltCtor.get(), MatcherRange, BindID, {}, Error); + if (Result.isNull()) + return false; + + *Value = Result; + return true; +} + /// 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 @@ -468,6 +636,9 @@ // Do not return here. We need to continue to give completion suggestions. } + if (Ctor && *Ctor && S->isBuilderMatcher(*Ctor)) + return parseMatcherBuilder(*Ctor, NameToken, OpenToken, Value); + std::vector Args; TokenInfo EndToken; @@ -517,14 +688,29 @@ std::string BindID; if (Tokenizer->peekNextToken().Kind == TokenInfo::TK_Period) { Tokenizer->consumeNextToken(); - TokenInfo BindToken = Tokenizer->consumeNextToken(); - if (BindToken.Kind == TokenInfo::TK_CodeCompletion) { - addCompletion(BindToken, MatcherCompletion("bind(\"", "bind", 1)); + TokenInfo ChainCallToken = Tokenizer->consumeNextToken(); + if (ChainCallToken.Kind == TokenInfo::TK_CodeCompletion) { + addCompletion(ChainCallToken, MatcherCompletion("bind(\"", "bind", 1)); + return false; + } + + if (ChainCallToken.Kind != TokenInfo::TK_Ident) { + Error->addError(ChainCallToken.Range, + Error->ET_ParserMalformedChainedExpr); + return false; + } + if (ChainCallToken.Text == TokenInfo::ID_With) { + + Diagnostics::Context Ctx(Diagnostics::Context::ConstructMatcher, Error, + NameToken.Text, NameToken.Range); + + Error->addError(ChainCallToken.Range, + Error->ET_RegistryMatcherNoWithSupport); return false; } - if (BindToken.Kind != TokenInfo::TK_Ident || - BindToken.Text != TokenInfo::ID_Bind) { - Error->addError(BindToken.Range, Error->ET_ParserMalformedBindExpr); + if (ChainCallToken.Text != TokenInfo::ID_Bind) { + Error->addError(ChainCallToken.Range, + Error->ET_ParserMalformedChainedExpr); return false; } if (!parseBindID(BindID)) @@ -668,6 +854,21 @@ return Registry::getMatcherCompletions(AcceptedTypes); } +bool Parser::RegistrySema::isBuilderMatcher(MatcherCtor Ctor) const { + return Registry::isBuilderMatcher(Ctor); +} + +ASTNodeKind Parser::RegistrySema::nodeMatcherType(MatcherCtor Ctor) const { + return Registry::nodeMatcherType(Ctor); +} + +internal::MatcherDescriptorPtr +Parser::RegistrySema::buildMatcherCtor(MatcherCtor Ctor, SourceRange NameRange, + ArrayRef Args, + Diagnostics *Error) const { + return Registry::buildMatcherCtor(Ctor, NameRange, Args, Error); +} + bool Parser::parseExpression(StringRef &Code, Sema *S, const NamedValueMap *NamedValues, VariantValue *Value, Diagnostics *Error) { diff --git a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp --- a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -32,6 +32,17 @@ return M.getID().second; } + bool isBuilderMatcher(MatcherCtor) const override { return false; } + + ASTNodeKind nodeMatcherType(MatcherCtor) const override { return {}; } + + internal::MatcherDescriptorPtr + buildMatcherCtor(MatcherCtor, SourceRange NameRange, + ArrayRef Args, + Diagnostics *Error) const override { + return internal::MatcherDescriptorPtr{nullptr}; + } + void parse(StringRef Code) { Diagnostics Error; VariantValue Value; @@ -329,7 +340,7 @@ "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.", + EXPECT_EQ("1:11: Period not followed by valid chained call.", ParseWithError("isArrow().biind")); EXPECT_EQ("1:15: Malformed bind() expression.", ParseWithError("isArrow().bind")); @@ -340,6 +351,20 @@ EXPECT_EQ("1:1: Error building matcher isArrow.\n" "1:1: Matcher does not support binding.", ParseWithError("isArrow().bind(\"foo\")")); + EXPECT_EQ("1:1: Error building matcher isArrow.\n" + "1:11: Matcher does not support with call.", + ParseWithError("isArrow().with")); + EXPECT_EQ( + "1:22: Error parsing matcher. Found token while looking for '('.", + ParseWithError("mapAnyOf(ifStmt).with")); + EXPECT_EQ( + "1:22: Error parsing matcher. Found end-of-code while looking for ')'.", + ParseWithError("mapAnyOf(ifStmt).with(")); + EXPECT_EQ("1:1: Failed to build matcher: mapAnyOf.", + ParseWithError("mapAnyOf()")); + EXPECT_EQ("1:1: Error parsing argument 1 for matcher mapAnyOf.\n1:1: Failed " + "to build matcher: mapAnyOf.", + ParseWithError("mapAnyOf(\"foo\")")); EXPECT_EQ("Input value has unresolved overloaded type: " "Matcher", ParseMatcherWithError("hasBody(stmt())")); @@ -470,7 +495,8 @@ )matcher"; M = Parser::parseMatcherExpression(Code, nullptr, &NamedValues, &Error); EXPECT_FALSE(M.hasValue()); - EXPECT_EQ("1:11: Malformed bind() expression.", Error.toStringFull()); + EXPECT_EQ("1:11: Period not followed by valid chained call.", + Error.toStringFull()); } {