--- /dev/null
+//===--- Stencil.h - Stencil class ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// /file
+/// 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 "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace tooling {
+
+/// 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:
+ virtual ~StencilPartInterface() = default;
+
+ /// Evaluates this part to a string and appends it to \c Result. \c Result is
+ /// undefined in the case of an error.
+ virtual llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match,
+ std::string *Result) const = 0;
+
+ virtual bool isEqual(const StencilPartInterface &other) const = 0;
+
+ const void *typeId() const { return TypeId; }
+
+protected:
+ StencilPartInterface(const void *DerivedId) : TypeId(DerivedId) {}
+
+ // 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;
+
+ /// Unique identifier of the concrete type of this instance. Supports safe
+ /// downcasting.
+ const void *TypeId;
+};
+
+/// 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::shared_ptr<StencilPartInterface> Impl)
+ : Impl(std::move(Impl)) {}
+
+ /// See `StencilPartInterface::eval()`.
+ llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match,
+ std::string *Result) const {
+ return Impl->eval(Match, Result);
+ }
+
+ bool operator==(const StencilPart &Other) const {
+ if (Impl == Other.Impl)
+ return true;
+ if (Impl == nullptr || Other.Impl == nullptr)
+ return false;
+ return Impl->isEqual(*Other.Impl);
+ }
+
+private:
+ std::shared_ptr<StencilPartInterface> Impl;
+};
+
+/// 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;
+
+ /// Composes a stencil from a series of parts.
+ template <typename... Ts> static Stencil cat(Ts &&... Parts) {
+ Stencil S;
+ S.Parts = {wrap(std::forward<Ts>(Parts))...};
+ return S;
+ }
+
+ /// Appends data from a \p OtherStencil to this stencil.
+ void append(Stencil OtherStencil);
+
+ // 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;
+
+ // Allow Stencils to operate as std::function, for compatibility with
+ // Transformer's TextGenerator.
+ llvm::Expected<std::string>
+ operator()(const ast_matchers::MatchFinder::MatchResult &Result) const {
+ return eval(Result);
+ }
+
+private:
+ friend bool operator==(const Stencil &A, const Stencil &B);
+ static StencilPart wrap(llvm::StringRef Text);
+ static StencilPart wrap(StencilPart Part) { return Part; }
+
+ std::vector<StencilPart> Parts;
+};
+
+inline bool operator==(const Stencil &A, const Stencil &B) {
+ return A.Parts == B.Parts;
+}
+
+inline bool operator!=(const Stencil &A, const Stencil &B) { return !(A == B); }
+
+// Functions for conveniently building stencils.
+namespace stencil {
+/// Convenience wrapper for Stencil::cat that can be imported with a using decl.
+template <typename... Ts> Stencil cat(Ts &&... Parts) {
+ return Stencil::cat(std::forward<Ts>(Parts)...);
+}
+
+/// \returns exactly the text provided.
+StencilPart text(llvm::StringRef Text);
+
+/// \returns the source corresponding to the identified node.
+StencilPart node(llvm::StringRef Id);
+/// Variant of \c node() that identifies the node as a statement, for purposes
+/// of deciding whether to include any trailing semicolon. Only relevant for
+/// Expr nodes, which, by default, are *not* considered as statements.
+/// \returns the source corresponding to the identified node, considered as a
+/// statement.
+StencilPart sNode(llvm::StringRef Id);
+
+/// For debug use only; semantics are not guaranteed.
+///
+/// \returns the string resulting from calling the node's print() method.
+StencilPart dPrint(llvm::StringRef Id);
+} // namespace stencil
+} // namespace tooling
+} // namespace clang
+#endif // LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
SourceCode.cpp
+ Stencil.cpp
Transformer.cpp
LINK_LIBS
--- /dev/null
+//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/Stencil.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/Refactoring/SourceCode.h"
+#include "llvm/Support/Errc.h"
+#include <atomic>
+#include <string>
+
+using namespace clang;
+using namespace tooling;
+
+using ast_matchers::MatchFinder;
+using llvm::Error;
+
+// A down_cast function to safely down cast a StencilPartInterface to a subclass
+// D. Returns nullptr if P is not an instance of D.
+template <typename D> const D *down_cast(const StencilPartInterface *P) {
+ if (P == nullptr || D::typeId() != P->typeId())
+ return nullptr;
+ return static_cast<const D *>(P);
+}
+
+static llvm::Expected<ast_type_traits::DynTypedNode>
+getNode(const ast_matchers::BoundNodes &Nodes, 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 {
+// An arbitrary fragment of code within a stencil.
+struct RawTextData {
+ explicit RawTextData(std::string T) : Text(std::move(T)) {}
+ std::string Text;
+};
+
+// A debugging operation to dump the AST for a particular (bound) AST node.
+struct DebugPrintNodeOpData {
+ explicit DebugPrintNodeOpData(std::string S) : Id(std::move(S)) {}
+ std::string Id;
+};
+// Whether to associate a trailing semicolon with a node when identifying it's
+// text. This flag is needed for expressions (clang::Expr), because their role
+// is ambiguous when they are also complete statements. When this flag is
+// `Always`, an expression node will be treated like a statement, and will
+// therefore be associated with any trailing semicolon.
+enum class SemiAssociation : bool {
+ Always,
+ Inferred,
+};
+
+// A reference to a particular (bound) AST node.
+struct NodeRefData {
+ explicit NodeRefData(std::string S, SemiAssociation SA)
+ : Id(std::move(S)), SemiAssoc(SA) {}
+ std::string Id;
+ SemiAssociation SemiAssoc;
+};
+} // namespace
+
+bool isEqualData(const RawTextData &A, const RawTextData &B) {
+ return A.Text == B.Text;
+}
+
+bool isEqualData(const DebugPrintNodeOpData &A, const DebugPrintNodeOpData &B) {
+ return A.Id == B.Id;
+}
+
+bool isEqualData(const NodeRefData &A, const NodeRefData &B) {
+ return A.Id == B.Id && A.SemiAssoc == B.SemiAssoc;
+}
+
+// The `evalData()` overloads evaluate the given stencil data to a string, given
+// the match result, and append it to `Result`. We define an overload for each
+// type of stencil data.
+
+Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &,
+ std::string *Result) {
+ Result->append(Data.Text);
+ return Error::success();
+}
+
+Error evalData(const DebugPrintNodeOpData &Data,
+ const MatchFinder::MatchResult &Match, std::string *Result) {
+ std::string Output;
+ llvm::raw_string_ostream Os(Output);
+ auto NodeOrErr = getNode(Match.Nodes, Data.Id);
+ if (auto Err = NodeOrErr.takeError())
+ return Err;
+ NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts()));
+ *Result += Os.str();
+ return Error::success();
+}
+
+Error evalData(const NodeRefData &Data, const MatchFinder::MatchResult &Match,
+ std::string *Result) {
+ auto NodeOrErr = getNode(Match.Nodes, Data.Id);
+ if (auto Err = NodeOrErr.takeError())
+ return Err;
+ auto &Node = *NodeOrErr;
+ switch (Data.SemiAssoc) {
+ case SemiAssociation::Inferred:
+ // Include the semicolon for non-expression statements:
+ *Result += Node.get<Stmt>() != nullptr && Node.get<Expr>() == nullptr
+ ? getExtendedText(NodeOrErr.get(), tok::TokenKind::semi,
+ *Match.Context)
+ : getText(NodeOrErr.get(), *Match.Context);
+ break;
+ case SemiAssociation::Always:
+ *Result +=
+ getExtendedText(NodeOrErr.get(), tok::TokenKind::semi, *Match.Context);
+ break;
+ }
+ return Error::success();
+}
+
+template <typename T>
+class StencilPartImpl : public StencilPartInterface {
+ T Data;
+
+public:
+ template <typename... Ps>
+ explicit StencilPartImpl(Ps &&... Args)
+ : StencilPartInterface(StencilPartImpl::typeId()),
+ Data(std::forward<Ps>(Args)...) {}
+
+ // Generates a unique identifier for this class (specifically, one per
+ // instantiation of the template).
+ static const void* typeId() {
+ static bool b;
+ return &b;
+ }
+
+ Error eval(const MatchFinder::MatchResult &Match,
+ std::string *Result) const override {
+ return evalData(Data, Match, Result);
+ }
+
+ bool isEqual(const StencilPartInterface &Other) const override {
+ if (const auto *OtherPtr = down_cast<StencilPartImpl>(&Other))
+ return isEqualData(Data, OtherPtr->Data);
+ return false;
+ }
+};
+
+namespace {
+using RawText = StencilPartImpl<RawTextData>;
+using DebugPrintNodeOp = StencilPartImpl<DebugPrintNodeOpData>;
+using NodeRef = StencilPartImpl<NodeRefData>;
+} // namespace
+
+StencilPart Stencil::wrap(StringRef Text) {
+ return stencil::text(Text);
+}
+
+void Stencil::append(Stencil OtherStencil) {
+ for (auto &Part : OtherStencil.Parts)
+ Parts.push_back(std::move(Part));
+}
+
+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;
+}
+
+StencilPart stencil::text(StringRef Text) {
+ return StencilPart(llvm::make_unique<RawText>(Text));
+}
+
+StencilPart stencil::node(StringRef Id) {
+ return StencilPart(llvm::make_unique<NodeRef>(Id, SemiAssociation::Inferred));
+}
+
+StencilPart stencil::sNode(StringRef Id) {
+ return StencilPart(llvm::make_unique<NodeRef>(Id, SemiAssociation::Always));
+}
+
+StencilPart stencil::dPrint(StringRef Id) {
+ return StencilPart(llvm::make_unique<DebugPrintNodeOp>(Id));
+}
ReplacementsYamlTest.cpp
RewriterTest.cpp
SourceCodeTest.cpp
+ StencilTest.cpp
ToolingTest.cpp
TransformerTest.cpp
)
--- /dev/null
+//===- unittest/Tooling/StencilTest.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/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"
+
+using namespace clang;
+using namespace tooling;
+using namespace ast_matchers;
+
+namespace {
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using MatchResult = MatchFinder::MatchResult;
+using tooling::stencil::node;
+using tooling::stencil::sNode;
+using tooling::stencil::text;
+
+// In tests, we can't directly match on llvm::Expected since its accessors
+// mutate the object. So, we collapse it to an Optional.
+static 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.
+static std::string wrapSnippet(llvm::Twine StatementCode) {
+ return ("auto stencil_test_snippet = []{" + StatementCode + "};").str();
+}
+
+static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) {
+ return varDecl(hasName("stencil_test_snippet"),
+ hasDescendant(compoundStmt(hasAnySubstatement(Matcher))));
+}
+
+struct TestMatch {
+ // The AST unit from which `result` is built. We bundle it because it backs
+ // the result. Users are not expected to access it.
+ std::unique_ptr<ASTUnit> AstUnit;
+ // The result to use in the test. References `ast_unit`.
+ MatchResult Result;
+};
+
+// Matches `Matcher` against the statement `StatementCode` and returns the
+// result. Handles putting the statement inside a function and modifying the
+// matcher correspondingly. `Matcher` should match `StatementCode` exactly --
+// that is, produce exactly one match.
+static llvm::Optional<TestMatch> matchStmt(llvm::Twine StatementCode,
+ StatementMatcher Matcher) {
+ auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode));
+ if (AstUnit == nullptr) {
+ ADD_FAILURE() << "AST construction failed";
+ return llvm::None;
+ }
+ ASTContext &Context = AstUnit->getASTContext();
+ auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context);
+ // We expect a single, exact match for the statement.
+ if (Matches.size() != 1) {
+ ADD_FAILURE() << "Wrong number of matches: " << Matches.size();
+ return llvm::None;
+ }
+ return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
+}
+
+class StencilTest : public ::testing::Test {
+protected:
+ // Verifies that the given stencil fails when evaluated on a valid match
+ // result. Binds a statement to "stmt", a (non-member) ctor-initializer to
+ // "init", an expression to "expr" and a (nameless) declaration to "decl".
+ void testError(const Stencil &Stencil,
+ ::testing::Matcher<std::string> Matcher) {
+ const std::string Snippet = R"cc(
+ struct A {};
+ class F : public A {
+ public:
+ F(int) {}
+ };
+ F(1);
+ )cc";
+ auto StmtMatch = matchStmt(
+ Snippet,
+ stmt(hasDescendant(
+ cxxConstructExpr(
+ hasDeclaration(decl(hasDescendant(cxxCtorInitializer(
+ isBaseInitializer())
+ .bind("init")))
+ .bind("decl")))
+ .bind("expr")))
+ .bind("stmt"));
+ ASSERT_TRUE(StmtMatch);
+ if (auto ResultOrErr = Stencil.eval(StmtMatch->Result)) {
+ ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr;
+ } else {
+ auto Err = llvm::handleErrors(ResultOrErr.takeError(),
+ [&Matcher](const 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, AllOf(HasSubstr(UnboundId), HasSubstr("not bound")));
+ }
+};
+
+TEST_F(StencilTest, SingleStatement) {
+ StringRef Condition("C"), Then("T"), Else("E");
+ const std::string Snippet = R"cc(
+ if (true)
+ return 1;
+ else
+ return 0;
+ )cc";
+ auto StmtMatch = matchStmt(
+ Snippet, ifStmt(hasCondition(expr().bind(Condition)),
+ hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else))));
+ ASSERT_TRUE(StmtMatch);
+ // Invert the if-then-else.
+ auto Stencil = Stencil::cat("if (!", node(Condition), ") ", sNode(Else),
+ " else ", sNode(Then));
+ EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+ IsSomething(Eq("if (!true) return 0; else return 1;")));
+}
+
+TEST_F(StencilTest, SingleStatementCallOperator) {
+ StringRef Condition("C"), Then("T"), Else("E");
+ const std::string Snippet = R"cc(
+ if (true)
+ return 1;
+ else
+ return 0;
+ )cc";
+ auto StmtMatch = matchStmt(
+ Snippet, ifStmt(hasCondition(expr().bind(Condition)),
+ hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else))));
+ ASSERT_TRUE(StmtMatch);
+ // Invert the if-then-else.
+ Stencil S = Stencil::cat("if (!", node(Condition), ") ", sNode(Else),
+ " else ", sNode(Then));
+ EXPECT_THAT(toOptional(S(StmtMatch->Result)),
+ IsSomething(Eq("if (!true) return 0; else return 1;")));
+}
+
+TEST_F(StencilTest, UnboundNode) {
+ const std::string Snippet = R"cc(
+ if (true)
+ return 1;
+ else
+ return 0;
+ )cc";
+ auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")),
+ hasThen(stmt().bind("a2"))));
+ ASSERT_TRUE(StmtMatch);
+ auto Stencil = Stencil::cat("if(!", sNode("a1"), ") ", node("UNBOUND"), ";");
+ auto ResultOrErr = Stencil.eval(StmtMatch->Result);
+ EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError()))
+ << "Expected unbound node, got " << *ResultOrErr;
+}
+
+// Tests that a stencil with a single parameter (`Id`) evaluates to the expected
+// string, when `Id` is bound to the expression-statement in `Snippet`.
+void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil,
+ StringRef Expected) {
+ auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
+ ASSERT_TRUE(StmtMatch);
+ EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)),
+ IsSomething(Expected));
+}
+
+TEST_F(StencilTest, NodeOp) {
+ StringRef Id = "id";
+ testExpr(Id, "3;", Stencil::cat(node(Id)), "3");
+}
+
+TEST_F(StencilTest, SNodeOp) {
+ StringRef Id = "id";
+ testExpr(Id, "3;", Stencil::cat(sNode(Id)), "3;");
+}
+
+TEST(StencilEqualityTest, Equality) {
+ using stencil::dPrint;
+ auto Lhs = Stencil::cat("foo", node("node"), dPrint("dprint_id"));
+ auto Rhs = Lhs;
+ EXPECT_EQ(Lhs, Rhs);
+}
+
+TEST(StencilEqualityTest, InEqualityDifferentOrdering) {
+ auto Lhs = Stencil::cat("foo", node("node"));
+ auto Rhs = Stencil::cat(node("node"), "foo");
+ EXPECT_NE(Lhs, Rhs);
+}
+
+TEST(StencilEqualityTest, InEqualityDifferentSizes) {
+ auto Lhs = Stencil::cat("foo", node("node"), "bar", "baz");
+ auto Rhs = Stencil::cat("foo", node("node"), "bar");
+ EXPECT_NE(Lhs, Rhs);
+}
+} // namespace