Skip to content

Commit e70a9f3

Browse files
[clangd] Handle go-to-definition in macro invocations where the target appears in the expansion multiple times
Fixes clangd/clangd#234 Reviewers: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D72041
1 parent fca49fe commit e70a9f3

File tree

4 files changed

+66
-29
lines changed

4 files changed

+66
-29
lines changed

clang-tools-extra/clangd/Selection.cpp

+42-13
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ using ast_type_traits::DynTypedNode;
5252
// On traversing an AST node, its token range is erased from the unclaimed set.
5353
// The tokens actually removed are associated with that node, and hit-tested
5454
// against the selection to determine whether the node is selected.
55-
template <typename T>
56-
class IntervalSet {
55+
template <typename T> class IntervalSet {
5756
public:
5857
IntervalSet(llvm::ArrayRef<T> Range) { UnclaimedRanges.insert(Range); }
5958

@@ -78,7 +77,7 @@ class IntervalSet {
7877
--Overlap.first;
7978
// ...unless B isn't selected at all.
8079
if (Overlap.first->end() <= Claim.begin())
81-
++Overlap.first;
80+
++Overlap.first;
8281
}
8382
if (Overlap.first == Overlap.second)
8483
return Out;
@@ -118,8 +117,7 @@ class IntervalSet {
118117
};
119118

120119
// Disjoint sorted unclaimed ranges of expanded tokens.
121-
std::set<llvm::ArrayRef<T>, RangeLess>
122-
UnclaimedRanges;
120+
std::set<llvm::ArrayRef<T>, RangeLess> UnclaimedRanges;
123121
};
124122

125123
// Sentinel value for the selectedness of a node where we've seen no tokens yet.
@@ -148,11 +146,37 @@ bool shouldIgnore(const syntax::Token &Tok) {
148146
return Tok.kind() == tok::comment || Tok.kind() == tok::semi;
149147
}
150148

149+
// Determine whether 'Target' is the first expansion of the macro
150+
// argument whose top-level spelling location is 'SpellingLoc'.
151+
bool isFirstExpansion(FileID Target, SourceLocation SpellingLoc,
152+
const SourceManager &SM) {
153+
SourceLocation Prev = SpellingLoc;
154+
while (true) {
155+
// If the arg is expanded multiple times, getMacroArgExpandedLocation()
156+
// returns the first expansion.
157+
SourceLocation Next = SM.getMacroArgExpandedLocation(Prev);
158+
// So if we reach the target, target is the first-expansion of the
159+
// first-expansion ...
160+
if (SM.getFileID(Next) == Target)
161+
return true;
162+
163+
// Otherwise, if the FileID stops changing, we've reached the innermost
164+
// macro expansion, and Target was on a different branch.
165+
if (SM.getFileID(Next) == SM.getFileID(Prev))
166+
return false;
167+
168+
Prev = Next;
169+
}
170+
return false;
171+
}
172+
151173
// SelectionTester can determine whether a range of tokens from the PP-expanded
152174
// stream (corresponding to an AST node) is considered selected.
153175
//
154176
// When the tokens result from macro expansions, the appropriate tokens in the
155177
// main file are examined (macro invocation or args). Similarly for #includes.
178+
// However, only the first expansion of a given spelled token is considered
179+
// selected.
156180
//
157181
// It tests each token in the range (not just the endpoints) as contiguous
158182
// expanded tokens may not have contiguous spellings (with macros).
@@ -260,9 +284,14 @@ class SelectionTester {
260284
// Handle tokens that were passed as a macro argument.
261285
SourceLocation ArgStart = SM.getTopMacroCallerLoc(StartLoc);
262286
if (SM.getFileID(ArgStart) == SelFile) {
263-
SourceLocation ArgEnd = SM.getTopMacroCallerLoc(Batch.back().location());
264-
return testTokenRange(SM.getFileOffset(ArgStart),
265-
SM.getFileOffset(ArgEnd));
287+
if (isFirstExpansion(FID, ArgStart, SM)) {
288+
SourceLocation ArgEnd =
289+
SM.getTopMacroCallerLoc(Batch.back().location());
290+
return testTokenRange(SM.getFileOffset(ArgStart),
291+
SM.getFileOffset(ArgEnd));
292+
} else {
293+
/* fall through and treat as part of the macro body */
294+
}
266295
}
267296

268297
// Handle tokens produced by non-argument macro expansion.
@@ -346,7 +375,7 @@ std::string printNodeToString(const DynTypedNode &N, const PrintingPolicy &PP) {
346375
}
347376
#endif
348377

349-
bool isImplicit(const Stmt* S) {
378+
bool isImplicit(const Stmt *S) {
350379
// Some Stmts are implicit and shouldn't be traversed, but there's no
351380
// "implicit" attribute on Stmt/Expr.
352381
// Unwrap implicit casts first if present (other nodes too?).
@@ -611,7 +640,7 @@ class SelectionVisitor : public RecursiveASTVisitor<SelectionVisitor> {
611640
// int (*[[s]])();
612641
else if (auto *VD = llvm::dyn_cast<VarDecl>(D))
613642
return VD->getLocation();
614-
} else if (const auto* CCI = N.get<CXXCtorInitializer>()) {
643+
} else if (const auto *CCI = N.get<CXXCtorInitializer>()) {
615644
// : [[b_]](42)
616645
return CCI->getMemberLocation();
617646
}
@@ -747,10 +776,10 @@ const Node *SelectionTree::commonAncestor() const {
747776
return Ancestor != Root ? Ancestor : nullptr;
748777
}
749778

750-
const DeclContext& SelectionTree::Node::getDeclContext() const {
751-
for (const Node* CurrentNode = this; CurrentNode != nullptr;
779+
const DeclContext &SelectionTree::Node::getDeclContext() const {
780+
for (const Node *CurrentNode = this; CurrentNode != nullptr;
752781
CurrentNode = CurrentNode->Parent) {
753-
if (const Decl* Current = CurrentNode->ASTNode.get<Decl>()) {
782+
if (const Decl *Current = CurrentNode->ASTNode.get<Decl>()) {
754783
if (CurrentNode != this)
755784
if (auto *DC = dyn_cast<DeclContext>(Current))
756785
return *DC;

clang-tools-extra/clangd/Selection.h

+6-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ namespace clangd {
6464
//
6565
// SelectionTree tries to behave sensibly in the presence of macros, but does
6666
// not model any preprocessor concepts: the output is a subset of the AST.
67+
// When a macro argument is specifically selected, only its first expansion is
68+
// selected in the AST. (Returning a selection forest is unreasonably difficult
69+
// for callers to handle correctly.)
6770
//
6871
// Comments, directives and whitespace are completely ignored.
6972
// Semicolons are also ignored, as the AST generally does not model them well.
@@ -127,15 +130,15 @@ class SelectionTree {
127130
Selection Selected;
128131
// Walk up the AST to get the DeclContext of this Node,
129132
// which is not the node itself.
130-
const DeclContext& getDeclContext() const;
133+
const DeclContext &getDeclContext() const;
131134
// Printable node kind, like "CXXRecordDecl" or "AutoTypeLoc".
132135
std::string kind() const;
133136
// If this node is a wrapper with no syntax (e.g. implicit cast), return
134137
// its contents. (If multiple wrappers are present, unwraps all of them).
135-
const Node& ignoreImplicit() const;
138+
const Node &ignoreImplicit() const;
136139
// If this node is inside a wrapper with no syntax (e.g. implicit cast),
137140
// return that wrapper. (If multiple are present, unwraps all of them).
138-
const Node& outerImplicit() const;
141+
const Node &outerImplicit() const;
139142
};
140143
// The most specific common ancestor of all the selected nodes.
141144
// Returns nullptr if the common ancestor is the root.

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

+6-12
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ TEST(SelectionTest, CommonAncestor) {
415415

416416
// Regression test: this used to match the injected X, not the outer X.
417417
TEST(SelectionTest, InjectedClassName) {
418-
const char* Code = "struct ^X { int x; };";
418+
const char *Code = "struct ^X { int x; };";
419419
auto AST = TestTU::withCode(Annotations(Code).code()).build();
420420
auto T = makeSelectionTree(Code, AST);
421421
ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T;
@@ -508,7 +508,8 @@ TEST(SelectionTest, IncludedFile) {
508508
}
509509

510510
TEST(SelectionTest, MacroArgExpansion) {
511-
// If a macro arg is expanded several times, we consider them all selected.
511+
// If a macro arg is expanded several times, we only consider the first one
512+
// selected.
512513
const char *Case = R"cpp(
513514
int mul(int, int);
514515
#define SQUARE(X) mul(X, X);
@@ -517,15 +518,8 @@ TEST(SelectionTest, MacroArgExpansion) {
517518
Annotations Test(Case);
518519
auto AST = TestTU::withCode(Test.code()).build();
519520
auto T = makeSelectionTree(Case, AST);
520-
// Unfortunately, this makes the common ancestor the CallExpr...
521-
// FIXME: hack around this by picking one?
522-
EXPECT_EQ("CallExpr", T.commonAncestor()->kind());
523-
EXPECT_FALSE(T.commonAncestor()->Selected);
524-
EXPECT_EQ(2u, T.commonAncestor()->Children.size());
525-
for (const auto* N : T.commonAncestor()->Children) {
526-
EXPECT_EQ("IntegerLiteral", N->kind());
527-
EXPECT_TRUE(N->Selected);
528-
}
521+
EXPECT_EQ("IntegerLiteral", T.commonAncestor()->kind());
522+
EXPECT_TRUE(T.commonAncestor()->Selected);
529523

530524
// Verify that the common assert() macro doesn't suffer from this.
531525
// (This is because we don't associate the stringified token with the arg).
@@ -542,7 +536,7 @@ TEST(SelectionTest, MacroArgExpansion) {
542536
}
543537

544538
TEST(SelectionTest, Implicit) {
545-
const char* Test = R"cpp(
539+
const char *Test = R"cpp(
546540
struct S { S(const char*); };
547541
int f(S);
548542
int x = f("^");

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,18 @@ TEST(LocateSymbol, All) {
338338
#define ADDRESSOF(X) &X;
339339
int *j = ADDRESSOF(^i);
340340
)cpp",
341-
341+
R"cpp(// Macro argument appearing multiple times in expansion
342+
#define VALIDATE_TYPE(x) (void)x;
343+
#define ASSERT(expr) \
344+
do { \
345+
VALIDATE_TYPE(expr); \
346+
if (!expr); \
347+
} while (false)
348+
bool [[waldo]]() { return true; }
349+
void foo() {
350+
ASSERT(wa^ldo());
351+
}
352+
)cpp",
342353
R"cpp(// Symbol concatenated inside macro (not supported)
343354
int *pi;
344355
#define POINTER(X) p ## X;

0 commit comments

Comments
 (0)