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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits