Skip to content

Commit c332a98

Browse files
committed
[libTooling] Add an EditGenerator that applies a rule throughout a bound node.
The new combinator, `rewriteDescendants`, applies a rewrite rule to all descendants of a specified bound node. That rewrite rule can refer to nodes bound by the parent, both in the matcher and in the edits. Reviewed By: gribozavr2 Differential Revision: https://reviews.llvm.org/D84409
1 parent 4ef2e59 commit c332a98

File tree

4 files changed

+269
-10
lines changed

4 files changed

+269
-10
lines changed

clang/include/clang/Tooling/Transformer/RewriteRule.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,23 @@ inline EditGenerator shrinkTo(RangeSelector outer, RangeSelector inner) {
332332
remove(enclose(after(inner), after(outer)))});
333333
}
334334

335+
/// Applies `Rule` to all descendants of the node bound to `NodeId`. `Rule` can
336+
/// refer to nodes bound by the calling rule. `Rule` is not applied to the node
337+
/// itself.
338+
///
339+
/// For example,
340+
/// ```
341+
/// auto InlineX =
342+
/// makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
343+
/// makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))).bind("f"),
344+
/// flatten(
345+
/// changeTo(name("f"), cat("newName")),
346+
/// rewriteDescendants("body", InlineX)));
347+
/// ```
348+
/// Here, we find the function `f`, change its name to `newName` and change all
349+
/// appearances of `x` in its body to `3`.
350+
EditGenerator rewriteDescendants(std::string NodeId, RewriteRule Rule);
351+
335352
/// The following three functions are a low-level part of the RewriteRule
336353
/// API. We expose them for use in implementing the fixtures that interpret
337354
/// RewriteRule, like Transformer and TransfomerTidy, or for more advanced

clang/lib/Tooling/Transformer/RewriteRule.cpp

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "clang/Tooling/Transformer/RewriteRule.h"
10+
#include "clang/AST/ASTTypeTraits.h"
11+
#include "clang/AST/Stmt.h"
1012
#include "clang/ASTMatchers/ASTMatchFinder.h"
1113
#include "clang/ASTMatchers/ASTMatchers.h"
1214
#include "clang/Basic/SourceLocation.h"
@@ -115,15 +117,144 @@ ASTEdit transformer::remove(RangeSelector S) {
115117
return change(std::move(S), std::make_shared<SimpleTextGenerator>(""));
116118
}
117119

