Skip to content

Commit da76a48

Browse files
yuhaouyymand
andauthored
[clang][transformer] Add merge range-selector for selecting the merge of two ranges. (llvm#169560)
This new range-selector `merge` takes in two ranges and selects from min(begin locs of input ranges) to max(end locs of input ranges). This is useful for when the user needs to select a range that is a merge of two arbitrary ranges (potentially overlapped and out of order). The existing `enclose` range-selector does something similar but it requires the first range's begin loc appears before the second range's end loc. The `merge` range-selector complements `enclose`. --------- Co-authored-by: Yitzhak Mandelbaum <[email protected]>
1 parent b9b9a23 commit da76a48

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ RangeSelector enclose(RangeSelector Begin, RangeSelector End);
3737
/// Convenience version of \c range where end-points are bound nodes.
3838
RangeSelector encloseNodes(std::string BeginID, std::string EndID);
3939

40+
/// Selects the merge of the two ranges, i.e. from min(First.begin,
41+
/// Second.begin) to max(First.end, Second.end).
42+
RangeSelector merge(RangeSelector First, RangeSelector Second);
43+
4044
/// DEPRECATED. Use `enclose`.
4145
inline RangeSelector range(RangeSelector Begin, RangeSelector End) {
4246
return enclose(std::move(Begin), std::move(End));

clang/lib/Tooling/Transformer/Parsing.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ getBinaryStringSelectors() {
108108
static const llvm::StringMap<RangeSelectorOp<RangeSelector, RangeSelector>> &
109109
getBinaryRangeSelectors() {
110110
static const llvm::StringMap<RangeSelectorOp<RangeSelector, RangeSelector>>
111-
M = {{"enclose", enclose}, {"between", between}};
111+
M = {{"enclose", enclose}, {"between", between}, {"merge", merge}};
112112
return M;
113113
}
114114

clang/lib/Tooling/Transformer/RangeSelector.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,63 @@ RangeSelector transformer::encloseNodes(std::string BeginID,
178178
return transformer::enclose(node(std::move(BeginID)), node(std::move(EndID)));
179179
}
180180

181+
RangeSelector transformer::merge(RangeSelector First, RangeSelector Second) {
182+
return [First,
183+
Second](const MatchResult &Result) -> Expected<CharSourceRange> {
184+
Expected<CharSourceRange> FirstRange = First(Result);
185+
if (!FirstRange)
186+
return FirstRange.takeError();
187+
Expected<CharSourceRange> SecondRange = Second(Result);
188+
if (!SecondRange)
189+
return SecondRange.takeError();
190+
191+
SourceLocation FirstB = FirstRange->getBegin();
192+
SourceLocation FirstE = FirstRange->getEnd();
193+
SourceLocation SecondB = SecondRange->getBegin();
194+
SourceLocation SecondE = SecondRange->getEnd();
195+
// Result begin loc is the minimum of the begin locs of the two ranges.
196+
SourceLocation B =
197+
Result.SourceManager->isBeforeInTranslationUnit(FirstB, SecondB)
198+
? FirstB
199+
: SecondB;
200+
if (FirstRange->isTokenRange() && SecondRange->isTokenRange()) {
201+
// Both ranges are token ranges. Just take the maximum of their end locs.
202+
SourceLocation E =
203+
Result.SourceManager->isBeforeInTranslationUnit(FirstE, SecondE)
204+
? SecondE
205+
: FirstE;
206+
return CharSourceRange::getTokenRange(B, E);
207+
}
208+
209+
if (FirstRange->isTokenRange()) {
210+
// The end of the first range is a token. Need to resolve the token to a
211+
// char range.
212+
FirstE = Lexer::getLocForEndOfToken(FirstE, /*Offset=*/0,
213+
*Result.SourceManager,
214+
Result.Context->getLangOpts());
215+
if (FirstE.isInvalid())
216+
return invalidArgumentError(
217+
"merge: can't resolve first token range to valid source range");
218+
}
219+
if (SecondRange->isTokenRange()) {
220+
// The end of the second range is a token. Need to resolve the token to a
221+
// char range.
222+
SecondE = Lexer::getLocForEndOfToken(SecondE, /*Offset=*/0,
223+
*Result.SourceManager,
224+
Result.Context->getLangOpts());
225+
if (SecondE.isInvalid())
226+
return invalidArgumentError(
227+
"merge: can't resolve second token range to valid source range");
228+
}
229+
// Result end loc is the maximum of the end locs of the two ranges.
230+
SourceLocation E =
231+
Result.SourceManager->isBeforeInTranslationUnit(FirstE, SecondE)
232+
? SecondE
233+
: FirstE;
234+
return CharSourceRange::getCharRange(B, E);
235+
};
236+
}
237+
181238
RangeSelector transformer::member(std::string ID) {
182239
return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {
183240
Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);

clang/unittests/Tooling/RangeSelectorTest.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,45 @@ TEST(RangeSelectorTest, EncloseOpGeneralParsed) {
327327
EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("3, 7"));
328328
}
329329

330+
TEST(RangeSelectorTest, MergeOp) {
331+
StringRef Code = R"cc(
332+
int f(int x, int y, int z) { return 3; }
333+
int g() { return f(/* comment */ 3, 7 /* comment */, 9); }
334+
)cc";
335+
auto Matcher = callExpr(hasArgument(0, expr().bind("a0")),
336+
hasArgument(1, expr().bind("a1")),
337+
hasArgument(2, expr().bind("a2")));
338+
RangeSelector R = merge(node("a0"), node("a1"));
339+
TestMatch Match = matchCode(Code, Matcher);
340+
EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7"));
341+
// Test the merge of two non-contiguous and out-of-order token-ranges.
342+
R = merge(node("a2"), node("a0"));
343+
EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7 /* comment */, 9"));
344+
// Test the merge of a token-range (expr node) with a char-range (before).
345+
R = merge(node("a1"), before(node("a0")));
346+
EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, 7"));
347+
// Test the merge of two char-ranges.
348+
R = merge(before(node("a0")), before(node("a1")));
349+
EXPECT_THAT_EXPECTED(select(R, Match), HasValue("3, "));
350+
}
351+
352+
TEST(RangeSelectorTest, MergeOpParsed) {
353+
StringRef Code = R"cc(
354+
int f(int x, int y, int z) { return 3; }
355+
int g() { return f(/* comment */ 3, 7 /* comment */, 9); }
356+
)cc";
357+
auto Matcher = callExpr(hasArgument(0, expr().bind("a0")),
358+
hasArgument(1, expr().bind("a1")),
359+
hasArgument(2, expr().bind("a2")));
360+
auto R = parseRangeSelector(R"rs(merge(node("a0"), node("a1")))rs");
361+
ASSERT_THAT_EXPECTED(R, llvm::Succeeded());
362+
TestMatch Match = matchCode(Code, Matcher);
363+
EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("3, 7"));
364+
R = parseRangeSelector(R"rs(merge(node("a2"), node("a1")))rs");
365+
ASSERT_THAT_EXPECTED(R, llvm::Succeeded());
366+
EXPECT_THAT_EXPECTED(select(*R, Match), HasValue("7 /* comment */, 9"));
367+
}
368+
330369
TEST(RangeSelectorTest, NodeOpStatement) {
331370
StringRef Code = "int f() { return 3; }";
332371
TestMatch Match = matchCode(Code, returnStmt().bind("id"));

0 commit comments

Comments
 (0)