diff --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h --- a/clang/include/clang/Tooling/Transformer/RewriteRule.h +++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h @@ -380,6 +380,38 @@ // RewriteRule API. Recast them as such. Or, just declare these functions // public and well-supported and move them out of `detail`. namespace detail { +/// The following overload set is a version of `rewriteDescendants` that +/// operates directly on the AST, rather than generating a Transformer +/// combinator. It applies `Rule` to all descendants of `Node`, although not +/// `Node` itself. `Rule` can refer to nodes bound in `Result`. +/// +/// For example, assuming that "body" is bound to a function body in MatchResult +/// `Results`, this will produce edits to change all appearances of `x` in that +/// body to `3`. +/// ``` +/// auto InlineX = +/// makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); +/// const auto *Node = Results.Nodes.getNodeAs("body"); +/// auto Edits = rewriteDescendants(*Node, InlineX, Results); +/// ``` +/// @{ +llvm::Expected> +rewriteDescendants(const Decl &Node, RewriteRule Rule, + const ast_matchers::MatchFinder::MatchResult &Result); + +llvm::Expected> +rewriteDescendants(const Stmt &Node, RewriteRule Rule, + const ast_matchers::MatchFinder::MatchResult &Result); + +llvm::Expected> +rewriteDescendants(const TypeLoc &Node, RewriteRule Rule, + const ast_matchers::MatchFinder::MatchResult &Result); + +llvm::Expected> +rewriteDescendants(const DynTypedNode &Node, RewriteRule Rule, + const ast_matchers::MatchFinder::MatchResult &Result); +/// @} + /// Builds a single matcher for the rule, covering all of the rule's cases. /// Only supports Rules whose cases' matchers share the same base "kind" /// (`Stmt`, `Decl`, etc.) Deprecated: use `buildMatchers` instead, which diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -242,7 +242,7 @@ } // namespace template -static llvm::Expected> +llvm::Expected> rewriteDescendantsImpl(const T &Node, RewriteRule Rule, const MatchResult &Result) { ApplyRuleCallback Callback(std::move(Rule)); @@ -252,10 +252,43 @@ return std::move(Callback.Edits); } +llvm::Expected> +transformer::detail::rewriteDescendants(const Decl &Node, RewriteRule Rule, + const MatchResult &Result) { + return rewriteDescendantsImpl(Node, std::move(Rule), Result); +} + +llvm::Expected> +transformer::detail::rewriteDescendants(const Stmt &Node, RewriteRule Rule, + const MatchResult &Result) { + return rewriteDescendantsImpl(Node, std::move(Rule), Result); +} + +llvm::Expected> +transformer::detail::rewriteDescendants(const TypeLoc &Node, RewriteRule Rule, + const MatchResult &Result) { + return rewriteDescendantsImpl(Node, std::move(Rule), Result); +} + +llvm::Expected> +transformer::detail::rewriteDescendants(const DynTypedNode &DNode, + RewriteRule Rule, + const MatchResult &Result) { + if (const auto *Node = DNode.get()) + return rewriteDescendantsImpl(*Node, std::move(Rule), Result); + if (const auto *Node = DNode.get()) + return rewriteDescendantsImpl(*Node, std::move(Rule), Result); + if (const auto *Node = DNode.get()) + return rewriteDescendantsImpl(*Node, std::move(Rule), Result); + + return llvm::make_error( + llvm::errc::invalid_argument, + "type unsupported for recursive rewriting, Kind=" + + DNode.getNodeKind().asStringRef()); +} + EditGenerator transformer::rewriteDescendants(std::string NodeId, RewriteRule Rule) { - // FIXME: warn or return error if `Rule` contains any `AddedIncludes`, since - // these will be dropped. return [NodeId = std::move(NodeId), Rule = std::move(Rule)](const MatchResult &Result) -> llvm::Expected> { @@ -265,17 +298,7 @@ if (It == NodesMap.end()) return llvm::make_error(llvm::errc::invalid_argument, "ID not bound: " + NodeId); - if (auto *Node = It->second.get()) - return rewriteDescendantsImpl(*Node, std::move(Rule), Result); - if (auto *Node = It->second.get()) - return rewriteDescendantsImpl(*Node, std::move(Rule), Result); - if (auto *Node = It->second.get()) - return rewriteDescendantsImpl(*Node, std::move(Rule), Result); - - return llvm::make_error( - llvm::errc::invalid_argument, - "type unsupported for recursive rewriting, ID=\"" + NodeId + - "\", Kind=" + It->second.getNodeKind().asStringRef()); + return detail::rewriteDescendants(It->second, std::move(Rule), Result); }; } diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -25,6 +25,7 @@ using ::testing::IsEmpty; using transformer::cat; using transformer::changeTo; +using transformer::rewriteDescendants; using transformer::RewriteRule; constexpr char KHeaderContents[] = R"cc( @@ -568,6 +569,88 @@ EXPECT_EQ(ErrorCount, 1); } +// +// We include one test per typed overload. We don't test extensively since that +// is already covered by the tests above. +// + +TEST_F(TransformerTest, RewriteDescendantsTypedStmt) { + // Add an unrelated definition to the header that also has a variable named + // "x", to test that the rewrite is limited to the scope we intend. + appendToHeader(R"cc(int g(int x) { return x; })cc"); + std::string Input = + "int f(int x) { int y = x; { int z = x * x; } return x; }"; + std::string Expected = + "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; + auto InlineX = + makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); + testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))), + [&InlineX](const MatchFinder::MatchResult &R) { + const auto *Node = R.Nodes.getNodeAs("body"); + assert(Node != nullptr && "body must be bound"); + return transformer::detail::rewriteDescendants( + *Node, InlineX, R); + }), + Input, Expected); +} + +TEST_F(TransformerTest, RewriteDescendantsTypedDecl) { + std::string Input = + "int f(int x) { int y = x; { int z = x * x; } return x; }"; + std::string Expected = + "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; + auto InlineX = + makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); + testRule(makeRule(functionDecl(hasName("f")).bind("fun"), + [&InlineX](const MatchFinder::MatchResult &R) { + const auto *Node = R.Nodes.getNodeAs("fun"); + assert(Node != nullptr && "fun must be bound"); + return transformer::detail::rewriteDescendants( + *Node, InlineX, R); + }), + Input, Expected); +} + +TEST_F(TransformerTest, RewriteDescendantsTypedTypeLoc) { + std::string Input = "int f(int *x) { return *x; }"; + std::string Expected = "int f(char *x) { return *x; }"; + auto IntToChar = + makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"), + changeTo(cat("char"))); + testRule( + makeRule( + functionDecl( + hasName("f"), + hasParameter(0, varDecl(hasTypeLoc(typeLoc().bind("parmType"))))), + [&IntToChar](const MatchFinder::MatchResult &R) { + const auto *Node = R.Nodes.getNodeAs("parmType"); + assert(Node != nullptr && "parmType must be bound"); + return transformer::detail::rewriteDescendants(*Node, IntToChar, R); + }), + Input, Expected); +} + +TEST_F(TransformerTest, RewriteDescendantsTypedDynTyped) { + // Add an unrelated definition to the header that also has a variable named + // "x", to test that the rewrite is limited to the scope we intend. + appendToHeader(R"cc(int g(int x) { return x; })cc"); + std::string Input = + "int f(int x) { int y = x; { int z = x * x; } return x; }"; + std::string Expected = + "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; + auto InlineX = + makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); + testRule( + makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))), + [&InlineX](const MatchFinder::MatchResult &R) { + auto It = R.Nodes.getMap().find("body"); + assert(It != R.Nodes.getMap().end() && "body must be bound"); + return transformer::detail::rewriteDescendants(It->second, + InlineX, R); + }), + Input, Expected); +} + TEST_F(TransformerTest, InsertBeforeEdit) { std::string Input = R"cc( int f() {