Skip to content

Commit 9df9b6f

Browse files
author
Eric Liu
committedSep 19, 2016
Recommit r281457 "Supports adding insertion around non-insertion replacements".
Summary: Diff to r281457: - added a test case `CalculateRangesOfInsertionAroundReplacement`. Reviewers: djasper Subscribers: cfe-commits, klimek Differential Revision: https://reviews.llvm.org/D24606 llvm-svn: 281891
1 parent 0efb96a commit 9df9b6f

File tree

3 files changed

+128
-24
lines changed

3 files changed

+128
-24
lines changed
 

‎clang/include/clang/Tooling/Core/Replacement.h

+11-7
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,18 @@ class Replacements {
159159

160160
/// \brief Adds a new replacement \p R to the current set of replacements.
161161
/// \p R must have the same file path as all existing replacements.
162-
/// Returns true if the replacement is successfully inserted; otherwise,
162+
/// Returns `success` if the replacement is successfully inserted; otherwise,
163163
/// it returns an llvm::Error, i.e. there is a conflict between R and the
164-
/// existing replacements or R's file path is different from the filepath of
165-
/// existing replacements. Callers must explicitly check the Error returned.
166-
/// This prevents users from adding order-dependent replacements. To control
167-
/// the order in which order-dependent replacements are applied, use
168-
/// merge({R}) with R referring to the changed code after applying all
169-
/// existing replacements.
164+
/// existing replacements (i.e. they are order-dependent) or R's file path is
165+
/// different from the filepath of existing replacements. Callers must
166+
/// explicitly check the Error returned. This prevents users from adding
167+
/// order-dependent replacements. To control the order in which
168+
/// order-dependent replacements are applied, use merge({R}) with R referring
169+
/// to the changed code after applying all existing replacements.
170+
/// Two replacements are considered order-independent if they:
171+
/// - don't overlap (being directly adjacent is fine) and
172+
/// - aren't both inserts at the same code location (would be
173+
/// order-dependent).
170174
/// Replacements with offset UINT_MAX are special - we do not detect conflicts
171175
/// for such replacements since users may add them intentionally as a special
172176
/// category of replacements.

‎clang/lib/Tooling/Core/Replacement.cpp

+34-12
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ void Replacement::setFromSourceRange(const SourceManager &Sources,
134134
ReplacementText);
135135
}
136136

