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