Skip to content

Commit 19daa21

Browse files
committed
[clangd] Rethink how SelectionTree deals with macros and #includes.
Summary: The exclusive-claim model is successful at resolving conflicts over tokens between parent/child or siblings. However claims at the spelled-token level do the wrong thing for macro expansions, where siblings can be equally associated with the macro invocation. Moreover, any model that only uses the endpoints in a range can fail when a macro invocation occurs inside the node. To address this, we use the existing TokenBuffer in more depth. Claims are expressed in terms of expanded tokens, so there is no need to worry about macros, includes etc. Once we know which expanded tokens were claimed, they are mapped onto spelled tokens for hit-testing. This mapping is fairly flexible, currently the handling of macros is pretty simple (map macro args onto spellings, other macro expansions onto the macro name token). This mapping is in principle token-by-token for correctness (though there's some batching for performance). The aggregation of the selection enum is now more principled as we need to be able to aggregate several hit-test results together. For simplicity i removed the ability to determine selectedness of TUDecl. (That was originally implemented in 90a5bf9, but doesn't seem to be very important or worth the complexity any longer). The expandedTokens(SourceLocation) helper could be added locally, but seems to make sense on TokenBuffer. Fixes clangd/clangd#202 Fixes clangd/clangd#126 Reviewers: hokein Subscribers: MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits, ilya-biryukov Tags: #clang Differential Revision: https://reviews.llvm.org/D70512
1 parent 45c843d commit 19daa21

File tree

7 files changed

+391
-153
lines changed

7 files changed

+391
-153
lines changed

clang-tools-extra/clangd/Selection.cpp

+279-135
Large diffs are not rendered by default.

clang-tools-extra/clangd/Selection.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class SelectionTree {
7676
unsigned Start, unsigned End);
7777

7878
// Describes to what extent an AST node is covered by the selection.
79-
enum Selection {
79+
enum Selection : unsigned char {
8080
// The AST node owns no characters covered by the selection.
8181
// Note that characters owned by children don't count:
8282
// if (x == 0) scream();

clang-tools-extra/clangd/unittests/SelectionTests.cpp

+63-3
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ TEST(SelectionTest, CommonAncestor) {
134134
)cpp",
135135
"IfStmt",
136136
},
137+
{
138+
R"cpp(
139+
int x(int);
140+
#define M(foo) x(foo)
141+
int a = 42;
142+
int b = M([[^a]]);
143+
)cpp",
144+
"DeclRefExpr",
145+
},
137146
{
138147
R"cpp(
139148
void foo();
@@ -378,6 +387,7 @@ TEST(SelectionTest, Selected) {
378387
$C[[return]];
379388
}]] else [[{^
380389
}]]]]
390+
char z;
381391
}
382392
)cpp",
383393
R"cpp(
@@ -386,10 +396,10 @@ TEST(SelectionTest, Selected) {
386396
void foo(^$C[[unique_ptr<$C[[unique_ptr<$C[[int]]>]]>]]^ a) {}
387397
)cpp",
388398
R"cpp(int a = [[5 >^> 1]];)cpp",
389-
R"cpp([[
399+
R"cpp(
390400
#define ECHO(X) X
391-
ECHO(EC^HO([[$C[[int]]) EC^HO(a]]));
392-
]])cpp",
401+
ECHO(EC^HO($C[[int]]) EC^HO(a));
402+
)cpp",
393403
R"cpp( $C[[^$C[[int]] a^]]; )cpp",
394404
R"cpp( $C[[^$C[[int]] a = $C[[5]]^]]; )cpp",
395405
};
@@ -428,6 +438,56 @@ TEST(SelectionTest, PathologicalPreprocessor) {
428438
EXPECT_EQ("WhileStmt", T.commonAncestor()->Parent->kind());
429439
}
430440