137+
llvm::Error makeConflictReplacementsError(const Replacement &New,
138+
const Replacement &Existing) {
139+
return llvm::make_error<llvm::StringError>(
140+
"New replacement:\n" + New.toString() +
141+
"\nconflicts with existing replacement:\n" + Existing.toString(),
142+
llvm::inconvertibleErrorCode());
143+
}
144+
137145
llvm::Error Replacements::add(const Replacement &R) {
138146
// Check the file path.
139147
if (!Replaces.empty() && R.getFilePath() != Replaces.begin()->getFilePath())
@@ -160,13 +168,24 @@ llvm::Error Replacements::add(const Replacement &R) {
160168
// entries that start at the end can still be conflicting if R is an
161169
// insertion.
162170
auto I = Replaces.lower_bound(AtEnd);
163-
// If it starts at the same offset as R (can only happen if R is an
164-
// insertion), we have a conflict. In that case, increase I to fall through
165-
// to the conflict check.
166-
if (I != Replaces.end() && R.getOffset() == I->getOffset())
167-
++I;
171+
// If `I` starts at the same offset as `R`, `R` must be an insertion.
172+
if (I != Replaces.end() && R.getOffset() == I->getOffset()) {
173+
assert(R.getLength() == 0);
174+
// `I` is also an insertion, `R` and `I` conflict.
175+
if (I->getLength() == 0)
176+
return makeConflictReplacementsError(R, *I);
177+
// Insertion `R` is adjacent to a non-insertion replacement `I`, so they
178+
// are order-independent. It is safe to assume that `R` will not conflict
179+
// with any replacement before `I` since all replacements before `I` must
180+
// either end before `R` or end at `R` but has length > 0 (if the
181+
// replacement before `I` is an insertion at `R`, it would have been `I`
182+
// since it is a lower bound of `AtEnd` and ordered before the current `I`
183+
// in the set).
184+
Replaces.insert(R);
185+
return llvm::Error::success();
186+
}
168187

169-
// I is the smallest iterator whose entry cannot overlap.
188+
// `I` is the smallest iterator (after `R`) whose entry cannot overlap.
170189
// If that is begin(), there are no overlaps.
171190
if (I == Replaces.begin()) {
172191
Replaces.insert(R);
@@ -175,16 +194,19 @@ llvm::Error Replacements::add(const Replacement &R) {
175194
--I;
176195
// If the previous entry does not overlap, we know that entries before it
177196
// can also not overlap.
178-
if (R.getOffset() != I->getOffset() &&
179-
!Range(R.getOffset(), R.getLength())
197+
if (!Range(R.getOffset(), R.getLength())
180198
.overlapsWith(Range(I->getOffset(), I->getLength()))) {
199+
// If `R` and `I` do not have the same offset, it is safe to add `R` since
200+
// it must come after `I`. Otherwise:
201+
// - If `R` is an insertion, `I` must not be an insertion since it would
202+
// have come after `AtEnd`.
203+
// - If `R` is not an insertion, `I` must be an insertion; otherwise, `R`
204+
// and `I` would have overlapped.
205+
// In either case, we can safely insert `R`.
181206
Replaces.insert(R);
182207
return llvm::Error::success();
183208
}
184-
return llvm::make_error<llvm::StringError>(
185-
"New replacement:\n" + R.toString() +
186-
"\nconflicts with existing replacement:\n" + I->toString(),
187-
llvm::inconvertibleErrorCode());
209+
return makeConflictReplacementsError(R, *I);
188210
}
189211

190212
namespace {

‎clang/unittests/Tooling/RefactoringTest.cpp

+83-5
Original file line numberDiff line numberDiff line change
@@ -115,24 +115,26 @@ TEST_F(ReplacementTest, FailAddReplacements) {
115115
llvm::consumeError(std::move(Err));
116116
}
117117

118-
TEST_F(ReplacementTest, FailAddOverlappingInsertions) {
118+
TEST_F(ReplacementTest, AddAdjacentInsertionAndReplacement) {
119119
Replacements Replaces;
120120
// Test adding an insertion at the offset of an existing replacement.
121121
auto Err = Replaces.add(Replacement("x.cc", 10, 3, "replace"));
122122
EXPECT_TRUE(!Err);
123123
llvm::consumeError(std::move(Err));
124124
Err = Replaces.add(Replacement("x.cc", 10, 0, "insert"));
125-
EXPECT_TRUE((bool)Err);
125+
EXPECT_TRUE(!Err);
126126
llvm::consumeError(std::move(Err));
127+
EXPECT_EQ(Replaces.size(), 2u);
127128

128129
Replaces.clear();
129130
// Test overlap with an existing insertion.
130131
Err = Replaces.add(Replacement("x.cc", 10, 0, "insert"));
131132
EXPECT_TRUE(!Err);
132133
llvm::consumeError(std::move(Err));
133134
Err = Replaces.add(Replacement("x.cc", 10, 3, "replace"));
134-
EXPECT_TRUE((bool)Err);
135+
EXPECT_TRUE(!Err);
135136
llvm::consumeError(std::move(Err));
137+
EXPECT_EQ(Replaces.size(), 2u);
136138
}
137139

138140
TEST_F(ReplacementTest, FailAddRegression) {
@@ -157,14 +159,24 @@ TEST_F(ReplacementTest, FailAddRegression) {
157159
llvm::consumeError(std::move(Err));
158160
}
159161

160-
TEST_F(ReplacementTest, FailAddInsertAtOffsetOfReplacement) {
162+
TEST_F(ReplacementTest, InsertAtOffsetOfReplacement) {
161163
Replacements Replaces;
162164
auto Err = Replaces.add(Replacement("x.cc", 10, 2, ""));
163165
EXPECT_TRUE(!Err);
164166
llvm::consumeError(std::move(Err));
165167
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
166-
EXPECT_TRUE((bool)Err);
168+
EXPECT_TRUE(!Err);
167169
llvm::consumeError(std::move(Err));
170+
EXPECT_EQ(Replaces.size(), 2u);
171+
172+
Replaces.clear();
173+
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
174+
EXPECT_TRUE(!Err);
175+
llvm::consumeError(std::move(Err));
176+
Err = Replaces.add(Replacement("x.cc", 10, 2, ""));
177+
EXPECT_TRUE(!Err);
178+
llvm::consumeError(std::move(Err));
179+
EXPECT_EQ(Replaces.size(), 2u);
168180
}
169181

170182
TEST_F(ReplacementTest, FailAddInsertAtOtherInsert) {
@@ -175,6 +187,38 @@ TEST_F(ReplacementTest, FailAddInsertAtOtherInsert) {
175187
Err = Replaces.add(Replacement("x.cc", 10, 0, "b"));
176188
EXPECT_TRUE((bool)Err);
177189
llvm::consumeError(std::move(Err));
190+
191+
Replaces.clear();
192+
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
193+
EXPECT_TRUE(!Err);
194+
llvm::consumeError(std::move(Err));
195+
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
196+
EXPECT_TRUE((bool)Err);
197+
llvm::consumeError(std::move(Err));
198+
199+
Replaces.clear();
200+
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
201+
EXPECT_TRUE(!Err);
202+
llvm::consumeError(std::move(Err));
203+
Err = Replaces.add(Replacement("x.cc", 10, 3, ""));
204+
EXPECT_TRUE(!Err);
205+
llvm::consumeError(std::move(Err));
206+
Err = Replaces.add(Replacement("x.cc", 10, 0, ""));
207+
EXPECT_TRUE((bool)Err);
208+
llvm::consumeError(std::move(Err));
209+
}
210+
211+
TEST_F(ReplacementTest, InsertBetweenAdjacentReplacements) {
212+
Replacements Replaces;
213+
auto Err = Replaces.add(Replacement("x.cc", 10, 5, "a"));
214+
EXPECT_TRUE(!Err);
215+
llvm::consumeError(std::move(Err));
216+
Err = Replaces.add(Replacement("x.cc", 8, 2, "a"));
217+
EXPECT_TRUE(!Err);
218+
llvm::consumeError(std::move(Err));
219+
Err = Replaces.add(Replacement("x.cc", 10, 0, "b"));
220+
EXPECT_TRUE(!Err);
221+
llvm::consumeError(std::move(Err));
178222
}
179223

180224
TEST_F(ReplacementTest, CanApplyReplacements) {
@@ -189,6 +233,29 @@ TEST_F(ReplacementTest, CanApplyReplacements) {
189233
EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID));
190234
}
191235

236+
// Verifies that replacement/deletion is applied before insertion at the same
237+
// offset.
238+
TEST_F(ReplacementTest, InsertAndDelete) {
239+
FileID ID = Context.createInMemoryFile("input.cpp",
240+
"line1\nline2\nline3\nline4");
241+
Replacements Replaces = toReplacements(
242+
{Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 6, ""),
243+
Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 0,
244+
"other\n")});
245+
EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
246+
EXPECT_EQ("line1\nother\nline3\nline4", Context.getRewrittenText(ID));
247+
}
248+
249+
TEST_F(ReplacementTest, AdjacentReplacements) {
250+
FileID ID = Context.createInMemoryFile("input.cpp",
251+
"ab");
252+
Replacements Replaces = toReplacements(
253+
{Replacement(Context.Sources, Context.getLocation(ID, 1, 1), 1, "x"),
254+
Replacement(Context.Sources, Context.getLocation(ID, 1, 2), 1, "y")});
255+
EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
256+
EXPECT_EQ("xy", Context.getRewrittenText(ID));
257+
}
258+
192259
TEST_F(ReplacementTest, SkipsDuplicateReplacements) {
193260
FileID ID = Context.createInMemoryFile("input.cpp",
194261
"line1\nline2\nline3\nline4");
@@ -506,6 +573,17 @@ TEST(Range, CalculateRangesOfReplacements) {
506573
EXPECT_TRUE(Ranges[1].getLength() == 22);
507574
}
508575

576+
TEST(Range, CalculateRangesOfInsertionAroundReplacement) {
577+
Replacements Replaces = toReplacements(
578+
{Replacement("foo", 0, 2, ""), Replacement("foo", 0, 0, "ba")});
579+
580+
std::vector<Range> Ranges = Replaces.getAffectedRanges();
581+
582+
EXPECT_EQ(1ul, Ranges.size());
583+
EXPECT_EQ(0u, Ranges[0].getOffset());
584+
EXPECT_EQ(2u, Ranges[0].getLength());
585+
}
586+
509587
TEST(Range, RangesAfterEmptyReplacements) {
510588
std::vector<Range> Ranges = {Range(5, 6), Range(10, 5)};
511589
Replacements Replaces;

0 commit comments

Comments
 (0)
Please sign in to comment.