Skip to content

Commit d1e3235

Browse files
tJenerymand
authored andcommitted
[libTooling] Change Tranformer's consumer to take multiple changes
Previously, Transformer would invoke the consumer once per file modified per match, in addition to any errors encountered. The consumer is not aware of which AtomicChanges come from any particular match. It is unclear which sets of edits may be related or whether an error invalidates any previously emitted changes. Modify the signature of the consumer to accept a set of changes. This keeps related changes (i.e. all edits from a single match) together, and clarifies that errors don't produce partial changes. Reviewed By: ymandel Differential Revision: https://reviews.llvm.org/D119745
1 parent fd4cc87 commit d1e3235

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,25 @@ namespace tooling {
2222
/// defined by the arguments of the constructor.
2323
class Transformer : public ast_matchers::MatchFinder::MatchCallback {
2424
public:
25-
using ChangeConsumer =
26-
std::function<void(Expected<clang::tooling::AtomicChange> Change)>;
27-
28-
/// \param Consumer Receives each rewrite or error. Will not necessarily be
29-
/// called for each match; for example, if the rewrite is not applicable
30-
/// because of macros, but doesn't fail. Note that clients are responsible
25+
/// Provides the set of changes to the consumer. The callback is free to move
26+
/// or destructively consume the changes as needed.
27+
///
28+
/// We use \c MutableArrayRef as an abstraction to provide decoupling, and we
29+
/// expect the majority of consumers to copy or move the individual values
30+
/// into a separate data structure.
31+
using ChangeSetConsumer = std::function<void(
32+
Expected<llvm::MutableArrayRef<AtomicChange>> Changes)>;
33+
34+
/// \param Consumer Receives all rewrites for a single match, or an error.
35+
/// Will not necessarily be called for each match; for example, if the rule
36+
/// generates no edits but does not fail. Note that clients are responsible
3137
/// for handling the case that independent \c AtomicChanges conflict with each
3238
/// other.
33-
Transformer(transformer::RewriteRule Rule, ChangeConsumer Consumer)
34-
: Rule(std::move(Rule)), Consumer(std::move(Consumer)) {}
39+
explicit Transformer(transformer::RewriteRule Rule,
40+
ChangeSetConsumer Consumer)
41+
: Rule(std::move(Rule)), Consumer(std::move(Consumer)) {
42+
assert(this->Consumer && "Consumer is empty");
43+
}
3544

3645
/// N.B. Passes `this` pointer to `MatchFinder`. So, this object should not
3746
/// be moved after this call.
@@ -43,8 +52,9 @@ class Transformer : public ast_matchers::MatchFinder::MatchCallback {
4352

4453
private:
4554
transformer::RewriteRule Rule;
46-
/// Receives each successful rewrites as an \c AtomicChange.
47-
ChangeConsumer Consumer;
55+
/// Receives sets of successful rewrites as an
56+
/// \c llvm::ArrayRef<AtomicChange>.
57+
ChangeSetConsumer Consumer;
4858
};
4959
} // namespace tooling
5060
} // namespace clang

clang/lib/Tooling/Transformer/Transformer.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ void Transformer::run(const MatchFinder::MatchResult &Result) {
6565
}
6666
}
6767

68+
llvm::SmallVector<AtomicChange, 1> Changes;
69+
Changes.reserve(ChangesByFileID.size());
6870
for (auto &IDChangePair : ChangesByFileID)
69-
Consumer(std::move(IDChangePair.second));
71+
Changes.push_back(std::move(IDChangePair.second));
72+
Consumer(llvm::MutableArrayRef<AtomicChange>(Changes));
7073
}

clang/unittests/Tooling/TransformerTest.cpp

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ using ::clang::transformer::applyFirst;
2626
using ::clang::transformer::before;
2727
using ::clang::transformer::cat;
2828
using ::clang::transformer::changeTo;
29+
using ::clang::transformer::editList;
2930
using ::clang::transformer::makeRule;
3031
using ::clang::transformer::member;
3132
using ::clang::transformer::name;
@@ -36,6 +37,8 @@ using ::clang::transformer::RewriteRule;
3637
using ::clang::transformer::statement;
3738
using ::testing::ElementsAre;
3839
using ::testing::IsEmpty;
40+
using ::testing::ResultOf;
41+
using ::testing::UnorderedElementsAre;
3942

