ymandel created this revision.
Herald added subscribers: cfe-commits, jfb.

REQUEST FOR COMMENT: this is not intended (yet) as a proper revision. It 
complements the design document for Transformer:
https://docs.google.com/document/d/1ppw0RhjwsrbBcHYhI85pe6ISDbA6r5d00ot3N8cQWeQ/edit?usp=sharing

This revision introduces the Stencil library, which is designed to complement 
the AST matchers in support of writing source-to-source transformations. A 
stencil is similar in spirit to a format string: it is composed of a series of 
raw text strings, references to node ids (bound in a corresponding matcher) and 
helper code-generation operations.  A stencil can be applied to a match result 
and uses that result to find all nodes referenced in the stencil and then 
produce a source string.

Stencil is part of the Transformer framework, but can also be used on its own, 
for example in a ClangTidy.  Please see the Transformer doc and the 
accompanying tests for example uses.


Repository:
  rC Clang

https://reviews.llvm.org/D56933

Files:
  clang/include/clang/Tooling/Refactoring/Stencil.h
  clang/lib/Tooling/Refactoring/Stencil.cpp
  clang/unittests/Tooling/StencilTest.cpp

Index: clang/unittests/Tooling/StencilTest.cpp
===================================================================
--- /dev/null
+++ clang/unittests/Tooling/StencilTest.cpp
@@ -0,0 +1,624 @@
+#include "clang/Tooling/Refactoring/Stencil.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/FixIt.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tooling {
+namespace {
+
+using ::clang::ast_matchers::compoundStmt;
+using ::clang::ast_matchers::decl;
+using ::clang::ast_matchers::declStmt;
+using ::clang::ast_matchers::expr;
+using ::clang::ast_matchers::hasAnySubstatement;
+using ::clang::ast_matchers::hasCondition;
+using ::clang::ast_matchers::hasDescendant;
+using ::clang::ast_matchers::hasElse;
+using ::clang::ast_matchers::hasInitializer;
+using ::clang::ast_matchers::hasName;
+using ::clang::ast_matchers::hasReturnValue;
+using ::clang::ast_matchers::hasSingleDecl;
+using ::clang::ast_matchers::hasThen;
+using ::clang::ast_matchers::ifStmt;
+using ::clang::ast_matchers::ignoringImplicit;
+using ::clang::ast_matchers::returnStmt;
+using ::clang::ast_matchers::stmt;
+using ::clang::ast_matchers::varDecl;
+
+using MatchResult = ::clang::ast_matchers::MatchFinder::MatchResult;
+
+using ::clang::tooling::stencil_generators::addInclude;
+using ::clang::tooling::stencil_generators::apply;
+using ::clang::tooling::stencil_generators::args;
+using ::clang::tooling::stencil_generators::asAddress;
+using ::clang::tooling::stencil_generators::asValue;
+using ::clang::tooling::stencil_generators::member;
+using ::clang::tooling::stencil_generators::name;
+using ::clang::tooling::stencil_generators::parens;
+using ::clang::tooling::stencil_generators::removeInclude;
+using ::clang::tooling::stencil_generators::text;
+
+using ::testing::Eq;
+
+// We can't directly match on llvm::Expected since its accessors mutate the
+// object. So, we collapse it to an Optional.
+llvm::Optional<std::string> toOptional(llvm::Expected<std::string> V) {
+  if (V)
+    return *V;
+  ADD_FAILURE() << "Losing error in conversion to IsSomething: "
+                << llvm::toString(V.takeError());
+  return llvm::None;
+}
+
+// A very simple matcher for llvm::Optional values.
+MATCHER_P(IsSomething, ValueMatcher, "") {
+  if (!arg)
+    return false;
+  return ::testing::ExplainMatchResult(ValueMatcher, *arg, result_listener);
+}
+
+// Create a valid translation-unit from a statement.
+std::string wrapSnippet(llvm::StringRef StatementCode) {
+  return ("auto stencil_test_snippet = []{" + StatementCode + "};").str();
+}
+
+clang::ast_matchers::DeclarationMatcher
+wrapMatcher(const clang::ast_matchers::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<clang::ASTUnit> AstUnit;
+  // The result to use in the test. References `ast_unit`.
+  MatchResult Result;
+};
+
+// Matches `matcher` against the statement `statement_code` and returns the
+// result. Handles putting the statement inside a function and modifying the
+// matcher correspondingly. `matcher` should match `statement_code` exactly --
+// that is, produce exactly one match.
+llvm::Optional<TestMatch>
+matchStmt(llvm::StringRef StatementCode,
+          clang::ast_matchers::StatementMatcher Matcher) {
+  auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode));
+  if (AstUnit == nullptr) {
+    ADD_FAILURE() << "AST construction failed";
+    return llvm::None;
+  }
+  clang::ASTContext &Context = AstUnit->getASTContext();
+  auto Matches = clang::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 llvm::None;
+  }
+  return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
+}
+
+class StencilTest : public ::testing::Test {
+public:
+  StencilTest() : Id0("id0"), Id1("id1") {}
+
+protected:
+  // Verifies that filling a single-parameter stencil from `context` will result
+  // in `expected`, assuming that the code in `context` contains a statement
+  // `return e` and "id0" is bound to `e`.
+  void testSingle(llvm::StringRef Snippet, const Stencil &Stencil,
+                  llvm::StringRef Expected) {
+    auto StmtMatch = matchStmt(
+        Snippet,
+        returnStmt(hasReturnValue(ignoringImplicit(expr().bind(Id0.id())))));
+    ASSERT_TRUE(StmtMatch);
+    EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+                IsSomething(Expected));
+    EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+    EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+  }
+
+  // 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) {
+    using ::clang::ast_matchers::cxxConstructExpr;
+    using ::clang::ast_matchers::cxxCtorInitializer;
+    using ::clang::ast_matchers::hasDeclaration;
+    using ::clang::ast_matchers::isBaseInitializer;
+
+    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 llvm::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, llvm::StringRef UnboundId) {
+    testError(Stencil, testing::AllOf(testing::HasSubstr(UnboundId),
+                                      testing::HasSubstr("not bound")));
+  }
+
+  NodeId Id0;
+  NodeId Id1;
+};
+
+TEST_F(StencilTest, SingleStatement) {
+  using stencil_generators::id;
+
+  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")),
+                                             hasElse(stmt().bind("a3"))));
+  ASSERT_TRUE(StmtMatch);
+  auto Stencil =
+      Stencil::cat("if(!", id("a1"), ") ", id("a3"), "; else ", id("a2"));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("if(!true) return 0; else return 1")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, UnboundNode) {
+  using stencil_generators::id;
+
+  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 = Stencil::cat("if(!", id("a1"), ") ", id("UNBOUND"), ";");
+  auto ResultOrErr = Stencil.eval(StmtMatch->Result);
+  EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError()))
+      << "Expected unbound node, got " << *ResultOrErr;
+}
+
+TEST_F(StencilTest, NodeOp) {
+  using stencil_generators::node;
+
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(node(Id0)), "x");
+  testSingle(Snippet, Stencil::cat(node("id0")), "x");
+}
+
+TEST_F(StencilTest, MemberOpValue) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(member(Id0, "field")), "x.field");
+}
+
+TEST_F(StencilTest, MemberOpValueExplicitText) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(member(Id0, text("field"))), "x.field");
+}
+
+TEST_F(StencilTest, MemberOpValueAddress) {
+  const std::string Snippet = R"cc(
+    int x;
+    return &x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(member(Id0, "field")), "x.field");
+}
+
+TEST_F(StencilTest, MemberOpPointer) {
+  const std::string Snippet = R"cc(
+    int *x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(member(Id0, "field")), "x->field");
+}
+
+TEST_F(StencilTest, MemberOpPointerDereference) {
+  const std::string Snippet = R"cc(
+    int *x;
+    return *x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(member(Id0, "field")), "x->field");
+}
+
+TEST_F(StencilTest, MemberOpThis) {
+  using clang::ast_matchers::hasObjectExpression;
+  using clang::ast_matchers::memberExpr;
+
+  const std::string 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 = Stencil::cat(member("obj", "field"));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("field")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, MemberOpUnboundNode) {
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(member("decl", "field")), "decl");
+  testUnboundNodeError(Stencil::cat(member("unbound", "field")), "unbound");
+}
+
+TEST_F(StencilTest, ValueOpValue) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(asValue(Id0)), "x");
+}
+
+TEST_F(StencilTest, ValueOpPointer) {
+  const std::string Snippet = R"cc(
+    int *x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(asValue(Id0)), "*x");
+}
+
+TEST_F(StencilTest, ValueOpUnboundNode) {
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(asValue("decl")), "decl");
+  testUnboundNodeError(Stencil::cat(asValue("unbound")), "unbound");
+}
+
+TEST_F(StencilTest, AddressOpValue) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(asAddress(Id0)), "&x");
+}
+
+TEST_F(StencilTest, AddressOpPointer) {
+  const std::string Snippet = R"cc(
+    int *x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(asAddress(Id0)), "x");
+}
+
+TEST_F(StencilTest, AddressOpUnboundNode) {
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(asAddress("decl")), "decl");
+  testUnboundNodeError(Stencil::cat(asAddress("unbound")), "unbound");
+}
+
+TEST_F(StencilTest, ParensOpVar) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "x");
+}
+
+TEST_F(StencilTest, ParensOpMinus) {
+  const std::string Snippet = R"cc(
+    int x;
+    return -x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "(-x)");
+}
+
+TEST_F(StencilTest, ParensOpDeref) {
+  const std::string Snippet = R"cc(
+    int *x;
+    return *x;
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "(*x)");
+}
+
+TEST_F(StencilTest, ParensOpExpr) {
+  const std::string Snippet = R"cc(
+    int x;
+    int y;
+    return x + y;
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "(x + y)");
+}
+
+// Tests that parens are not added when the expression already has them.
+TEST_F(StencilTest, ParensOpParens) {
+  const std::string Snippet = R"cc(
+    int x;
+    int y;
+    return (x + y);
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "(x + y)");
+}
+
+TEST_F(StencilTest, ParensOpFun) {
+  const std::string Snippet = R"cc(
+    int bar(int);
+    int x;
+    int y;
+    return bar(x);
+  )cc";
+  testSingle(Snippet, Stencil::cat(parens(Id0)), "bar(x)");
+}
+
+TEST_F(StencilTest, ParensOpUnboundNode) {
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(parens("decl")), "decl");
+  testUnboundNodeError(Stencil::cat(parens("unbound")), "unbound");
+}
+
+TEST_F(StencilTest, NameOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  auto StmtMatch =
+      matchStmt(Snippet, declStmt(hasSingleDecl(decl().bind("d"))));
+  ASSERT_TRUE(StmtMatch);
+  const Stencil Stencil = Stencil::cat(name("d"));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("x")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, NameOpCtorInitializer) {
+  using clang::ast_matchers::cxxCtorInitializer;
+
+  const std::string Snippet = R"cc(
+    class C {
+     public:
+      C() : field(3) {}
+      int field;
+      int foo() { return field; }
+    };
+  )cc";
+  auto StmtMatch = matchStmt(
+      Snippet, stmt(hasDescendant(cxxCtorInitializer().bind("init"))));
+  ASSERT_TRUE(StmtMatch);
+  const Stencil Stencil = Stencil::cat(name("init"));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("field")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, NameOpUnboundNode) {
+  // Decl has no name.
+  testError(Stencil::cat(name("decl")), testing::HasSubstr("not identifier"));
+  // Non-member (hence, no name) initializer.
+  testError(Stencil::cat(name("init")),
+            testing::HasSubstr("non-member initializer"));
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(name("expr")), "expr");
+  testUnboundNodeError(Stencil::cat(name("unbound")), "unbound");
+}
+
+TEST_F(StencilTest, ArgsOp) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar(int, int);
+    };
+    C x;
+    return x.bar(3, 4);
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "3, 4");
+}
+
+TEST_F(StencilTest, ArgsOpNoArgs) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar();
+    };
+    C x;
+    return x.bar();
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "");
+}
+
+TEST_F(StencilTest, ArgsOpNoArgsWithComments) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar();
+    };
+    C x;
+    return x.bar(/*empty*/);
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "/*empty*/");
+}
+
+// Tests that arguments are extracted correctly when a temporary (with parens)
+// is used.
+TEST_F(StencilTest, ArgsOpWithParens) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    C x;
+    return C().bar(3, 4);
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "3, 4");
+}
+
+TEST_F(StencilTest, ArgsOpLeadingComments) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    C x;
+    return C().bar(/*leading*/ 3, 4);
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "/*leading*/ 3, 4");
+}
+
+TEST_F(StencilTest, ArgsOpTrailingComments) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    C x;
+    return C().bar(3 /*trailing*/, 4);
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), "3 /*trailing*/, 4");
+}
+
+TEST_F(StencilTest, ArgsOpEolComments) {
+  const std::string Snippet = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    C x;
+    return C().bar(  // Header
+        1,           // foo
+        2            // bar
+    );
+  )cc";
+  testSingle(Snippet, Stencil::cat(args(Id0)), R"(  // Header
+        1,           // foo
+        2            // bar
+    )");
+}
+
+TEST_F(StencilTest, ArgsOpUnboundNode) {
+  // Mistyped.
+  testUnboundNodeError(Stencil::cat(args("stmt")), "stmt");
+  testUnboundNodeError(Stencil::cat(args("unbound")), "unbound");
+}
+
+TEST_F(StencilTest, MemberOpWithNameOp) {
+  const std::string Snippet = R"cc(
+    int object;
+    int* method = &object;
+    (void)method;
+    return object;
+  )cc";
+  auto StmtMatch = matchStmt(
+      Snippet, declStmt(hasSingleDecl(
+                   varDecl(hasInitializer(expr().bind("e"))).bind("d"))));
+  ASSERT_TRUE(StmtMatch);
+  const Stencil Stencil = Stencil::cat(member("e", name("d")));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("object.method")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, NodeFunctionOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  auto SimpleFn = [](const ast_type_traits::DynTypedNode &Node,
+                     const ASTContext &Context) {
+    return fixit::getText(Node, Context).str();
+  };
+  testSingle(Snippet, Stencil::cat(apply(SimpleFn, Id0)), "x");
+  testSingle(Snippet, Stencil::cat(apply(SimpleFn, "id0")), "x");
+}
+
+TEST_F(StencilTest, StringFunctionOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  auto SimpleFn = [](llvm::StringRef S) { return (S + " - 3").str(); };
+  testSingle(Snippet, Stencil::cat(apply(SimpleFn, Id0)), "x - 3");
+  testSingle(Snippet, Stencil::cat(apply(SimpleFn, "id0")), "x - 3");
+}
+
+TEST_F(StencilTest, StringFunctionOpNameOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return x;
+  )cc";
+  auto SimpleFn = [](llvm::StringRef S) { return (S + " - 3").str(); };
+  auto StmtMatch =
+      matchStmt(Snippet, declStmt(hasSingleDecl(decl().bind("d"))));
+  ASSERT_TRUE(StmtMatch);
+  const Stencil Stencil = Stencil::cat(apply(SimpleFn, name("d")));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("x - 3")));
+}
+
+TEST_F(StencilTest, AddIncludeOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return -x;
+  )cc";
+  auto StmtMatch = matchStmt(Snippet, stmt());
+  ASSERT_TRUE(StmtMatch);
+  auto Stencil = Stencil::cat(addInclude("include/me.h"), "foo",
+                              addInclude("include/metoo.h"));
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("foo")));
+  EXPECT_THAT(Stencil.addedIncludes(),
+              testing::UnorderedElementsAre("include/me.h", "include/metoo.h"));
+  EXPECT_THAT(Stencil.removedIncludes(), testing::IsEmpty());
+}
+
+TEST_F(StencilTest, RemoveIncludeOp) {
+  const std::string Snippet = R"cc(
+    int x;
+    return -x;
+  )cc";
+  auto StmtMatch = matchStmt(Snippet, stmt());
+  ASSERT_TRUE(StmtMatch);
+  auto Stencil = Stencil::cat(removeInclude("include/me.h"), "foo");
+  EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+              IsSomething(Eq("foo")));
+  EXPECT_THAT(Stencil.addedIncludes(), testing::IsEmpty());
+  EXPECT_THAT(Stencil.removedIncludes(),
+              testing::UnorderedElementsAre("include/me.h"));
+}
+
+} // namespace
+} // namespace tooling
+} // namespace clang
Index: clang/lib/Tooling/Refactoring/Stencil.cpp
===================================================================
--- /dev/null
+++ clang/lib/Tooling/Refactoring/Stencil.cpp
@@ -0,0 +1,639 @@
+#include "clang/Tooling/Refactoring/Stencil.h"
+
+#include <atomic>
+#include <string>
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Expr.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/Support/Errc.h"
+
+namespace clang {
+namespace tooling {
+
+//
+// BEGIN Utilities -- the folowing functions all belong in a separate utilities
+// library.  We include them here for the purposes of this demo so that it will
+// compile
+//
+
+// Returns true if expr needs to be put in parens when it is the target
+// of a dot or arrow, i.e. when it is an operator syntactically.
+static bool needParensBeforeDotOrArrow(const clang::Expr &Expr) {
+  // We always want parens around unary, binary, and ternary operators.
+  if (llvm::dyn_cast<clang::UnaryOperator>(&Expr) ||
+      llvm::dyn_cast<clang::BinaryOperator>(&Expr) ||
+      llvm::dyn_cast<clang::ConditionalOperator>(&Expr)) {
+    return true;
+  }
+
+  // We need parens around calls to all overloaded operators except for function
+  // calls, subscripts, and expressions that are already part of an implicit
+  // call to operator->.
+  if (const auto *Op = llvm::dyn_cast<clang::CXXOperatorCallExpr>(&Expr)) {
+    return Op->getOperator() != clang::OO_Call &&
+           Op->getOperator() != clang::OO_Subscript &&
+           Op->getOperator() != clang::OO_Arrow;
+  }
+
+  return false;
+}
+
+// BEGIN from clang-tidy/readability/RedundantStringCStrCheck.cpp
+
+// Return true if expr needs to be put in parens when it is an argument of a
+// prefix unary operator, e.g. when it is a binary or ternary operator
+// syntactically.
+static bool needParensAfterUnaryOperator(const Expr &ExprNode) {
+  if (isa<clang::BinaryOperator>(&ExprNode) ||
+      isa<clang::ConditionalOperator>(&ExprNode)) {
+    return true;
+  }
+  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) {
+    return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
+           Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
+           Op->getOperator() != OO_Subscript;
+  }
+  return false;
+}
+
+// Format a pointer to an expression: prefix with '*' but simplify
+// when it already begins with '&'.  Return empty string on failure.
+static std::string formatDereference(const ASTContext &Context,
+                                     const Expr &ExprNode) {
+  if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) {
+    if (Op->getOpcode() == UO_AddrOf) {
+      // Strip leading '&'.
+      return tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(),
+                                     Context);
+    }
+  }
+  StringRef Text = tooling::fixit::getText(ExprNode, Context);
+
+  if (Text.empty())
+    return std::string();
+  // Add leading '*'.
+  if (needParensAfterUnaryOperator(ExprNode)) {
+    return (llvm::Twine("*(") + Text + ")").str();
+  }
+  return (llvm::Twine("*") + Text).str();
+}
+
+// END from clang-tidy/readability/RedundantStringCStrCheck.cpp
+
+// Format a pointer to an expression: prefix with '&' but simplify when it
+// already begins with '*'.  Returns empty string on failure.
+static std::string formatAddressOf(const ASTContext &Context,
+                            const clang::Expr &Expr) {
+  if (const auto *Op = llvm::dyn_cast<clang::UnaryOperator>(&Expr)) {
+    if (Op->getOpcode() == clang::UO_Deref) {
+      // Strip leading '*'.
+      return tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(),
+                                     Context);
+    }
+  }
+  // Add leading '&'.
+  const std::string Text = fixit::getText(Expr, Context);
+  if (Text.empty())
+    return std::string();
+  if (needParensAfterUnaryOperator(Expr)) {
+    return (llvm::Twine("&(") + Text + ")").str();
+  }
+  return (llvm::Twine("&") + Text).str();
+}
+
+static std::string formatDot(const ASTContext &Context,
+                             const clang::Expr &Expr) {
+  if (const auto *Op = llvm::dyn_cast<clang::UnaryOperator>(&Expr)) {
+    if (Op->getOpcode() == clang::UO_Deref) {
+      // Strip leading '*', add following '->'.
+      const clang::Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
+      const std::string DerefText = fixit::getText(*SubExpr, Context);
+      if (DerefText.empty())
+        return std::string();
+      if (needParensBeforeDotOrArrow(*SubExpr)) {
+        return (llvm::Twine("(") + DerefText + ")->").str();
+      }
+      return (llvm::Twine(DerefText) + "->").str();
+    }
+  }
+  // Add following '.'.
+  const std::string Text = fixit::getText(Expr, Context);
+  if (Text.empty())
+    return std::string();
+  if (needParensBeforeDotOrArrow(Expr)) {
+    return (llvm::Twine("(") + Text + ").").str();
+  }
+  return (llvm::Twine(Text) + ".").str();
+}
+
+static std::string formatArrow(const ASTContext &Context,
+                               const clang::Expr &Expr) {
+  if (const auto *Op = llvm::dyn_cast<clang::UnaryOperator>(&Expr)) {
+    if (Op->getOpcode() == clang::UO_AddrOf) {
+      // Strip leading '&', add following '.'.
+      const clang::Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
+      const std::string DerefText = fixit::getText(*SubExpr, Context);
+      if (DerefText.empty())
+        return std::string();
+      if (needParensBeforeDotOrArrow(*SubExpr)) {
+        return (llvm::Twine("(") + DerefText + ").").str();
+      }
+      return (llvm::Twine(DerefText) + ".").str();
+    }
+  }
+  // Add following '->'.
+  const std::string Text = fixit::getText(Expr, Context);
+  if (Text.empty())
+    return std::string();
+  if (needParensBeforeDotOrArrow(Expr)) {
+    return (llvm::Twine("(") + Text + ")->").str();
+  }
+  return (llvm::Twine(Text) + "->").str();
+}
+
+// BEGIN from: clang-tidy/utils/LexerUtils.cpp
+static SourceLocation findPreviousTokenStart(SourceLocation Start,
+                                             const SourceManager &SM,
+                                             const LangOptions &LangOpts) {
+  if (Start.isInvalid() || Start.isMacroID())
+    return SourceLocation();
+
+  SourceLocation BeforeStart = Start.getLocWithOffset(-1);
+  if (BeforeStart.isInvalid() || BeforeStart.isMacroID())
+    return SourceLocation();
+
+  return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts);
+}
+
+static SourceLocation findPreviousTokenKind(SourceLocation Start,
+                                            const SourceManager &SM,
+                                            const LangOptions &LangOpts,
+                                            tok::TokenKind TK) {
+  while (true) {
+    SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts);
+    if (L.isInvalid() || L.isMacroID())
+      return SourceLocation();
+
+    Token T;
+    if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true))
+      return SourceLocation();
+
+    if (T.is(TK))
+      return T.getLocation();
+
+    Start = L;
+  }
+}
+// END From: clang-tidy/utils/LexerUtils
+
+// For refactoring purposes, some expressions should be wrapped in parentheses
+// to avoid changes in the order of operation, assuming no other information
+// about the surrounding context.
+static bool needsParens(const Expr *E) {
+  return isa<BinaryOperator>(E) || isa<UnaryOperator>(E) ||
+         isa<CXXOperatorCallExpr>(E) || isa<AbstractConditionalOperator>(E);
+}
+
+// Finds the open paren of the call expression and return its location.  Returns
+// an invalid location if not found.
+static SourceLocation
+getOpenParen(const CallExpr &E,
+             const ast_matchers::MatchFinder::MatchResult &Result) {
+  SourceLocation EndLoc =
+      E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc();
+  return findPreviousTokenKind(EndLoc, *Result.SourceManager,
+                               Result.Context->getLangOpts(),
+                               tok::TokenKind::l_paren);
+}
+
+//
+// END Utilities
+//
+
+// For guaranteeing unique ids on NodeId creation.
+static size_t nextId() {
+  // Start with a relatively high number to avoid bugs if the user mixes
+  // explicitly-numbered ids with those generated with `NextId()`. Similarly, we
+  // choose a number that allows generated ids to be easily recognized.
+  static std::atomic<size_t> Next = 2222;
+  return Next.fetch_add(1, std::memory_order_relaxed);
+}
+
+// Gets the source text of the arguments to the call expression. Includes all
+// source between the parentheses delimting the call.
+static StringRef getArgumentsText(
+    const CallExpr &CE, const ast_matchers::MatchFinder::MatchResult &Result) {
+  auto Range = CharSourceRange::getCharRange(
+      getOpenParen(CE, Result).getLocWithOffset(1), CE.getRParenLoc());
+  return Lexer::getSourceText(Range, Result.Context->getSourceManager(),
+                              Result.Context->getLangOpts());
+}
+
+static Expected<ast_type_traits::DynTypedNode>
+getNode(const ast_matchers::BoundNodes &Nodes, llvm::StringRef Id) {
+  auto &NodesMap = Nodes.getMap();
+  auto It = NodesMap.find(Id);
+  if (It == NodesMap.end()) {
+    return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,
+                                               "Id not bound: " + Id);
+  }
+  return It->second;
+}
+
+namespace {
+using ::clang::ast_matchers::MatchFinder;
+using ::llvm::errc;
+using ::llvm::Error;
+using ::llvm::Expected;
+using ::llvm::StringError;
+using ::llvm::StringRef;
+
+// An arbitrary fragment of code within a stencil.
+class RawText : public StencilPartInterface {
+public:
+  explicit RawText(StringRef Text) : Text(Text) {}
+
+  Error eval(const MatchFinder::MatchResult &,
+             std::string *Result) const override {
+    Result->append(Text);
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<RawText>(*this);
+  }
+
+private:
+  std::string Text;
+};
+
+// A debugging operation to dump the AST for a particular (bound) AST node.
+class DebugPrintNodeOp : public StencilPartInterface {
+public:
+  explicit DebugPrintNodeOp(StringRef Id) : Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    std::string Output;
+    llvm::raw_string_ostream Os(Output);
+    auto NodeOrErr = getNode(Match.Nodes, Id);
+    if (auto Err = NodeOrErr.takeError()) {
+      return Err;
+    }
+    NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts()));
+    *Result += Os.str();
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<DebugPrintNodeOp>(*this);
+  }
+
+private:
+  std::string Id;
+};
+
+// A reference to a particular (bound) AST node.
+class NodeRef : public StencilPartInterface {
+public:
+  explicit NodeRef(StringRef Id) : Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    auto NodeOrErr = getNode(Match.Nodes, Id);
+    if (auto Err = NodeOrErr.takeError()) {
+      return Err;
+    }
+    *Result += fixit::getText(NodeOrErr.get(), *Match.Context);
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<NodeRef>(*this);
+  }
+
+private:
+  std::string Id;
+};
+
+// A stencil operation that, given a reference to an expression e and a Part
+// describing a member m, yields "e->m", when e is a pointer, "e2->m" when e =
+// "*e2" and "e.m" otherwise.
+class MemberOp : public StencilPartInterface {
+public:
+  MemberOp(StringRef ObjectId, StencilPart Member)
+      : ObjectId(ObjectId), Member(std::move(Member)) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    const auto *E = Match.Nodes.getNodeAs<Expr>(ObjectId);
+    if (E == nullptr) {
+      return llvm::make_error<StringError>(errc::invalid_argument,
+                                           "Id not bound: " + ObjectId);
+    }
+    // N.B. The RHS is a google string. TODO(yitzhakm): fix the RHS to be a
+    // std::string.
+    if (!E->isImplicitCXXThis()) {
+      *Result += E->getType()->isAnyPointerType()
+                     ? formatArrow(*Match.Context, *E)
+                     : formatDot(*Match.Context, *E);
+    }
+    return Member.eval(Match, Result);
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<MemberOp>(*this);
+  }
+
+private:
+  std::string ObjectId;
+  StencilPart Member;
+};
+
+// Operations all take a single reference to a Expr parameter, e.
+class ExprOp : public StencilPartInterface {
+public:
+  enum class Operator {
+    // Yields "e2" when e = "&e2" (with '&' the builtin operator), "*e" when e
+    // is a pointer and "e" otherwise.
+    kValue,
+    // Yields "e2" when e = "*e2" (with '*' the builtin operator), "e" when e is
+    // a pointer and "&e" otherwise.
+    kAddress,
+    // Wraps e in parens if it may parse differently depending on context. For
+    // example, a binary operation is always wrapped while a variable reference
+    // is never wrapped.
+    kParens,
+  };
+
+  ExprOp(Operator Op, StringRef Id) : Op(Op), Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    const auto *Expression = Match.Nodes.getNodeAs<Expr>(Id);
+    if (Expression == nullptr) {
+      return llvm::make_error<StringError>(errc::invalid_argument,
+                                           "Id not bound: " + Id);
+    }
+    const auto &Context = *Match.Context;
+    switch (Op) {
+    case ExprOp::Operator::kValue:
+      if (Expression->getType()->isAnyPointerType()) {
+        *Result += formatDereference(Context, *Expression);
+      } else {
+        *Result += fixit::getText(*Expression, Context);
+      }
+      break;
+    case ExprOp::Operator::kAddress:
+      if (Expression->getType()->isAnyPointerType()) {
+        *Result += fixit::getText(*Expression, Context);
+      } else {
+        *Result += formatAddressOf(Context, *Expression);
+      }
+      break;
+    case ExprOp::Operator::kParens:
+      if (needsParens(Expression)) {
+        *Result += "(";
+        *Result += fixit::getText(*Expression, Context);
+        *Result += ")";
+      } else {
+        *Result += fixit::getText(*Expression, Context);
+      }
+      break;
+    }
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<ExprOp>(*this);
+  }
+
+private:
+  Operator Op;
+  std::string Id;
+};
+
+// Given a reference to a named declaration d (NamedDecl), yields
+// the name. "d" must have an identifier name (that is, constructors are
+// not valid arguments to the Name operation).
+class NameOp : public StencilPartInterface {
+public:
+  explicit NameOp(StringRef Id) : Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    const NamedDecl *Decl;
+    if (const auto *Init = Match.Nodes.getNodeAs<CXXCtorInitializer>(Id)) {
+      Decl = Init->getMember();
+      if (Decl == nullptr) {
+        return llvm::make_error<StringError>(errc::invalid_argument,
+                                             "non-member initializer: " + Id);
+      }
+    } else {
+      Decl = Match.Nodes.getNodeAs<NamedDecl>(Id);
+      if (Decl == nullptr) {
+        return llvm::make_error<StringError>(
+            errc::invalid_argument,
+            "Id not bound or wrong type for Name op: " + Id);
+      }
+    }
+    // getIdentifier() guards the validity of getName().
+    if (Decl->getIdentifier() == nullptr) {
+      return llvm::make_error<StringError>(errc::invalid_argument,
+                                           "Decl is not identifier: " + Id);
+    }
+    *Result += Decl->getName();
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<NameOp>(*this);
+  }
+
+private:
+  std::string Id;
+};
+
+// Given a reference to a call expression (CallExpr), yields the
+// arguments as a comma separated list.
+class ArgsOp : public StencilPartInterface {
+public:
+  explicit ArgsOp(StringRef Id) : Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    const auto *CE = Match.Nodes.getNodeAs<CallExpr>(Id);
+    if (CE == nullptr) {
+      return llvm::make_error<StringError>(errc::invalid_argument,
+                                           "Id not bound: " + Id);
+    }
+    *Result += getArgumentsText(*CE, Match);
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<ArgsOp>(*this);
+  }
+
+private:
+  std::string Id;
+};
+
+// Given a function and a reference to a node, yields the string that results
+// from applying the function to the referenced node.
+class NodeFunctionOp : public StencilPartInterface {
+public:
+  NodeFunctionOp(stencil_generators::NodeFunction F, StringRef Id)
+      : F(std::move(F)), Id(Id) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    auto NodeOrErr = getNode(Match.Nodes, Id);
+    if (auto Err = NodeOrErr.takeError()) {
+      return Err;
+    }
+    *Result += F(*NodeOrErr, *Match.Context);
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<NodeFunctionOp>(*this);
+  }
+
+private:
+  stencil_generators::NodeFunction F;
+  std::string Id;
+};
+
+// Given a function and a stencil part, yields the string that results from
+// applying the function to the part's evaluation.
+class StringFunctionOp : public StencilPartInterface {
+public:
+  StringFunctionOp(stencil_generators::StringFunction F, StencilPart Part)
+      : F(std::move(F)), Part(std::move(Part)) {}
+
+  Error eval(const MatchFinder::MatchResult &Match,
+             std::string *Result) const override {
+    std::string PartResult;
+    if (auto Err = Part.eval(Match, &PartResult)) {
+      return Err;
+    }
+    *Result += F(PartResult);
+    return Error::success();
+  }
+
+  std::unique_ptr<StencilPartInterface> clone() const override {
+    return llvm::make_unique<StringFunctionOp>(*this);
+  }
+
+private:
+  stencil_generators::StringFunction F;
+  StencilPart Part;
+};
+} // namespace
+
+NodeId::NodeId() : NodeId(nextId()) {}
+
+void Stencil::append(const NodeId &Id) {
+  Parts.emplace_back(llvm::make_unique<NodeRef>(Id.id()));
+}
+
+void Stencil::append(StringRef Text) {
+  Parts.emplace_back(llvm::make_unique<RawText>(Text));
+}
+
+llvm::Expected<std::string>
+Stencil::eval(const MatchFinder::MatchResult &Match) const {
+  std::string Result;
+  for (const auto &Part : Parts) {
+    if (auto Err = Part.eval(Match, &Result)) {
+      return std::move(Err);
+    }
+  }
+  return Result;
+}
+
+namespace stencil_generators {
+StencilPart text(StringRef Text) {
+  return StencilPart(llvm::make_unique<RawText>(Text));
+}
+
+StencilPart node(llvm::StringRef Id) {
+  return StencilPart(llvm::make_unique<NodeRef>(Id));
+}
+StencilPart node(const NodeId &Id) { return node(Id.id()); }
+
+StencilPart member(StringRef Id, StringRef Member) {
+  return StencilPart(llvm::make_unique<MemberOp>(Id, text(Member)));
+}
+StencilPart member(const NodeId &ObjectId, StringRef Member) {
+  return member(ObjectId.id(), Member);
+}
+
+StencilPart member(StringRef Id, StencilPart Member) {
+  return StencilPart(llvm::make_unique<MemberOp>(Id, std::move(Member)));
+}
+StencilPart member(const NodeId &ObjectId, StencilPart Member) {
+  return member(ObjectId.id(), std::move(Member));
+}
+
+StencilPart asValue(StringRef Id) {
+  return StencilPart(llvm::make_unique<ExprOp>(ExprOp::Operator::kValue, Id));
+}
+StencilPart asValue(const NodeId &Id) { return asValue(Id.id()); }
+
+StencilPart asAddress(StringRef Id) {
+  return StencilPart(llvm::make_unique<ExprOp>(ExprOp::Operator::kAddress, Id));
+}
+StencilPart asAddress(const NodeId &Id) { return asAddress(Id.id()); }
+
+StencilPart parens(StringRef Id) {
+  return StencilPart(llvm::make_unique<ExprOp>(ExprOp::Operator::kParens, Id));
+}
+StencilPart parens(const NodeId &Id) { return parens(Id.id()); }
+
+StencilPart name(StringRef DeclId) {
+  return StencilPart(llvm::make_unique<NameOp>(DeclId));
+}
+StencilPart name(const NodeId &DeclId) { return name(DeclId.id()); }
+
+StencilPart apply(NodeFunction Fn, StringRef Id) {
+  return StencilPart(llvm::make_unique<NodeFunctionOp>(std::move(Fn), Id));
+}
+StencilPart apply(NodeFunction Fn, const NodeId &Id) {
+  return apply(std::move(Fn), Id.id());
+}
+
+StencilPart apply(StringFunction Fn, StencilPart Part) {
+  return StencilPart(
+      llvm::make_unique<StringFunctionOp>(std::move(Fn), std::move(Part)));
+}
+StencilPart apply(StringFunction Fn, llvm::StringRef Id) {
+  return apply(std::move(Fn), node(Id));
+}
+StencilPart apply(StringFunction Fn, const NodeId &Id) {
+  return apply(std::move(Fn), node(Id));
+}
+
+StencilPart args(StringRef CallId) {
+  return StencilPart(llvm::make_unique<ArgsOp>(CallId));
+}
+StencilPart args(const NodeId &CallId) { return args(CallId.id()); }
+
+StencilPart dPrint(StringRef Id) {
+  return StencilPart(llvm::make_unique<DebugPrintNodeOp>(Id));
+}
+StencilPart dPrint(const NodeId &Id) { return dPrint(Id.id()); }
+
+AddIncludeOp addInclude(StringRef Path) {
+  return AddIncludeOp{std::string(Path)};
+}
+RemoveIncludeOp removeInclude(StringRef Path) {
+  return RemoveIncludeOp{std::string(Path)};
+}
+} // namespace stencil_generators
+} // namespace tooling
+} // namespace clang
Index: clang/include/clang/Tooling/Refactoring/Stencil.h
===================================================================
--- /dev/null
+++ clang/include/clang/Tooling/Refactoring/Stencil.h
@@ -0,0 +1,273 @@
+// This file defines the *Stencil* abstraction: a code-generating object,
+// parameterized by named references to (bound) AST nodes.  Given a match
+// result, a stencil can be evaluated to a string of source code.
+//
+// A stencil is similar in spirit to a format string: it is composed of a series
+// of raw text strings, references to nodes (the parameters) and helper
+// code-generation operations.
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_
+#define LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_
+
+#include <string>
+#include <vector>
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace tooling {
+
+// A strong type for AST node identifiers.  The standard API uses StringRefs for
+// identifiers.  The strong type allows us to distinguish ids from arbitrary
+// text snippets in various parts of the API.
+class NodeId {
+public:
+  explicit NodeId(std::string Id) : Id(std::move(Id)) {}
+
+  // Creates a NodeId whose name is based on the id. Guarantees that unique ids
+  // map to unique NodeIds.
+  explicit NodeId(size_t Id) : Id("id" + std::to_string(Id)) {}
+
+  // Convenience constructor that generates a fresh id (with respect to other
+  // generated ids).
+  NodeId();
+
+  llvm::StringRef id() const { return Id; }
+
+  // Gets the AST node in `result` corresponding to this NodeId, if
+  // any. Otherwise, returns null.
+  template <typename Node>
+  const Node *
+  getNodeAs(const ast_matchers::MatchFinder::MatchResult &Result) const {
+    return Result.Nodes.getNodeAs<Node>(Id);
+  }
+
+private:
+  std::string Id;
+};
+
+// A stencil is represented as a sequence of "parts" that can each individually
+// generate a code string based on a match result.  The different kinds of parts
+// include (raw) text, references to bound nodes and assorted operations on
+// bound nodes.
+//
+// Users can create custom Stencil operations by implementing this interface.
+class StencilPartInterface {
+public:
+  StencilPartInterface() = default;
+  virtual ~StencilPartInterface() = default;
+
+  // Evaluates this part to a string and appends it to `result`.
+  virtual llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match,
+                           std::string *Result) const = 0;
+
+  virtual std::unique_ptr<StencilPartInterface> clone() const = 0;
+
+protected:
+  // Since this is an abstract class, copying/assigning only make sense for
+  // derived classes implementing `Clone()`.
+  StencilPartInterface(const StencilPartInterface &) = default;
+  StencilPartInterface &operator=(const StencilPartInterface &) = default;
+};
+
+// A copyable facade for a std::unique_ptr<StencilPartInterface>. Copies result
+// in a copy of the underlying pointee object.
+class StencilPart {
+public:
+  explicit StencilPart(std::unique_ptr<StencilPartInterface> Impl)
+      : Impl(std::move(Impl)) {}
+
+  // Copy constructor/assignment produce a deep copy.
+  StencilPart(const StencilPart &P) : Impl(P.Impl->clone()) {}
+  StencilPart(StencilPart &&) = default;
+  StencilPart &operator=(const StencilPart &P) {
+    Impl = P.Impl->clone();
+    return *this;
+  }
+  StencilPart &operator=(StencilPart &&) = default;
+
+  // See StencilPartInterface::Eval.
+  llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match,
+                   std::string *Result) const {
+    return Impl->eval(Match, Result);
+  }
+
+private:
+  std::unique_ptr<StencilPartInterface> Impl;
+};
+
+// Include directive modification
+//
+// Stencils also support operations to add and remove preprocessor include
+// directives. Users specify the included file with a string, which can
+// optionally be enclosed in <> or "". If unenclosed, surrounding double quotes
+// are implied. The resulting string is treated literally in the relevant
+// operation. No attempt is made to interpret the path in the string; for
+// example, to identify it with another path that resolves to the same file.
+
+// Add an #include for the specified path to the file being rewritten. No-op
+// if the directive is already present. A `path` surrounded by <> adds a
+// directive that uses <>; surrounded by "" (explicit or implicit) adds a
+// directive that uses "".
+struct AddIncludeOp {
+  std::string Path;
+};
+
+// Remove an #include of the specified path from the file being rewritten.
+// No-op if the include isn't present.  A `path` surrounded by <> removes a
+// directive that uses <>; surrounded by "" (explicit or implicit) removes a
+// directive that uses "".
+struct RemoveIncludeOp {
+  std::string Path;
+};
+
+// A sequence of code fragments, references to parameters and code-generation
+// operations that together can be evaluated to (a fragment of) source code,
+// given a match result.
+class Stencil {
+public:
+  Stencil() = default;
+
+  Stencil(const Stencil &) = default;
+  Stencil(Stencil &&) = default;
+  Stencil &operator=(const Stencil &) = default;
+  Stencil &operator=(Stencil &&) = default;
+
+  // Compose a stencil from a series of parts.
+  template <typename... Ts> static Stencil cat(Ts &&... Parts) {
+    Stencil Stencil;
+    Stencil.Parts.reserve(sizeof...(Parts));
+    auto Unused = {(Stencil.append(std::forward<Ts>(Parts)), true)...};
+    (void)Unused;
+    return Stencil;
+  }
+
+  // Evaluates the stencil given a match result. Requires that the nodes in the
+  // result includes any ids referenced in the stencil. References to missing
+  // nodes will result in an invalid_argument error.
+  llvm::Expected<std::string>
+  eval(const ast_matchers::MatchFinder::MatchResult &Match) const;
+
+  // List of paths for which an include directive should be added. See
+  // AddIncludeOp for the meaning of the path strings.
+  const std::vector<std::string> &addedIncludes() const {
+    return AddedIncludes;
+  }
+
+  // List of paths for which an include directive should be removed. See
+  // RemoveIncludeOp for the meaning of the path strings.
+  const std::vector<std::string> &removedIncludes() const {
+    return RemovedIncludes;
+  }
+
+private:
+  void append(const NodeId &Id);
+  void append(llvm::StringRef Text);
+  void append(StencilPart Part) { Parts.push_back(std::move(Part)); }
+  void append(AddIncludeOp Op) { AddedIncludes.push_back(std::move(Op.Path)); }
+  void append(RemoveIncludeOp Op) {
+    RemovedIncludes.push_back(std::move(Op.Path));
+  }
+
+  std::vector<StencilPart> Parts;
+  // See corresponding accessors for descriptions of these two fields.
+  std::vector<std::string> AddedIncludes;
+  std::vector<std::string> RemovedIncludes;
+};
+
+// Functions for conveniently building stencil parts.
+namespace stencil_generators {
+// Abbreviation for NodeId construction allowing for more concise references to
+// node ids in stencils.
+inline NodeId id(llvm::StringRef Id) { return NodeId(Id); }
+
+// Yields exactly the text provided.
+StencilPart text(llvm::StringRef Text);
+
+// Yields the source corresponding to the identified node.
+StencilPart node(const NodeId &Id);
+StencilPart node(llvm::StringRef Id);
+
+// Given a reference to a node e and a member m, yields "e->m", when e is a
+// pointer, "e2->m" when e = "*e2" and "e.m" otherwise.  "e" is wrapped in
+// parentheses, if needed.  Objects can be identified by NodeIds or strings and
+// members can be identified by other parts (e.g. Name()) or raw text, hence the
+// 4 overloads.
+StencilPart member(const NodeId &ObjectId, StencilPart Member);
+StencilPart member(const NodeId &ObjectId, llvm::StringRef Member);
+StencilPart member(llvm::StringRef ObjectId, StencilPart Member);
+StencilPart member(llvm::StringRef ObjectId, llvm::StringRef Member);
+
+// Renders a node's source as a value, even if the node is a pointer.
+// Specifically, given a reference to a node "e",
+// * when "e" has the form `&$expr`, yields `$expr`.
+// * when "e" is a pointer, yields `*$e`.
+// * otherwise, yields `$e`.
+StencilPart asValue(const NodeId &Id);
+StencilPart asValue(llvm::StringRef Id);
+
+// Renders a node's source as an address, even if the node is an lvalue.
+// Specifically, given a reference to a node "e",
+// * when "e" has the form `*$expr` (with '*' the builtin operator and `$expr`
+//   source code of an arbitrary expression), yields `$expr`.
+// * when "e" is a pointer, yields `$e`,
+// * otherwise, yields `&$e`.
+StencilPart asAddress(const NodeId &Id);
+StencilPart asAddress(llvm::StringRef Id);
+
+// Given a reference to a node "e", yields `($e)` if "e" may parse differently
+// depending on context. For example, a binary operation is always wrapped while
+// a variable reference is never wrapped.
+StencilPart parens(const NodeId &Id);
+StencilPart parens(llvm::StringRef Id);
+
+// Given a reference to a named declaration "d" (that is, a node of type
+// NamedDecl or one its derived classes), yields the name. "d" must have
+// an identifier name (that is, constructors are not valid arguments to the Name
+// operation).
+StencilPart name(const NodeId &DeclId);
+StencilPart name(llvm::StringRef DeclId);
+
+// Given a reference to call expression node, yields the source text of the
+// arguments (all source between the call's parentheses).
+StencilPart args(const NodeId &CallId);
+StencilPart args(llvm::StringRef CallId);
+
+// Derive a string from a node.
+using NodeFunction = std::function<std::string(
+    const ast_type_traits::DynTypedNode &Node, const ASTContext &Context)>;
+
+// Derive a string from the result of a stencil-part evaluation.
+using StringFunction = std::function<std::string(llvm::StringRef)>;
+
+// Yields the string from applying `fn` to the referenced node.
+StencilPart apply(NodeFunction Fn, const NodeId &Id);
+StencilPart apply(NodeFunction Fn, llvm::StringRef Id);
+
+// Yields the string from applying `fn` to the evaluation of `part`.
+StencilPart apply(StringFunction Fn, StencilPart Part);
+
+// Convenience overloads for case where target part is a node.
+StencilPart apply(StringFunction Fn, const NodeId &Id);
+StencilPart apply(StringFunction Fn, llvm::StringRef Id);
+
+// Add an include directive for 'path' into the file that is being rewritten.
+// See comments on AddIncludeOp for more details. Not (yet) supported by clang
+// tidy.
+AddIncludeOp addInclude(llvm::StringRef Path);
+// Remove an include directive for 'path' in the file that is being rewritten.
+// See comments on RemoveIncludeOp for more details. Not (yet) supported by
+// clang tidy.
+RemoveIncludeOp removeInclude(llvm::StringRef Path);
+
+// For debug use only; semantics are not guaranteed. Generates the string
+// resulting from calling the node's print() method.
+StencilPart dPrint(const NodeId &Id);
+StencilPart dPrint(llvm::StringRef Id);
+} // namespace stencil_generators
+} // namespace tooling
+} // namespace clang
+#endif // LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to