441+
TEST(SelectionTest, IncludedFile) {
442+
const char *Case = R"cpp(
443+
void test() {
444+
#include "Exp^and.inc"
445+
break;
446+
}
447+
)cpp";
448+
Annotations Test(Case);
449+
auto TU = TestTU::withCode(Test.code());
450+
TU.AdditionalFiles["Expand.inc"] = "while(1)\n";
451+
auto AST = TU.build();
452+
auto T = makeSelectionTree(Case, AST);
453+
454+
EXPECT_EQ("WhileStmt", T.commonAncestor()->kind());
455+
}
456+
457+
TEST(SelectionTest, MacroArgExpansion) {
458+
// If a macro arg is expanded several times, we consider them all selected.
459+
const char *Case = R"cpp(
460+
int mul(int, int);
461+
#define SQUARE(X) mul(X, X);
462+
int nine = SQUARE(^3);
463+
)cpp";
464+
Annotations Test(Case);
465+
auto AST = TestTU::withCode(Test.code()).build();
466+
auto T = makeSelectionTree(Case, AST);
467+
// Unfortunately, this makes the common ancestor the CallExpr...
468+
// FIXME: hack around this by picking one?
469+
EXPECT_EQ("CallExpr", T.commonAncestor()->kind());
470+
EXPECT_FALSE(T.commonAncestor()->Selected);
471+
EXPECT_EQ(2u, T.commonAncestor()->Children.size());
472+
for (const auto* N : T.commonAncestor()->Children) {
473+
EXPECT_EQ("IntegerLiteral", N->kind());
474+
EXPECT_TRUE(N->Selected);
475+
}
476+
477+
// Verify that the common assert() macro doesn't suffer from this.
478+
// (This is because we don't associate the stringified token with the arg).
479+
Case = R"cpp(
480+
void die(const char*);
481+
#define assert(x) (x ? (void)0 : die(#x)
482+
void foo() { assert(^42); }
483+
)cpp";
484+
Test = Annotations(Case);
485+
AST = TestTU::withCode(Test.code()).build();
486+
T = makeSelectionTree(Case, AST);
487+
488+
EXPECT_EQ("IntegerLiteral", T.commonAncestor()->kind());
489+
}
490+
431491
TEST(SelectionTest, Implicit) {
432492
const char* Test = R"cpp(
433493
struct S { S(const char*); };

clang-tools-extra/clangd/unittests/TweakTests.cpp

+12-14
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ TEST_F(ExtractVariableTest, Test) {
269269
EXPECT_UNAVAILABLE(UnavailableCases);
270270

271271
// vector of pairs of input and output strings
272-
const std::vector<std::pair<llvm::StringLiteral, llvm::StringLiteral>>
272+
const std::vector<std::pair<std::string, std::string>>
273273
InputOutputs = {
274274
// extraction from variable declaration/assignment
275275
{R"cpp(void varDecl() {
@@ -321,17 +321,10 @@ TEST_F(ExtractVariableTest, Test) {
321321
if(1)
322322
LOOP(5 + [[3]])
323323
})cpp",
324-
/*FIXME: It should be extracted like this. SelectionTree needs to be
325-
* fixed for macros.
326324
R"cpp(#define LOOP(x) while (1) {a = x;}
327-
void f(int a) {
328-
auto dummy = 3; if(1)
329-
LOOP(5 + dummy)
330-
})cpp"},*/
331-
R"cpp(#define LOOP(x) while (1) {a = x;}
332325
void f(int a) {
333-
auto dummy = LOOP(5 + 3); if(1)
334-
dummy
326+
auto dummy = 3; if(1)
327+
LOOP(5 + dummy)
335328
})cpp"},
336329
{R"cpp(#define LOOP(x) do {x;} while(1);
337330
void f(int a) {
@@ -644,13 +637,18 @@ void f(const int c) {
644637
)cpp";
645638
EXPECT_EQ(apply(TemplateFailInput), "unavailable");
646639

647-
// FIXME: This should be extractable after selectionTree works correctly for
648-
// macros (currently it doesn't select anything for the following case)
649-
std::string MacroFailInput = R"cpp(
640+
std::string MacroInput = R"cpp(
650641
#define F(BODY) void f() { BODY }
651642
F ([[int x = 0;]])
652643
)cpp";
653-
EXPECT_EQ(apply(MacroFailInput), "unavailable");
644+
std::string MacroOutput = R"cpp(
645+
#define F(BODY) void f() { BODY }
646+
void extracted() {
647+
int x = 0;
648+
}
649+
F (extracted();)
650+
)cpp";
651+
EXPECT_EQ(apply(MacroInput), MacroOutput);
654652

655653
// Shouldn't crash.
656654
EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");

clang/include/clang/Tooling/Syntax/Tokens.h

+5
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,18 @@ class TokenBuffer {
175175
/// All tokens produced by the preprocessor after all macro replacements,
176176
/// directives, etc. Source locations found in the clang AST will always
177177
/// point to one of these tokens.
178+
/// Tokens are in TU order (per SourceManager::isBeforeInTranslationUnit()).
178179
/// FIXME: figure out how to handle token splitting, e.g. '>>' can be split
179180
/// into two '>' tokens by the parser. However, TokenBuffer currently
180181
/// keeps it as a single '>>' token.
181182
llvm::ArrayRef<syntax::Token> expandedTokens() const {
182183
return ExpandedTokens;
183184
}
184185

186+
/// Returns the subrange of expandedTokens() corresponding to the closed
187+
/// token range R.
188+
llvm::ArrayRef<syntax::Token> expandedTokens(SourceRange R) const;
189+
185190
/// Find the subrange of spelled tokens that produced the corresponding \p
186191
/// Expanded tokens.
187192
///

clang/lib/Tooling/Syntax/Tokens.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ llvm::StringRef FileRange::text(const SourceManager &SM) const {
119119
return Text.substr(Begin, length());
120120
}
121121

122+
llvm::ArrayRef<syntax::Token> TokenBuffer::expandedTokens(SourceRange R) const {
123+
if (R.isInvalid())
124+
return {};
125+
const Token *Begin =
126+
llvm::partition_point(expandedTokens(), [&](const syntax::Token &T) {
127+
return SourceMgr->isBeforeInTranslationUnit(T.location(), R.getBegin());
128+
});
129+
const Token *End =
130+
llvm::partition_point(expandedTokens(), [&](const syntax::Token &T) {
131+
return !SourceMgr->isBeforeInTranslationUnit(R.getEnd(), T.location());
132+
});
133+
if (Begin > End)
134+
return {};
135+
return {Begin, End};
136+
}
137+
122138
std::pair<const syntax::Token *, const TokenBuffer::Mapping *>
123139
TokenBuffer::spelledForExpandedToken(const syntax::Token *Expanded) const {
124140
assert(Expanded);

clang/unittests/Tooling/Syntax/TokensTest.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "llvm/Support/raw_ostream.h"
4141
#include "llvm/Testing/Support/Annotations.h"
4242
#include "llvm/Testing/Support/SupportHelpers.h"
43+
#include "gmock/gmock.h"
4344
#include <cassert>
4445
#include <cstdlib>
4546
#include <gmock/gmock.h>
@@ -663,6 +664,20 @@ TEST_F(TokenBufferTest, SpelledByExpanded) {
663664
ValueIs(SameRange(findSpelled("not_mapped"))));
664665
}
665666

667+
TEST_F(TokenBufferTest, ExpandedTokensForRange) {
668+
recordTokens(R"cpp(
669+
#define SIGN(X) X##_washere
670+
A SIGN(B) C SIGN(D) E SIGN(F) G
671+
)cpp");
672+
673+
SourceRange R(findExpanded("C").front().location(),
674+
findExpanded("F_washere").front().location());
675+
// Sanity check: expanded and spelled tokens are stored separately.
676+
EXPECT_THAT(Buffer.expandedTokens(R),
677+
SameRange(findExpanded("C D_washere E F_washere")));
678+
EXPECT_THAT(Buffer.expandedTokens(SourceRange()), testing::IsEmpty());
679+
}
680+
666681
TEST_F(TokenBufferTest, ExpansionStartingAt) {
667682
// Object-like macro expansions.
668683
recordTokens(R"cpp(

0 commit comments

Comments
 (0)