118-
RewriteRule transformer::makeRule(ast_matchers::internal::DynTypedMatcher M,
119-
EditGenerator Edits,
120+
RewriteRule transformer::makeRule(DynTypedMatcher M, EditGenerator Edits,
120121
TextGenerator Explanation) {
121122
return RewriteRule{{RewriteRule::Case{
122123
std::move(M), std::move(Edits), std::move(Explanation), {}}}};
123124
}
124125

126+
namespace {
127+
128+
/// Unconditionally binds the given node set before trying `InnerMatcher` and
129+
/// keeps the bound nodes on a successful match.
130+
template <typename T>
131+
class BindingsMatcher : public ast_matchers::internal::MatcherInterface<T> {
132+
ast_matchers::BoundNodes Nodes;
133+
const ast_matchers::internal::Matcher<T> InnerMatcher;
134+
135+
public:
136+
explicit BindingsMatcher(ast_matchers::BoundNodes Nodes,
137+
ast_matchers::internal::Matcher<T> InnerMatcher)
138+
: Nodes(std::move(Nodes)), InnerMatcher(std::move(InnerMatcher)) {}
139+
140+
bool matches(
141+
const T &Node, ast_matchers::internal::ASTMatchFinder *Finder,
142+
ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override {
143+
ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);
144+
for (const auto &N : Nodes.getMap())
145+
Result.setBinding(N.first, N.second);
146+
if (InnerMatcher.matches(Node, Finder, &Result)) {
147+
*Builder = std::move(Result);
148+
return true;
149+
}
150+
return false;
151+
}
152+
};
153+
154+
/// Matches nodes of type T that have at least one descendant node for which the
155+
/// given inner matcher matches. Will match for each descendant node that
156+
/// matches. Based on ForEachDescendantMatcher, but takes a dynamic matcher,
157+
/// instead of a static one, because it is used by RewriteRule, which carries
158+
/// (only top-level) dynamic matchers.
159+
template <typename T>
160+
class DynamicForEachDescendantMatcher
161+
: public ast_matchers::internal::MatcherInterface<T> {
162+
const DynTypedMatcher DescendantMatcher;
163+
164+
public:
165+
explicit DynamicForEachDescendantMatcher(DynTypedMatcher DescendantMatcher)
166+
: DescendantMatcher(std::move(DescendantMatcher)) {}
167+
168+
bool matches(
169+
const T &Node, ast_matchers::internal::ASTMatchFinder *Finder,
170+
ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override {
171+
return Finder->matchesDescendantOf(
172+
Node, this->DescendantMatcher, Builder,
173+
ast_matchers::internal::ASTMatchFinder::BK_All);
174+
}
175+
};
176+
177+
template <typename T>
178+
ast_matchers::internal::Matcher<T>
179+
forEachDescendantDynamically(ast_matchers::BoundNodes Nodes,
180+
DynTypedMatcher M) {
181+
return ast_matchers::internal::makeMatcher(new BindingsMatcher<T>(
182+
std::move(Nodes),
183+
ast_matchers::internal::makeMatcher(
184+
new DynamicForEachDescendantMatcher<T>(std::move(M)))));
185+
}
186+
187+
class ApplyRuleCallback : public MatchFinder::MatchCallback {
188+
public:
189+
ApplyRuleCallback(RewriteRule Rule) : Rule(std::move(Rule)) {}
190+
191+
template <typename T>
192+
void registerMatchers(const ast_matchers::BoundNodes &Nodes,
193+
MatchFinder *MF) {
194+
for (auto &Matcher : transformer::detail::buildMatchers(Rule))
195+
MF->addMatcher(forEachDescendantDynamically<T>(Nodes, Matcher), this);
196+
}
197+
198+
void run(const MatchFinder::MatchResult &Result) override {
199+
if (!Edits)
200+
return;
201+
transformer::RewriteRule::Case Case =
202+
transformer::detail::findSelectedCase(Result, Rule);
203+
auto Transformations = Case.Edits(Result);
204+
if (!Transformations) {
205+
Edits = Transformations.takeError();
206+
return;
207+
}
208+
Edits->append(Transformations->begin(), Transformations->end());
209+
}
210+
211+
RewriteRule Rule;
212+
213+
// Initialize to a non-error state.
214+
Expected<SmallVector<Edit, 1>> Edits = SmallVector<Edit, 1>();
215+
};
216+
} // namespace
217+
218+
template <typename T>
219+
llvm::Expected<SmallVector<clang::transformer::Edit, 1>>
220+
rewriteDescendantsImpl(const T &Node, RewriteRule Rule,
221+
const MatchResult &Result) {
222+
ApplyRuleCallback Callback(std::move(Rule));
223+
MatchFinder Finder;
224+
Callback.registerMatchers<T>(Result.Nodes, &Finder);
225+
Finder.match(Node, *Result.Context);
226+
return std::move(Callback.Edits);
227+
}
228+
229+
EditGenerator transformer::rewriteDescendants(std::string NodeId,
230+
RewriteRule Rule) {
231+
// FIXME: warn or return error if `Rule` contains any `AddedIncludes`, since
232+
// these will be dropped.
233+
return [NodeId = std::move(NodeId),
234+
Rule = std::move(Rule)](const MatchResult &Result)
235+
-> llvm::Expected<SmallVector<clang::transformer::Edit, 1>> {
236+
const ast_matchers::BoundNodes::IDToNodeMap &NodesMap =
237+
Result.Nodes.getMap();
238+
auto It = NodesMap.find(NodeId);
239+
if (It == NodesMap.end())
240+
return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,
241+
"ID not bound: " + NodeId);
242+
if (auto *Node = It->second.get<Decl>())
243+
return rewriteDescendantsImpl(*Node, std::move(Rule), Result);
244+
if (auto *Node = It->second.get<Stmt>())
245+
return rewriteDescendantsImpl(*Node, std::move(Rule), Result);
246+
if (auto *Node = It->second.get<TypeLoc>())
247+
return rewriteDescendantsImpl(*Node, std::move(Rule), Result);
248+
249+
return llvm::make_error<llvm::StringError>(
250+
llvm::errc::invalid_argument,
251+
"type unsupported for recursive rewriting, ID=\"" + NodeId +
252+
"\", Kind=" + It->second.getNodeKind().asStringRef());
253+
};
254+
}
255+
125256
void transformer::addInclude(RewriteRule &Rule, StringRef Header,
126-
IncludeFormat Format) {
257+
IncludeFormat Format) {
127258
for (auto &Case : Rule.Cases)
128259
Case.AddedIncludes.emplace_back(Header.str(), Format);
129260
}

clang/lib/Tooling/Transformer/Transformer.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,8 @@ void Transformer::run(const MatchFinder::MatchResult &Result) {
3838
return;
3939
}
4040

41-
if (Transformations->empty()) {
42-
// No rewrite applied (but no error encountered either).
43-
transformer::detail::getRuleMatchLoc(Result).print(
44-
llvm::errs() << "note: skipping match at loc ", *Result.SourceManager);
45-
llvm::errs() << "\n";
41+
if (Transformations->empty())
4642
return;
47-
}
4843

4944
// Group the transformations, by file, into AtomicChanges, each anchored by
5045
// the location of the first change in that file.

clang/unittests/Tooling/TransformerTest.cpp

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ class ClangRefactoringTestBase : public testing::Test {
114114
if (C) {
115115
Changes.push_back(std::move(*C));
116116
} else {
117-
consumeError(C.takeError());
117+
// FIXME: stash this error rather then printing.
118+
llvm::errs() << "Error generating changes: "
119+
<< llvm::toString(C.takeError()) << "\n";
118120
++ErrorCount;
119121
}
120122
};
@@ -414,6 +416,120 @@ TEST_F(TransformerTest, ShrinkTo) {
414416
Input, Expected);
415417
}
416418