4043
constexpr char KHeaderContents[] = R"cc(
4144
struct string {
@@ -120,10 +123,11 @@ class ClangRefactoringTestBase : public testing::Test {
120123
return *ChangedCode;
121124
}
122125

123-
Transformer::ChangeConsumer consumer() {
124-
return [this](Expected<AtomicChange> C) {
126+
Transformer::ChangeSetConsumer consumer() {
127+
return [this](Expected<MutableArrayRef<AtomicChange>> C) {
125128
if (C) {
126-
Changes.push_back(std::move(*C));
129+
Changes.insert(Changes.end(), std::make_move_iterator(C->begin()),
130+
std::make_move_iterator(C->end()));
127131
} else {
128132
// FIXME: stash this error rather then printing.
129133
llvm::errs() << "Error generating changes: "
@@ -799,7 +803,6 @@ TEST_F(TransformerTest, MultiChange) {
799803
}
800804

801805
TEST_F(TransformerTest, EditList) {
802-
using clang::transformer::editList;
803806
std::string Input = R"cc(
804807
void foo() {
805808
if (10 > 1.0)
@@ -827,7 +830,6 @@ TEST_F(TransformerTest, EditList) {
827830
}
828831

829832
TEST_F(TransformerTest, Flatten) {
830-
using clang::transformer::editList;
831833
std::string Input = R"cc(
832834
void foo() {
833835
if (10 > 1.0)
@@ -1638,4 +1640,46 @@ TEST_F(TransformerTest, AddIncludeMultipleFiles) {
16381640
<< "Could not update code: " << llvm::toString(UpdatedCode.takeError());
16391641
EXPECT_EQ(format(*UpdatedCode), format(Header));
16401642
}
1643+
1644+
// A single change set can span multiple files.
1645+
TEST_F(TransformerTest, MultiFileEdit) {
1646+
// NB: The fixture is unused for this test, but kept for the test suite name.
1647+
std::string Header = R"cc(void Func(int id);)cc";
1648+
std::string Source = R"cc(#include "input.h"
1649+
void Caller() {
1650+
int id = 0;
1651+
Func(id);
1652+
})cc";
1653+
int ErrorCount = 0;
1654+
std::vector<AtomicChanges> ChangeSets;
1655+
clang::ast_matchers::MatchFinder MatchFinder;
1656+
Transformer T(
1657+
makeRule(callExpr(callee(functionDecl(hasName("Func"))),
1658+
forEachArgumentWithParam(expr().bind("arg"),
1659+
parmVarDecl().bind("param"))),
1660+
editList({changeTo(node("arg"), cat("ARG")),
1661+
changeTo(node("param"), cat("PARAM"))})),
1662+
[&](Expected<MutableArrayRef<AtomicChange>> Changes) {
1663+
if (Changes)
1664+
ChangeSets.push_back(AtomicChanges(Changes->begin(), Changes->end()));
1665+
else
1666+
++ErrorCount;
1667+
});
1668+
T.registerMatchers(&MatchFinder);
1669+
auto Factory = newFrontendActionFactory(&MatchFinder);
1670+
EXPECT_TRUE(runToolOnCodeWithArgs(
1671+
Factory->create(), Source, std::vector<std::string>(), "input.cc",
1672+
"clang-tool", std::make_shared<PCHContainerOperations>(),
1673+
{{"input.h", Header}}));
1674+
1675+
EXPECT_EQ(ErrorCount, 0);
1676+
EXPECT_THAT(
1677+
ChangeSets,
1678+
UnorderedElementsAre(UnorderedElementsAre(
1679+
ResultOf([](const AtomicChange &C) { return C.getFilePath(); },
1680+
"input.cc"),
1681+
ResultOf([](const AtomicChange &C) { return C.getFilePath(); },
1682+
"./input.h"))));
1683+
}
1684+
16411685
} // namespace

0 commit comments

Comments
 (0)