[clang] [clang] add transformer range selector for spelled ranges (PR #177442)
Tobias Ribizel via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 23 08:15:13 PST 2026
https://github.com/upsj updated https://github.com/llvm/llvm-project/pull/177442
>From 0b997c6786c68aa29278d27900ce1ecd2e09bb32 Mon Sep 17 00:00:00 2001
From: Tobias Ribizel <mail at ribizel.de>
Date: Thu, 22 Jan 2026 20:20:45 +0100
Subject: [PATCH 1/3] format files
---
clang/lib/Tooling/Transformer/RangeSelector.cpp | 3 +--
clang/unittests/Tooling/RangeSelectorTest.cpp | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Tooling/Transformer/RangeSelector.cpp b/clang/lib/Tooling/Transformer/RangeSelector.cpp
index 68b16f91652fb..8ba065f98864e 100644
--- a/clang/lib/Tooling/Transformer/RangeSelector.cpp
+++ b/clang/lib/Tooling/Transformer/RangeSelector.cpp
@@ -405,8 +405,7 @@ RangeSelector transformer::constructExprArgs(std::string ID) {
namespace {
// Returns the range of the elements of the initializer list. Includes all
// source between the braces.
-CharSourceRange getElementsRange(const MatchResult &,
- const InitListExpr &E) {
+CharSourceRange getElementsRange(const MatchResult &, const InitListExpr &E) {
return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1),
E.getRBraceLoc());
}
diff --git a/clang/unittests/Tooling/RangeSelectorTest.cpp b/clang/unittests/Tooling/RangeSelectorTest.cpp
index 9e83fa1dc92ff..543902bcafeab 100644
--- a/clang/unittests/Tooling/RangeSelectorTest.cpp
+++ b/clang/unittests/Tooling/RangeSelectorTest.cpp
@@ -173,7 +173,7 @@ TEST(RangeSelectorTest, AfterOp) {
)cc";
StringRef Call = "call";
TestMatch Match = matchCode(Code, callExpr().bind(Call));
- const auto* E = Match.Result.Nodes.getNodeAs<Expr>(Call);
+ const auto *E = Match.Result.Nodes.getNodeAs<Expr>(Call);
assert(E != nullptr);
const SourceRange Range = E->getSourceRange();
// The end token, a right paren, is one character wide, so advance by one,
>From 6c2dbd6cbf7df3998c6121d9902cb77e1310c217 Mon Sep 17 00:00:00 2001
From: Tobias Ribizel <mail at ribizel.de>
Date: Thu, 22 Jan 2026 20:22:37 +0100
Subject: [PATCH 2/3] [clang] add transformer range selector for spelled ranges
This supports transformer-based text replacements inside
macro definitions using the new spelled(...) function
as well as retrieving names of declarations inside
macro definitions using the existing name(...) function
---
.../clang/Tooling/Transformer/RangeSelector.h | 5 ++
.../lib/Tooling/Transformer/RangeSelector.cpp | 23 +++++++++
clang/unittests/Tooling/RangeSelectorTest.cpp | 48 +++++++++++++++----
3 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Tooling/Transformer/RangeSelector.h b/clang/include/clang/Tooling/Transformer/RangeSelector.h
index c76a5106edd65..24cdc9133b5f3 100644
--- a/clang/include/clang/Tooling/Transformer/RangeSelector.h
+++ b/clang/include/clang/Tooling/Transformer/RangeSelector.h
@@ -111,6 +111,11 @@ RangeSelector elseBranch(std::string ID);
/// source), if `S` is an expansion, and `S` itself, otherwise. Corresponds to
/// `SourceManager::getExpansionRange`.
RangeSelector expansion(RangeSelector S);
+
+/// Selects the spelling range of `S` if it is spelled inside a macro
+/// definition, and `S` itself, otherwise. Corresponds to calling
+/// `SourceManager::getSpellingLoc` on both endpoints.
+RangeSelector spelled(RangeSelector S);
} // namespace transformer
} // namespace clang
diff --git a/clang/lib/Tooling/Transformer/RangeSelector.cpp b/clang/lib/Tooling/Transformer/RangeSelector.cpp
index 8ba065f98864e..9e40620e1f91d 100644
--- a/clang/lib/Tooling/Transformer/RangeSelector.cpp
+++ b/clang/lib/Tooling/Transformer/RangeSelector.cpp
@@ -257,6 +257,8 @@ RangeSelector transformer::name(std::string ID) {
if (!D->getDeclName().isIdentifier())
return missingPropertyError(ID, "name", "identifier");
SourceLocation L = D->getLocation();
+ // the name may be spelled in a macro
+ L = Result.SourceManager->getSpellingLoc(L);
auto R = CharSourceRange::getTokenRange(L, L);
// Verify that the range covers exactly the name.
// FIXME: extend this code to support cases like `operator +` or
@@ -436,3 +438,24 @@ RangeSelector transformer::expansion(RangeSelector S) {
return Result.SourceManager->getExpansionRange(*SRange);
};
}
+
+RangeSelector transformer::spelled(RangeSelector S) {
+ return [S](const MatchResult &Result) -> Expected<CharSourceRange> {
+ Expected<CharSourceRange> SRange = S(Result);
+ if (!SRange)
+ return SRange.takeError();
+ if (!SRange->isTokenRange()) {
+ return invalidArgumentError("spelled: only supports token ranges");
+ }
+ const auto &SM = *Result.SourceManager;
+ const auto B = SRange->getBegin();
+ const auto E = SRange->getEnd();
+ if (SM.getFileID(B) != SM.getFileID(E)) {
+ return invalidArgumentError(
+ "spelled: range crosses file/macro boundaries");
+ }
+ return CharSourceRange(
+ SourceRange(SM.getSpellingLoc(B), SM.getSpellingLoc(E)),
+ SRange->isTokenRange());
+ };
+}
diff --git a/clang/unittests/Tooling/RangeSelectorTest.cpp b/clang/unittests/Tooling/RangeSelectorTest.cpp
index 543902bcafeab..7fd71177dfc03 100644
--- a/clang/unittests/Tooling/RangeSelectorTest.cpp
+++ b/clang/unittests/Tooling/RangeSelectorTest.cpp
@@ -583,19 +583,14 @@ TEST(RangeSelectorTest, NameOpDeclInMacroArg) {
EXPECT_THAT_EXPECTED(select(name(ID), Match), HasValue("x"));
}
-TEST(RangeSelectorTest, NameOpDeclInMacroBodyError) {
+TEST(RangeSelectorTest, NameSpelledInMacro) {
StringRef Code = R"cc(
- #define MACRO int x;
- MACRO
+ #define NAME foo;
+ int NAME;
)cc";
const char *ID = "id";
TestMatch Match = matchCode(Code, varDecl().bind(ID));
- EXPECT_THAT_EXPECTED(
- name(ID)(Match.Result),
- Failed<StringError>(testing::Property(
- &StringError::getMessage,
- AllOf(HasSubstr("range selected by name(node id="),
- HasSubstr("' is different from decl name 'x'")))));
+ EXPECT_THAT_EXPECTED(select(name(ID), Match), HasValue("foo"));
}
TEST(RangeSelectorTest, CallArgsOp) {
@@ -924,6 +919,41 @@ TEST(RangeSelectorTest, ExpansionOpPartial) {
HasValue("BADDECL(x * x)"));
}
+TEST(RangeSelectorTest, SpelledFullyInMacro) {
+ StringRef Code = R"cc(
+ #define MACRO int i;
+ MACRO
+ )cc";
+ const char *ID = "id";
+ TestMatch Match = matchCode(Code, varDecl().bind(ID));
+ EXPECT_THAT_EXPECTED(select(spelled(node(ID)), Match), HasValue("int i"));
+}
+
+TEST(RangeSelectorTest, SpelledWithArgumentInMacro) {
+ StringRef Code = R"cc(
+ #define MACRO(T) void foo(T t);
+ MACRO(int)
+ )cc";
+ const char *ID = "id";
+ TestMatch Match = matchCode(Code, functionDecl().bind(ID));
+ EXPECT_THAT_EXPECTED(select(spelled(node(ID)), Match),
+ HasValue("void foo(T t)"));
+}
+
+TEST(RangeSelectorTest, SpelledPartiallyInMacro) {
+ StringRef Code = R"cc(
+ #define MACRO(T) foo(T t); int i;
+ void MACRO(int);
+ )cc";
+ const char *ID = "id";
+ TestMatch Match = matchCode(Code, functionDecl().bind(ID));
+ EXPECT_THAT_EXPECTED(
+ select(spelled(node(ID)), Match),
+ Failed<StringError>(testing::Property(
+ &StringError::getMessage,
+ HasSubstr("spelled: range crosses file/macro boundaries"))));
+}
+
TEST(RangeSelectorTest, IfBoundOpBound) {
StringRef Code = R"cc(
int f() {
>From 93fdc614c2940054ae07c51a299783b696c6d2a3 Mon Sep 17 00:00:00 2001
From: Tobias Ribizel <mail at ribizel.de>
Date: Fri, 23 Jan 2026 17:14:43 +0100
Subject: [PATCH 3/3] [clang] less restrictive spelled(...) selector
---
clang/lib/Tooling/Transformer/RangeSelector.cpp | 7 -------
1 file changed, 7 deletions(-)
diff --git a/clang/lib/Tooling/Transformer/RangeSelector.cpp b/clang/lib/Tooling/Transformer/RangeSelector.cpp
index 9e40620e1f91d..6e5d12a500bac 100644
--- a/clang/lib/Tooling/Transformer/RangeSelector.cpp
+++ b/clang/lib/Tooling/Transformer/RangeSelector.cpp
@@ -444,16 +444,9 @@ RangeSelector transformer::spelled(RangeSelector S) {
Expected<CharSourceRange> SRange = S(Result);
if (!SRange)
return SRange.takeError();
- if (!SRange->isTokenRange()) {
- return invalidArgumentError("spelled: only supports token ranges");
- }
const auto &SM = *Result.SourceManager;
const auto B = SRange->getBegin();
const auto E = SRange->getEnd();
- if (SM.getFileID(B) != SM.getFileID(E)) {
- return invalidArgumentError(
- "spelled: range crosses file/macro boundaries");
- }
return CharSourceRange(
SourceRange(SM.getSpellingLoc(B), SM.getSpellingLoc(E)),
SRange->isTokenRange());
More information about the cfe-commits
mailing list