419+
// Rewrite various Stmts inside a Decl.
420+
TEST_F(TransformerTest, RewriteDescendantsDeclChangeStmt) {
421+
std::string Input =
422+
"int f(int x) { int y = x; { int z = x * x; } return x; }";
423+
std::string Expected =
424+
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
425+
auto InlineX =
426+
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
427+
testRule(makeRule(functionDecl(hasName("f")).bind("fun"),
428+
rewriteDescendants("fun", InlineX)),
429+
Input, Expected);
430+
}
431+
432+
// Rewrite various TypeLocs inside a Decl.
433+
TEST_F(TransformerTest, RewriteDescendantsDeclChangeTypeLoc) {
434+
std::string Input = "int f(int *x) { return *x; }";
435+
std::string Expected = "char f(char *x) { return *x; }";
436+
auto IntToChar = makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))),
437+
changeTo(cat("char")));
438+
testRule(makeRule(functionDecl(hasName("f")).bind("fun"),
439+
rewriteDescendants("fun", IntToChar)),
440+
Input, Expected);
441+
}
442+
443+
TEST_F(TransformerTest, RewriteDescendantsStmt) {
444+
// Add an unrelated definition to the header that also has a variable named
445+
// "x", to test that the rewrite is limited to the scope we intend.
446+
appendToHeader(R"cc(int g(int x) { return x; })cc");
447+
std::string Input =
448+
"int f(int x) { int y = x; { int z = x * x; } return x; }";
449+
std::string Expected =
450+
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
451+
auto InlineX =
452+
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
453+
testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
454+
rewriteDescendants("body", InlineX)),
455+
Input, Expected);
456+
}
457+
458+
TEST_F(TransformerTest, RewriteDescendantsStmtWithAdditionalChange) {
459+
std::string Input =
460+
"int f(int x) { int y = x; { int z = x * x; } return x; }";
461+
std::string Expected =
462+
"int newName(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
463+
auto InlineX =
464+
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
465+
testRule(
466+
makeRule(
467+
functionDecl(hasName("f"), hasBody(stmt().bind("body"))).bind("f"),
468+
flatten(changeTo(name("f"), cat("newName")),
469+
rewriteDescendants("body", InlineX))),
470+
Input, Expected);
471+
}
472+
473+
TEST_F(TransformerTest, RewriteDescendantsTypeLoc) {
474+
std::string Input = "int f(int *x) { return *x; }";
475+
std::string Expected = "int f(char *x) { return *x; }";
476+
auto IntToChar =
477+
makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"),
478+
changeTo(cat("char")));
479+
testRule(
480+
makeRule(functionDecl(hasName("f"),
481+
hasParameter(0, varDecl(hasTypeLoc(
482+
typeLoc().bind("parmType"))))),
483+
rewriteDescendants("parmType", IntToChar)),
484+
Input, Expected);
485+
}
486+
487+
TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) {
488+
std::string Input =
489+
"int f(int p) { int y = p; { int z = p * p; } return p; }";
490+
std::string Expected =
491+
"int f(int p) { int y = 3; { int z = 3 * 3; } return 3; }";
492+
std::string VarId = "var";
493+
auto InlineVar = makeRule(declRefExpr(to(varDecl(equalsBoundNode(VarId)))),
494+
changeTo(cat("3")));
495+
testRule(makeRule(functionDecl(hasName("f"),
496+
hasParameter(0, varDecl().bind(VarId)))
497+
.bind("fun"),
498+
rewriteDescendants("fun", InlineVar)),
499+
Input, Expected);
500+
}
501+
502+
TEST_F(TransformerTest, RewriteDescendantsUnboundNode) {
503+
std::string Input =
504+
"int f(int x) { int y = x; { int z = x * x; } return x; }";
505+
auto InlineX =
506+
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
507+
Transformer T(makeRule(functionDecl(hasName("f")),
508+
rewriteDescendants("UNBOUND", InlineX)),
509+
consumer());
510+
T.registerMatchers(&MatchFinder);
511+
EXPECT_FALSE(rewrite(Input));
512+
EXPECT_THAT(Changes, IsEmpty());
513+
EXPECT_EQ(ErrorCount, 1);
514+
}
515+
516+
TEST_F(TransformerTest, RewriteDescendantsInvalidNodeType) {
517+
std::string Input =
518+
"int f(int x) { int y = x; { int z = x * x; } return x; }";
519+
auto IntToChar =
520+
makeRule(qualType(isInteger(), builtinType()), changeTo(cat("char")));
521+
Transformer T(
522+
makeRule(functionDecl(
523+
hasName("f"),
524+
hasParameter(0, varDecl(hasType(qualType().bind("type"))))),
525+
rewriteDescendants("type", IntToChar)),
526+
consumer());
527+
T.registerMatchers(&MatchFinder);
528+
EXPECT_FALSE(rewrite(Input));
529+
EXPECT_THAT(Changes, IsEmpty());
530+
EXPECT_EQ(ErrorCount, 1);
531+
}
532+
417533
TEST_F(TransformerTest, InsertBeforeEdit) {
418534
std::string Input = R"cc(
419535
int f() {

0 commit comments

Comments
 (0)