[clang-tools-extra] [clangd] Implement simple folding for preprocessor branches (PR #140959)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 5 18:58:57 PDT 2025
https://github.com/aketchum15 updated https://github.com/llvm/llvm-project/pull/140959
>From f6abf494febe7becb42dd24071a3309c2d4b66d5 Mon Sep 17 00:00:00 2001
From: Ruihua Dong <dongruihua.drh at alibaba-inc.com>
Date: Fri, 17 Jan 2025 12:52:19 +0500
Subject: [PATCH 1/6] [clangd] Implement simple folding of preprocessor
branches
Extract directive branches information from DirectiveTree, fold branches
that don't end with eof.
Fixes https://github.com/clangd/clangd/issues/1661
---
.../clangd/SemanticSelection.cpp | 18 +++++++
.../clangd/support/DirectiveTree.cpp | 54 +++++++++++++++++++
.../clangd/support/DirectiveTree.h | 4 ++
3 files changed, 76 insertions(+)
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index dd7116e619e6d..57e7ff8b97c65 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/clang-tools-extra/clangd/SemanticSelection.cpp
@@ -220,6 +220,24 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto EndPosition = [&](const Token &T) {
return offsetToPosition(Code, EndOffset(T));
};
+
+ // Preprocessor directives
+ auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream);
+ for (const auto &R : PPRanges) {
+ auto BTok = OrigStream.tokens()[R.Begin];
+ auto ETok = OrigStream.tokens()[R.End];
+ if (ETok.Kind == tok::eof)
+ continue;
+ if (BTok.Line >= ETok.Line)
+ continue;
+
+ Position Start = EndPosition(BTok);
+ Position End = StartPosition(ETok);
+ if (LineFoldingOnly)
+ End.line--;
+ AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
+ }
+
auto Tokens = ParseableStream.tokens();
// Brackets.
for (const auto &Tok : Tokens) {
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp
index 7ea08add7a107..25ef1dcffd750 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.cpp
+++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp
@@ -356,5 +356,59 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const {
return Out;
}
+namespace {
+class RangePairer {
+ std::vector<Token::Range> &Ranges;
+
+public:
+ RangePairer(std::vector<Token::Range> &Ranges) : Ranges(Ranges) {}
+
+ void walk(const DirectiveTree &T) {
+ for (const auto &C : T.Chunks)
+ std::visit(*this, C);
+ }
+
+ void operator()(const DirectiveTree::Code &C) {}
+
+ void operator()(const DirectiveTree::Directive &) {}
+
+ void operator()(const DirectiveTree::Conditional &C) {
+ Token::Range Range;
+ Token::Index Last;
+ auto First = true;
+ for (const auto &B : C.Branches) {
+ if (First) {
+ First = false;
+ } else {
+ Range = {Last, B.first.Tokens.Begin};
+ Ranges.push_back(Range);
+ }
+ Last = B.first.Tokens.Begin;
+ }
+ Range = {Last, C.End.Tokens.Begin};
+ Ranges.push_back(Range);
+
+ for (const auto &B : C.Branches)
+ walk(B.second);
+ }
+};
+} // namespace
+
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+ const TokenStream &Code) {
+ std::vector<Token::Range> Ranges;
+ RangePairer(Ranges).walk(Tree);
+
+ // Transform paired ranges to start with last token in its logical line
+ for (auto &R : Ranges) {
+ const Token *Tok = &Code.tokens()[R.Begin + 1];
+ while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine))
+ ++Tok;
+ Tok = Tok - 1;
+ R.Begin = Tok->OriginalIndex;
+ }
+ return Ranges;
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.h b/clang-tools-extra/clangd/support/DirectiveTree.h
index 34f5a888863f2..373af322bca0c 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.h
+++ b/clang-tools-extra/clangd/support/DirectiveTree.h
@@ -124,6 +124,10 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
/// The choices are stored in Conditional::Taken nodes.
void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code);
+/// Pairs preprocessor conditional directives and computes their token ranges.
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+ const TokenStream &Code);
+
} // namespace clangd
} // namespace clang
>From 6ddc0539ffe3a654dd310b9e91fea6a487000a8f Mon Sep 17 00:00:00 2001
From: Adam Ketchum <ketchua at purdue.edu>
Date: Wed, 21 May 2025 16:17:57 -0400
Subject: [PATCH 2/6] Add unit test for preprocessor directive folding
---
.../unittests/SemanticSelectionTests.cpp | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
index 7faef6f95d8f9..b4249590c7f66 100644
--- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
@@ -370,6 +370,26 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
//[[ foo
/* bar */]]
)cpp",
+ R"cpp(
+ //Ignore non-conditional directives
+ #define A 1
+
+ void func() {[[
+ int Variable = 100;
+
+ #ifdef FOO[[
+ Variable = 1;
+ ]]#else[[
+ Variable = 2;
+ //handle nested directives
+ #if 1[[
+ Variable = 3;
+ ]]#endif
+ ]]#endif
+
+
+ ]]}
+ )cpp",
};
for (const char *Test : Tests) {
auto T = Annotations(Test);
>From 265f452340aa451606cb1c40469a13e44bed3a30 Mon Sep 17 00:00:00 2001
From: Adam Ketchum <ketchua at purdue.edu>
Date: Tue, 5 Aug 2025 21:11:46 -0400
Subject: [PATCH 3/6] Update to test nested pp regions in inactive regions and
incomplete pp regions
---
.../unittests/SemanticSelectionTests.cpp | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
index b4249590c7f66..ec6ef3bed54e9 100644
--- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
@@ -379,6 +379,9 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
#ifdef FOO[[
Variable = 1;
+ #if 1[[
+ Variable = 4;
+ ]]#endif
]]#else[[
Variable = 2;
//handle nested directives
@@ -390,6 +393,22 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
]]}
)cpp",
+ R"cpp(
+ int Variable = 0;
+ #if defined(WALDO)
+ Variable = 1;
+ #
+ )cpp",
+ R"cpp(
+ int Variable = 0;
+ #if defined(WALDO)[[
+ Variable = 1;
+ ]]#elif 1[[
+ Variable = 2;
+ ]]#else
+ Variable = 3;
+ #
+ )cpp",
};
for (const char *Test : Tests) {
auto T = Annotations(Test);
@@ -400,6 +419,12 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
}
}
+#if defined(WALDO)
+#elif 1
+#else
+#endif
+
+
TEST(FoldingRanges, PseudoParserLineFoldingsOnly) {
const char *Tests[] = {
R"cpp(
>From 34d8cdf40b1c010c3a50a6ab1aca05152c9e23e8 Mon Sep 17 00:00:00 2001
From: Adam Ketchum <ketchua at purdue.edu>
Date: Tue, 5 Aug 2025 21:13:34 -0400
Subject: [PATCH 4/6] Fix creating invalid rangs for incomplete PP regions
---
clang-tools-extra/clangd/support/DirectiveTree.cpp | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp
index 25ef1dcffd750..89cfa6a365150 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.cpp
+++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp
@@ -385,8 +385,11 @@ class RangePairer {
}
Last = B.first.Tokens.Begin;
}
- Range = {Last, C.End.Tokens.Begin};
- Ranges.push_back(Range);
+
+ if (C.End.Kind != tok::pp_not_keyword) {
+ Range = {Last, C.End.Tokens.Begin};
+ Ranges.push_back(Range);
+ }
for (const auto &B : C.Branches)
walk(B.second);
>From 9aa17b4e7dda37315308948b2a3adf1cf6a1f8dd Mon Sep 17 00:00:00 2001
From: Adam Ketchum <ketchua at purdue.edu>
Date: Tue, 5 Aug 2025 21:15:18 -0400
Subject: [PATCH 5/6] formatting changes and updating FIXME comment
---
clang-tools-extra/clangd/SemanticSelection.cpp | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index 57e7ff8b97c65..33b829c7744e8 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/clang-tools-extra/clangd/SemanticSelection.cpp
@@ -175,9 +175,8 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
return collectFoldingRanges(SyntaxTree, TM);
}
-// FIXME( usaxena95): Collect PP conditional regions, includes and other code
-// regions (e.g. public/private/protected sections of classes, control flow
-// statement bodies).
+// FIXME( usaxena95): Collect includes and other code regions (e.g.
+// public/private/protected sections of classes, control flow statement bodies).
// Related issue: https://github.com/clangd/clangd/issues/310
llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
@@ -186,12 +185,6 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto DirectiveStructure = DirectiveTree::parse(OrigStream);
chooseConditionalBranches(DirectiveStructure, OrigStream);
- // FIXME: Provide ranges in the disabled-PP regions as well.
- auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
-
- auto ParseableStream = cook(Preprocessed, genericLangOpts());
- pairBrackets(ParseableStream);
-
std::vector<FoldingRange> Result;
auto AddFoldingRange = [&](Position Start, Position End,
llvm::StringLiteral Kind) {
@@ -238,7 +231,13 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
}
+ auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
+
+ auto ParseableStream = cook(Preprocessed, genericLangOpts());
+ pairBrackets(ParseableStream);
+
auto Tokens = ParseableStream.tokens();
+
// Brackets.
for (const auto &Tok : Tokens) {
if (auto *Paired = Tok.pair()) {
@@ -258,6 +257,7 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
return OriginalToken(T).Length >= 2 &&
Code.substr(StartOffset(T), 2) == "/*";
};
+
// Multi-line comments.
for (auto *T = Tokens.begin(); T != Tokens.end();) {
if (T->Kind != tok::comment) {
>From 0b63c7de2db8cb660cd36d581e176de0618ed9c0 Mon Sep 17 00:00:00 2001
From: Adam Ketchum <ketchua at purdue.edu>
Date: Tue, 5 Aug 2025 21:58:42 -0400
Subject: [PATCH 6/6] remove scratchpad code
---
.../clangd/unittests/SemanticSelectionTests.cpp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
index ec6ef3bed54e9..b4d67b778a70d 100644
--- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
@@ -419,12 +419,6 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
}
}
-#if defined(WALDO)
-#elif 1
-#else
-#endif
-
-
TEST(FoldingRanges, PseudoParserLineFoldingsOnly) {
const char *Tests[] = {
R"cpp(
More information about the cfe-commits
mailing list