|  | //===- unittest/Tooling/StencilTest.cpp -----------------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/Tooling/Transformer/Stencil.h" | 
|  | #include "clang/AST/ASTTypeTraits.h" | 
|  | #include "clang/AST/Expr.h" | 
|  | #include "clang/ASTMatchers/ASTMatchers.h" | 
|  | #include "clang/Tooling/FixIt.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "llvm/Support/Error.h" | 
|  | #include "llvm/Testing/Support/Error.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <optional> | 
|  |  | 
|  | using namespace clang; | 
|  | using namespace transformer; | 
|  | using namespace ast_matchers; | 
|  |  | 
|  | namespace { | 
|  | using ::llvm::Failed; | 
|  | using ::llvm::HasValue; | 
|  | using ::llvm::StringError; | 
|  | using ::testing::AllOf; | 
|  | using ::testing::HasSubstr; | 
|  | using MatchResult = MatchFinder::MatchResult; | 
|  |  | 
|  | // Create a valid translation-unit from a statement. | 
|  | static std::string wrapSnippet(StringRef ExtraPreface, | 
|  | StringRef StatementCode) { | 
|  | constexpr char Preface[] = R"cc( | 
|  | namespace N { class C {}; } | 
|  | namespace { class AnonC {}; } | 
|  | struct S { int Field; }; | 
|  | namespace std { | 
|  | template <typename T> | 
|  | struct unique_ptr { | 
|  | T* operator->() const; | 
|  | T& operator*() const; | 
|  | }; | 
|  | } | 
|  | template<class T> T desugar() { return T(); }; | 
|  | )cc"; | 
|  | return (Preface + ExtraPreface + "auto stencil_test_snippet = []{" + | 
|  | StatementCode + "};") | 
|  | .str(); | 
|  | } | 
|  |  | 
|  | static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { | 
|  | return varDecl(hasName("stencil_test_snippet"), | 
|  | hasDescendant(compoundStmt(hasAnySubstatement(Matcher)))); | 
|  | } | 
|  |  | 
|  | struct TestMatch { | 
|  | // The AST unit from which `result` is built. We bundle it because it backs | 
|  | // the result. Users are not expected to access it. | 
|  | std::unique_ptr<ASTUnit> AstUnit; | 
|  | // The result to use in the test. References `ast_unit`. | 
|  | MatchResult Result; | 
|  | }; | 
|  |  | 
|  | // Matches `Matcher` against the statement `StatementCode` and returns the | 
|  | // result. Handles putting the statement inside a function and modifying the | 
|  | // matcher correspondingly. `Matcher` should match one of the statements in | 
|  | // `StatementCode` exactly -- that is, produce exactly one match. However, | 
|  | // `StatementCode` may contain other statements not described by `Matcher`. | 
|  | // `ExtraPreface` (optionally) adds extra decls to the TU, before the code. | 
|  | static std::optional<TestMatch> matchStmt(StringRef StatementCode, | 
|  | StatementMatcher Matcher, | 
|  | StringRef ExtraPreface = "") { | 
|  | auto AstUnit = tooling::buildASTFromCodeWithArgs( | 
|  | wrapSnippet(ExtraPreface, StatementCode), {"-Wno-unused-value"}); | 
|  | if (AstUnit == nullptr) { | 
|  | ADD_FAILURE() << "AST construction failed"; | 
|  | return std::nullopt; | 
|  | } | 
|  | ASTContext &Context = AstUnit->getASTContext(); | 
|  | auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); | 
|  | // We expect a single, exact match for the statement. | 
|  | if (Matches.size() != 1) { | 
|  | ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); | 
|  | return std::nullopt; | 
|  | } | 
|  | return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)}; | 
|  | } | 
|  |  | 
|  | class StencilTest : public ::testing::Test { | 
|  | protected: | 
|  | // Verifies that the given stencil fails when evaluated on a valid match | 
|  | // result. Binds a statement to "stmt", a (non-member) ctor-initializer to | 
|  | // "init", an expression to "expr" and a (nameless) declaration to "decl". | 
|  | void testError(const Stencil &Stencil, | 
|  | ::testing::Matcher<std::string> Matcher) { | 
|  | const std::string Snippet = R"cc( | 
|  | struct A {}; | 
|  | class F : public A { | 
|  | public: | 
|  | F(int) {} | 
|  | }; | 
|  | F(1); | 
|  | )cc"; | 
|  | auto StmtMatch = matchStmt( | 
|  | Snippet, | 
|  | stmt(hasDescendant( | 
|  | cxxConstructExpr( | 
|  | hasDeclaration(decl(hasDescendant(cxxCtorInitializer( | 
|  | isBaseInitializer()) | 
|  | .bind("init"))) | 
|  | .bind("decl"))) | 
|  | .bind("expr"))) | 
|  | .bind("stmt")); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | if (auto ResultOrErr = Stencil->eval(StmtMatch->Result)) { | 
|  | ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; | 
|  | } else { | 
|  | auto Err = llvm::handleErrors(ResultOrErr.takeError(), | 
|  | [&Matcher](const StringError &Err) { | 
|  | EXPECT_THAT(Err.getMessage(), Matcher); | 
|  | }); | 
|  | if (Err) { | 
|  | ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Tests failures caused by references to unbound nodes. `unbound_id` is the | 
|  | // id that will cause the failure. | 
|  | void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) { | 
|  | testError(Stencil, | 
|  | AllOf(HasSubstr(std::string(UnboundId)), HasSubstr("not bound"))); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(StencilTest, SingleStatement) { | 
|  | StringRef Condition("C"), Then("T"), Else("E"); | 
|  | const std::string Snippet = R"cc( | 
|  | if (true) | 
|  | return 1; | 
|  | else | 
|  | return 0; | 
|  | )cc"; | 
|  | auto StmtMatch = matchStmt( | 
|  | Snippet, ifStmt(hasCondition(expr().bind(Condition)), | 
|  | hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | // Invert the if-then-else. | 
|  | auto Stencil = | 
|  | cat("if (!", node(std::string(Condition)), ") ", | 
|  | statement(std::string(Else)), " else ", statement(std::string(Then))); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), | 
|  | HasValue("if (!true) return 0; else return 1;")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, UnboundNode) { | 
|  | const std::string Snippet = R"cc( | 
|  | if (true) | 
|  | return 1; | 
|  | else | 
|  | return 0; | 
|  | )cc"; | 
|  | auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")), | 
|  | hasThen(stmt().bind("a2")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";"); | 
|  | auto ResultOrErr = Stencil->eval(StmtMatch->Result); | 
|  | EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) | 
|  | << "Expected unbound node, got " << *ResultOrErr; | 
|  | } | 
|  |  | 
|  | // Tests that a stencil with a single parameter (`Id`) evaluates to the expected | 
|  | // string, when `Id` is bound to the expression-statement in `Snippet`. | 
|  | void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, | 
|  | StringRef Expected) { | 
|  | auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil, | 
|  | testing::Matcher<std::string> MessageMatcher) { | 
|  | auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), | 
|  | Failed<StringError>(testing::Property( | 
|  | &StringError::getMessage, MessageMatcher))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, SelectionOp) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "3;", cat(node(std::string(Id))), "3"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, IfBoundOpBound) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "3;", ifBound(Id, cat("5"), cat("7")), "5"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, IfBoundOpUnbound) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "3;", ifBound("other", cat("5"), cat("7")), "7"); | 
|  | } | 
|  |  | 
|  | static auto selectMatcher() { | 
|  | // The `anything` matcher is not bound, to test for none of the cases | 
|  | // matching. | 
|  | return expr(anyOf(integerLiteral().bind("int"), cxxBoolLiteral().bind("bool"), | 
|  | floatLiteral().bind("float"), anything())); | 
|  | } | 
|  |  | 
|  | static auto selectStencil() { | 
|  | return selectBound({ | 
|  | {"int", cat("I")}, | 
|  | {"bool", cat("B")}, | 
|  | {"bool", cat("redundant")}, | 
|  | {"float", cat("F")}, | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, SelectBoundChooseDetectedMatch) { | 
|  | std::string Input = "3;"; | 
|  | auto StmtMatch = matchStmt(Input, selectMatcher()); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result), | 
|  | HasValue(std::string("I"))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, SelectBoundChooseFirst) { | 
|  | std::string Input = "true;"; | 
|  | auto StmtMatch = matchStmt(Input, selectMatcher()); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result), | 
|  | HasValue(std::string("B"))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, SelectBoundDiesOnExhaustedCases) { | 
|  | std::string Input = "\"string\";"; | 
|  | auto StmtMatch = matchStmt(Input, selectMatcher()); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED( | 
|  | selectStencil()->eval(StmtMatch->Result), | 
|  | Failed<StringError>(testing::Property( | 
|  | &StringError::getMessage, | 
|  | AllOf(HasSubstr("selectBound failed"), HasSubstr("no default"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, SelectBoundSucceedsWithDefault) { | 
|  | std::string Input = "\"string\";"; | 
|  | auto StmtMatch = matchStmt(Input, selectMatcher()); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | auto Stencil = selectBound({{"int", cat("I")}}, cat("D")); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), | 
|  | HasValue(std::string("D"))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, ExpressionOpNoParens) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "3;", expression(Id), "3"); | 
|  | } | 
|  |  | 
|  | // Don't parenthesize a parens expression. | 
|  | TEST_F(StencilTest, ExpressionOpNoParensParens) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "(3);", expression(Id), "(3)"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, ExpressionOpBinaryOpParens) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "3+4;", expression(Id), "(3+4)"); | 
|  | } | 
|  |  | 
|  | // `expression` shares code with other ops, so we get sufficient coverage of the | 
|  | // error handling code with this test. If that changes in the future, more error | 
|  | // tests should be added. | 
|  | TEST_F(StencilTest, ExpressionOpUnbound) { | 
|  | StringRef Id = "id"; | 
|  | testFailure(Id, "3;", expression("ACACA"), | 
|  | AllOf(HasSubstr("ACACA"), HasSubstr("not bound"))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DerefPointer) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; x;", deref(Id), "*x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DerefBinOp) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; x + 1;", deref(Id), "*(x + 1)"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DerefAddressExpr) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; &x;", deref(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AddressOfValue) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; x;", addressOf(Id), "&x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AddressOfDerefExpr) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; *x;", addressOf(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefValue) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; x;", maybeDeref(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefPointer) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; x;", maybeDeref(Id), "*x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefBinOp) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; x + 1;", maybeDeref(Id), "*(x + 1)"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefAddressExpr) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; &x;", maybeDeref(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefSmartPointer) { | 
|  | StringRef Id = "id"; | 
|  | std::string Snippet = R"cc( | 
|  | std::unique_ptr<S> x; | 
|  | x; | 
|  | )cc"; | 
|  | testExpr(Id, Snippet, maybeDeref(Id), "*x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeDerefSmartPointerFromMemberExpr) { | 
|  | StringRef Id = "id"; | 
|  | std::string Snippet = "std::unique_ptr<S> x; x->Field;"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind(Id)))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | const Stencil Stencil = maybeDeref(Id); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("*x")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfPointer) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; x;", maybeAddressOf(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfValue) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; x;", addressOf(Id), "&x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfBinOp) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int x; x + 1;", maybeAddressOf(Id), "&(x + 1)"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfDerefExpr) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "int *x; *x;", addressOf(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfSmartPointer) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "std::unique_ptr<S> x; x;", maybeAddressOf(Id), "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfSmartPointerFromMemberCall) { | 
|  | StringRef Id = "id"; | 
|  | std::string Snippet = "std::unique_ptr<S> x; x->Field;"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind(Id)))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | const Stencil Stencil = maybeAddressOf(Id); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("x")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, MaybeAddressOfSmartPointerDerefNoCancel) { | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, "std::unique_ptr<S> x; *x;", maybeAddressOf(Id), "&*x"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpValue) { | 
|  | StringRef Snippet = R"cc( | 
|  | S x; | 
|  | x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x.field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpValueExplicitText) { | 
|  | StringRef Snippet = R"cc( | 
|  | S x; | 
|  | x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, cat("field")), "x.field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpValueAddress) { | 
|  | StringRef Snippet = R"cc( | 
|  | S x; | 
|  | &x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x.field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpPointer) { | 
|  | StringRef Snippet = R"cc( | 
|  | S *x; | 
|  | x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x->field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpPointerDereference) { | 
|  | StringRef Snippet = R"cc( | 
|  | S *x; | 
|  | *x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x->field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpSmartPointer) { | 
|  | StringRef Snippet = R"cc( | 
|  | std::unique_ptr<S> x; | 
|  | x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x->field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpSmartPointerDereference) { | 
|  | StringRef Snippet = R"cc( | 
|  | std::unique_ptr<S> x; | 
|  | *x; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | testExpr(Id, Snippet, access(Id, "field"), "x->field"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpSmartPointerMemberCall) { | 
|  | StringRef Snippet = R"cc( | 
|  | std::unique_ptr<S> x; | 
|  | x->Field; | 
|  | )cc"; | 
|  | StringRef Id = "id"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind(Id)))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(access(Id, "field")->eval(StmtMatch->Result), | 
|  | HasValue("x->field")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpExplicitThis) { | 
|  | using clang::ast_matchers::hasObjectExpression; | 
|  | using clang::ast_matchers::memberExpr; | 
|  |  | 
|  | // Set up the code so we can bind to a use of this. | 
|  | StringRef Snippet = R"cc( | 
|  | class C { | 
|  | public: | 
|  | int x; | 
|  | int foo() { return this->x; } | 
|  | }; | 
|  | )cc"; | 
|  | auto StmtMatch = matchStmt( | 
|  | Snippet, | 
|  | traverse(TK_AsIs, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( | 
|  | hasObjectExpression(expr().bind("obj")))))))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | const Stencil Stencil = access("obj", "field"); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), | 
|  | HasValue("this->field")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, AccessOpImplicitThis) { | 
|  | using clang::ast_matchers::hasObjectExpression; | 
|  | using clang::ast_matchers::memberExpr; | 
|  |  | 
|  | // Set up the code so we can bind to a use of (implicit) this. | 
|  | StringRef Snippet = R"cc( | 
|  | class C { | 
|  | public: | 
|  | int x; | 
|  | int foo() { return x; } | 
|  | }; | 
|  | )cc"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( | 
|  | hasObjectExpression(expr().bind("obj"))))))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | const Stencil Stencil = access("obj", "field"); | 
|  | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeType) { | 
|  | std::string Snippet = "int *x; x;"; | 
|  | std::string Expected = "int *"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeSugaredType) { | 
|  | std::string Snippet = "using Ty = int; Ty *x; x;"; | 
|  | std::string Expected = "Ty *"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeDeclType) { | 
|  | std::string Snippet = "S s; s;"; | 
|  | std::string Expected = "S"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeQualifiedType) { | 
|  | std::string Snippet = "N::C c; c;"; | 
|  | std::string Expected = "N::C"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeUnqualifiedType) { | 
|  | std::string Snippet = "using N::C; C c; c;"; | 
|  | std::string Expected = "C"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeAnonNamespaceType) { | 
|  | std::string Snippet = "auto c = desugar<AnonC>(); c;"; | 
|  | std::string Expected = "(anonymous namespace)::AnonC"; | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeFunction) { | 
|  | std::string Snippet = "int F(); F();"; | 
|  | std::string Expected = "F"; | 
|  | auto StmtMatch = matchStmt(Snippet, callExpr(callee(namedDecl().bind("fn")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("fn")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, DescribeImplicitOperator) { | 
|  | std::string Snippet = "struct Tag {}; [](Tag){};"; | 
|  | std::string Expected = "operator()"; | 
|  | auto StmtMatch = matchStmt( | 
|  | Snippet, | 
|  | stmt(hasDescendant( | 
|  | cxxMethodDecl(hasParameter(0, hasType(namedDecl(hasName("Tag"))))) | 
|  | .bind("fn")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | EXPECT_THAT_EXPECTED(describe("fn")->eval(StmtMatch->Result), | 
|  | HasValue(std::string(Expected))); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, RunOp) { | 
|  | StringRef Id = "id"; | 
|  | auto SimpleFn = [Id](const MatchResult &R) { | 
|  | return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound" | 
|  | : "Unbound"); | 
|  | }; | 
|  | testExpr(Id, "3;", run(SimpleFn), "Bound"); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, CatOfMacroRangeSucceeds) { | 
|  | StringRef Snippet = R"cpp( | 
|  | #define MACRO 3.77 | 
|  | double foo(double d); | 
|  | foo(MACRO);)cpp"; | 
|  |  | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))), | 
|  | argumentCountIs(1), | 
|  | hasArgument(0, expr().bind("arg")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | Stencil S = cat(node("arg")); | 
|  | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("MACRO")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, CatOfMacroArgRangeSucceeds) { | 
|  | StringRef Snippet = R"cpp( | 
|  | #define MACRO(a, b) a + b | 
|  | MACRO(2, 3);)cpp"; | 
|  |  | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, binaryOperator(hasRHS(expr().bind("rhs")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | Stencil S = cat(node("rhs")); | 
|  | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, CatOfMacroArgSubRangeSucceeds) { | 
|  | StringRef Snippet = R"cpp( | 
|  | #define MACRO(a, b) a + b | 
|  | int foo(int); | 
|  | MACRO(2, foo(3));)cpp"; | 
|  |  | 
|  | auto StmtMatch = matchStmt( | 
|  | Snippet, binaryOperator(hasRHS(callExpr( | 
|  | callee(functionDecl(hasName("foo"))), argumentCountIs(1), | 
|  | hasArgument(0, expr().bind("arg")))))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | Stencil S = cat(node("arg")); | 
|  | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3")); | 
|  | } | 
|  |  | 
|  | TEST_F(StencilTest, CatOfInvalidRangeFails) { | 
|  | StringRef Snippet = R"cpp( | 
|  | #define MACRO (3.77) | 
|  | double foo(double d); | 
|  | foo(MACRO);)cpp"; | 
|  |  | 
|  | auto StmtMatch = | 
|  | matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))), | 
|  | argumentCountIs(1), | 
|  | hasArgument(0, expr().bind("arg")))); | 
|  | ASSERT_TRUE(StmtMatch); | 
|  | Stencil S = cat(node("arg")); | 
|  | Expected<std::string> Result = S->eval(StmtMatch->Result); | 
|  | ASSERT_FALSE(Result); | 
|  | llvm::handleAllErrors(Result.takeError(), [](const llvm::StringError &E) { | 
|  | EXPECT_THAT(E.getMessage(), AllOf(HasSubstr("selected range"), | 
|  | HasSubstr("macro expansion"))); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // The `StencilToStringTest` tests verify that the string representation of the | 
|  | // stencil combinator matches (as best possible) the spelling of the | 
|  | // combinator's construction.  Exceptions include those combinators that have no | 
|  | // explicit spelling (like raw text) and those supporting non-printable | 
|  | // arguments (like `run`, `selection`). | 
|  |  | 
|  | TEST(StencilToStringTest, RawTextOp) { | 
|  | auto S = cat("foo bar baz"); | 
|  | StringRef Expected = R"("foo bar baz")"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, RawTextOpEscaping) { | 
|  | auto S = cat("foo \"bar\" baz\\n"); | 
|  | StringRef Expected = R"("foo \"bar\" baz\\n")"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, DescribeOp) { | 
|  | auto S = describe("Id"); | 
|  | StringRef Expected = R"repr(describe("Id"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, DebugPrintNodeOp) { | 
|  | auto S = dPrint("Id"); | 
|  | StringRef Expected = R"repr(dPrint("Id"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, ExpressionOp) { | 
|  | auto S = expression("Id"); | 
|  | StringRef Expected = R"repr(expression("Id"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, DerefOp) { | 
|  | auto S = deref("Id"); | 
|  | StringRef Expected = R"repr(deref("Id"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, AddressOfOp) { | 
|  | auto S = addressOf("Id"); | 
|  | StringRef Expected = R"repr(addressOf("Id"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SelectionOp) { | 
|  | auto S1 = cat(node("node1")); | 
|  | EXPECT_EQ(S1->toString(), "selection(...)"); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, AccessOpText) { | 
|  | auto S = access("Id", "memberData"); | 
|  | StringRef Expected = R"repr(access("Id", "memberData"))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, AccessOpSelector) { | 
|  | auto S = access("Id", cat(name("otherId"))); | 
|  | StringRef Expected = R"repr(access("Id", selection(...)))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, AccessOpStencil) { | 
|  | auto S = access("Id", cat("foo_", "bar")); | 
|  | StringRef Expected = R"repr(access("Id", seq("foo_", "bar")))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, IfBoundOp) { | 
|  | auto S = ifBound("Id", cat("trueText"), access("exprId", "memberData")); | 
|  | StringRef Expected = | 
|  | R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SelectBoundOp) { | 
|  | auto S = selectBound({ | 
|  | {"int", cat("I")}, | 
|  | {"float", cat("F")}, | 
|  | }); | 
|  | StringRef Expected = R"repr(selectBound({{"int", "I"}, {"float", "F"}}))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SelectBoundOpWithOneCase) { | 
|  | auto S = selectBound({{"int", cat("I")}}); | 
|  | StringRef Expected = R"repr(selectBound({{"int", "I"}}))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SelectBoundOpWithDefault) { | 
|  | auto S = selectBound({{"int", cat("I")}, {"float", cat("F")}}, cat("D")); | 
|  | StringRef Expected = | 
|  | R"cc(selectBound({{"int", "I"}, {"float", "F"}}, "D"))cc"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, RunOp) { | 
|  | auto F1 = [](const MatchResult &R) { return "foo"; }; | 
|  | auto S1 = run(F1); | 
|  | EXPECT_EQ(S1->toString(), "run(...)"); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, Sequence) { | 
|  | auto S = cat("foo", access("x", "m()"), "bar", | 
|  | ifBound("x", cat("t"), access("e", "f"))); | 
|  | StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" | 
|  | R"repr(ifBound("x", "t", access("e", "f"))))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SequenceEmpty) { | 
|  | auto S = cat(); | 
|  | StringRef Expected = "seq()"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SequenceSingle) { | 
|  | auto S = cat("foo"); | 
|  | StringRef Expected = "\"foo\""; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  |  | 
|  | TEST(StencilToStringTest, SequenceFromVector) { | 
|  | auto S = catVector({cat("foo"), access("x", "m()"), cat("bar"), | 
|  | ifBound("x", cat("t"), access("e", "f"))}); | 
|  | StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" | 
|  | R"repr(ifBound("x", "t", access("e", "f"))))repr"; | 
|  | EXPECT_EQ(S->toString(), Expected); | 
|  | } | 
|  | } // namespace |