Skip to content

Commit 91679c9

Browse files
committed
[clangd] Include macro expansions in documentSymbol hierarchy
Browsing macro-generated symbols is confusing. On the one hand, it seems very *useful* to be able to see the summary of symbols that were generated. On the other hand, some macros spew a lot of confusing symbols into the namespace and when used repeatedly (ABSL_FLAG) can create a lot of spam that's hard to navigate. Design constraints: - the macro expansion tree need not align with the AST, though it often does in practice. We address this by defining the nesting based on the *primary* location of decls, rather than their ranges. - DocumentSymbol.children[*].range should nest within DocumentSymbol.range (This constraint is not in LSP "breadcrumbs" breaks without it) We adjust macro ranges so they cover their "children", rather than just the macro expansion - LSP does not have a "macro expansion" symbolkind, nor does it allow a symbol to have no kind. I've arbitrarily picked "null" as this is unlikely to conflict with anything useful. This patch makes all macros and children visible for simplicity+consistency, though in some cases it may be better to elide the macro node. We may consider adding heuristics for this in future (e.g. when it expands to one decl only?) but it doesn't seem clear-cut to me. Differential Revision: https://reviews.llvm.org/D97615
1 parent 3c3c4ee commit 91679c9

File tree

2 files changed

+227
-19
lines changed

2 files changed

+227
-19
lines changed

Diff for: clang-tools-extra/clangd/FindSymbols.cpp

+140-9
Original file line numberDiff line numberDiff line change
@@ -289,21 +289,103 @@ llvm::Optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
289289
/// - visiting decls is actually simple, so we don't hit the complicated
290290
/// cases that RAV mostly helps with (types, expressions, etc.)
291291
class DocumentOutline {
292+
// A DocumentSymbol we're constructing.
293+
// We use this instead of DocumentSymbol directly so that we can keep track
294+
// of the nodes we insert for macros.
295+
class SymBuilder {
296+
std::vector<SymBuilder> Children;
297+
DocumentSymbol Symbol; // Symbol.children is empty, use Children instead.
298+
// Macro expansions that this node or its parents are associated with.
299+
// (Thus we will never create further children for these expansions).
300+
llvm::SmallVector<SourceLocation> EnclosingMacroLoc;
301+
302+
public:
303+
DocumentSymbol build() && {
304+
for (SymBuilder &C : Children) {
305+
Symbol.children.push_back(std::move(C).build());
306+
// Expand range to ensure children nest properly, which editors expect.
307+
// This can fix some edge-cases in the AST, but is vital for macros.
308+
// A macro expansion "contains" AST node if it covers the node's primary
309+
// location, but it may not span the node's whole range.
310+
Symbol.range.start =
311+
std::min(Symbol.range.start, Symbol.children.back().range.start);
312+
Symbol.range.end =
313+
std::max(Symbol.range.end, Symbol.children.back().range.end);
314+
}
315+
return std::move(Symbol);
316+
}
317+
318+
// Add a symbol as a child of the current one.
319+
SymBuilder &addChild(DocumentSymbol S) {
320+
Children.emplace_back();
321+
Children.back().EnclosingMacroLoc = EnclosingMacroLoc;
322+
Children.back().Symbol = std::move(S);
323+
return Children.back();
324+
}
325+
326+
// Get an appropriate container for children of this symbol that were
327+
// expanded from a macro (whose spelled name is Tok).
328+
//
329+
// This may return:
330+
// - a macro symbol child of this (either new or previously created)
331+
// - this scope itself, if it *is* the macro symbol or is nested within it
332+
SymBuilder &inMacro(const syntax::Token &Tok, const SourceManager &SM,
333+
llvm::Optional<syntax::TokenBuffer::Expansion> Exp) {
334+
if (llvm::is_contained(EnclosingMacroLoc, Tok.location()))
335+
return *this;
336+
// If there's an existing child for this macro, we expect it to be last.
337+
if (!Children.empty() && !Children.back().EnclosingMacroLoc.empty() &&
338+
Children.back().EnclosingMacroLoc.back() == Tok.location())
339+
return Children.back();
340+
341+
DocumentSymbol Sym;
342+
Sym.name = Tok.text(SM).str();
343+
Sym.kind = SymbolKind::Null; // There's no suitable kind!
344+
Sym.range = Sym.selectionRange =
345+
halfOpenToRange(SM, Tok.range(SM).toCharRange(SM));
346+
347+
// FIXME: Exp is currently unavailable for nested expansions.
348+
if (Exp) {
349+
// Full range covers the macro args.
350+
Sym.range = halfOpenToRange(SM, CharSourceRange::getCharRange(
351+
Exp->Spelled.front().location(),
352+
Exp->Spelled.back().endLocation()));
353+
// Show macro args as detail.
354+
llvm::raw_string_ostream OS(Sym.detail);
355+
const syntax::Token *Prev = nullptr;
356+
for (const auto &Tok : Exp->Spelled.drop_front()) {
357+
// Don't dump arbitrarily long macro args.
358+
if (OS.tell() > 80) {
359+
OS << " ...)";
360+
break;
361+
}
362+
if (Prev && Prev->endLocation() != Tok.location())
363+
OS << ' ';
364+
OS << Tok.text(SM);
365+
Prev = &Tok;
366+
}
367+
}
368+
SymBuilder &Child = addChild(std::move(Sym));
369+
Child.EnclosingMacroLoc.push_back(Tok.location());
370+
return Child;
371+
}
372+
};
373+
292374
public:
293375
DocumentOutline(ParsedAST &AST) : AST(AST) {}
294376

295377
/// Builds the document outline for the generated AST.
296378
std::vector<DocumentSymbol> build() {
297-
std::vector<DocumentSymbol> Results;
379+
SymBuilder DummyRoot;
298380
for (auto &TopLevel : AST.getLocalTopLevelDecls())
299-
traverseDecl(TopLevel, Results);
300-
return Results;
381+
traverseDecl(TopLevel, DummyRoot);
382+
return std::move(std::move(DummyRoot).build().children);
301383
}
302384

303385
private:
304386
enum class VisitKind { No, OnlyDecl, OnlyChildren, DeclAndChildren };
305387

306-
void traverseDecl(Decl *D, std::vector<DocumentSymbol> &Results) {
388+
void traverseDecl(Decl *D, SymBuilder &Parent) {
307389
// Skip symbols which do not originate from the main file.
308390
if (!isInsideMainFile(D->getLocation(), AST.getSourceManager()))
309391
return;
@@ -319,27 +401,76 @@ class DocumentOutline {
319401
return;
320402

321403
if (Visit == VisitKind::OnlyChildren)
322-
return traverseChildren(D, Results);
404+
return traverseChildren(D, Parent);
323405

324406
auto *ND = llvm::cast<NamedDecl>(D);
325407
auto Sym = declToSym(AST.getASTContext(), *ND);
326408
if (!Sym)
327409
return;
328-
Results.push_back(std::move(*Sym));
410+
SymBuilder &MacroParent = possibleMacroContainer(D->getLocation(), Parent);
411+
SymBuilder &Child = MacroParent.addChild(std::move(*Sym));
329412

330413
if (Visit == VisitKind::OnlyDecl)
331414
return;
332415

333416
assert(Visit == VisitKind::DeclAndChildren && "Unexpected VisitKind");
334-
traverseChildren(ND, Results.back().children);
417+
traverseChildren(ND, Child);
418+
}
419+
420+
// Determines where a decl should appear in the DocumentSymbol hierarchy.
421+
//
422+
// This is usually a direct child of the relevant AST parent.
423+
// But we may also insert nodes for macros. Given:
424+
// #define DECLARE_INT(V) int v;
425+
// namespace a { DECLARE_INT(x) }
426+
// We produce:
427+
// Namespace a
428+
// Macro DECLARE_INT(x)
429+
// Variable x
430+
//
431+
// In the absence of macros, this method simply returns Parent.
432+
// Otherwise it may return a macro expansion node instead.
433+
// Each macro only has at most one node in the hierarchy, even if it expands
434+
// to multiple decls.
435+
SymBuilder &possibleMacroContainer(SourceLocation TargetLoc,
436+
SymBuilder &Parent) {
437+
const auto &SM = AST.getSourceManager();
438+
// Look at the path of macro-callers from the token to the main file.
439+
// Note that along these paths we see the "outer" macro calls first.
440+
SymBuilder *CurParent = &Parent;
441+
for (SourceLocation Loc = TargetLoc; Loc.isMacroID();
442+
Loc = SM.getImmediateMacroCallerLoc(Loc)) {
443+
// Find the virtual macro body that our token is being substituted into.
444+
FileID MacroBody;
445+
if (SM.isMacroArgExpansion(Loc)) {
446+
// Loc is part of a macro arg being substituted into a macro body.
447+
MacroBody = SM.getFileID(SM.getImmediateExpansionRange(Loc).getBegin());
448+
} else {
449+
// Loc is already in the macro body.
450+
MacroBody = SM.getFileID(Loc);
451+
}
452+
// The macro body is being substituted for a macro expansion, whose
453+
// first token is the name of the macro.
454+
SourceLocation MacroName =
455+
SM.getSLocEntry(MacroBody).getExpansion().getExpansionLocStart();
456+
// Only include the macro expansion in the outline if it was written
457+
// directly in the main file, rather than expanded from another macro.
458+
if (!MacroName.isValid() || !MacroName.isFileID())
459+
continue;
460+
// All conditions satisfied, add the macro.
461+
if (auto *Tok = AST.getTokens().spelledTokenAt(MacroName))
462+
CurParent = &CurParent->inMacro(
463+
*Tok, SM, AST.getTokens().expansionStartingAt(Tok));
464+
}
465+
return *CurParent;
335466
}
336467

337-
void traverseChildren(Decl *D, std::vector<DocumentSymbol> &Results) {
468+
void traverseChildren(Decl *D, SymBuilder &Builder) {
338469
auto *Scope = llvm::dyn_cast<DeclContext>(D);
339470
if (!Scope)
340471
return;
341472
for (auto *C : Scope->decls())
342-
traverseDecl(C, Results);
473+
traverseDecl(C, Builder);
343474
}
344475

345476
VisitKind shouldVisit(Decl *D) {

Diff for: clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp

+87-10
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,76 @@ TEST(DocumentSymbols, Enums) {
662662
WithDetail("(unnamed)"))))))));
663663
}
664664

665-
TEST(DocumentSymbols, FromMacro) {
665+
TEST(DocumentSymbols, Macro) {
666+
struct Test {
667+
const char *Code;
668+
testing::Matcher<DocumentSymbol> Matcher;
669+
} Tests[] = {
670+
{
671+
R"cpp(
672+
// Basic macro that generates symbols.
673+
#define DEFINE_FLAG(X) bool FLAGS_##X; bool FLAGS_no##X
674+
DEFINE_FLAG(pretty);
675+
)cpp",
676+
AllOf(WithName("DEFINE_FLAG"), WithDetail("(pretty)"),
677+
Children(WithName("FLAGS_pretty"), WithName("FLAGS_nopretty"))),
678+
},
679+
{
680+
R"cpp(
681+
// Hierarchy is determined by primary (name) location.
682+
#define ID(X) X
683+
namespace ID(ns) { int ID(y); }
684+
)cpp",
685+
AllOf(WithName("ID"), WithDetail("(ns)"),
686+
Children(AllOf(WithName("ns"),
687+
Children(AllOf(WithName("ID"), WithDetail("(y)"),
688+
Children(WithName("y"))))))),
689+
},
690+
{
691+
R"cpp(
692+
// More typical example where macro only generates part of a decl.
693+
#define TEST(A, B) class A##_##B { void go(); }; void A##_##B::go()
694+
TEST(DocumentSymbols, Macro) { }
695+
)cpp",
696+
AllOf(WithName("TEST"), WithDetail("(DocumentSymbols, Macro)"),
697+
Children(AllOf(WithName("DocumentSymbols_Macro"),
698+
Children(WithName("go"))),
699+
WithName("DocumentSymbols_Macro::go"))),
700+
},
701+
{
702+
R"cpp(
703+
// Nested macros.
704+
#define NAMESPACE(NS, BODY) namespace NS { BODY }
705+
NAMESPACE(a, NAMESPACE(b, int x;))
706+
)cpp",
707+
AllOf(
708+
WithName("NAMESPACE"), WithDetail("(a, NAMESPACE(b, int x;))"),
709+
Children(AllOf(
710+
WithName("a"),
711+
Children(AllOf(WithName("NAMESPACE"),
712+
// FIXME: nested expansions not in TokenBuffer
713+
WithDetail(""),
714+
Children(AllOf(WithName("b"),
715+
Children(WithName("x"))))))))),
716+
},
717+
{
718+
R"cpp(
719+
// Macro invoked from body is not exposed.
720+
#define INNER(X) int X
721+
#define OUTER(X) INNER(X)
722+
OUTER(foo);
723+
)cpp",
724+
AllOf(WithName("OUTER"), WithDetail("(foo)"),
725+
Children(WithName("foo"))),
726+
},
727+
};
728+
for (const Test &T : Tests) {
729+
auto TU = TestTU::withCode(T.Code);
730+
EXPECT_THAT(getSymbols(TU.build()), ElementsAre(T.Matcher)) << T.Code;
731+
}
732+
}
733+
734+
TEST(DocumentSymbols, RangeFromMacro) {
666735
TestTU TU;
667736
Annotations Main(R"(
668737
#define FF(name) \
@@ -671,9 +740,9 @@ TEST(DocumentSymbols, FromMacro) {
671740
$expansion1[[FF]](abc);
672741
673742
#define FF2() \
674-
class Test {};
743+
class Test {}
675744
676-
$expansion2[[FF2]]();
745+
$expansion2parens[[$expansion2[[FF2]]()]];
677746
678747
#define FF3() \
679748
void waldo()
@@ -683,13 +752,21 @@ TEST(DocumentSymbols, FromMacro) {
683752
}]]
684753
)");
685754
TU.Code = Main.code().str();
686-
EXPECT_THAT(getSymbols(TU.build()),
687-
ElementsAre(AllOf(WithName("abc_Test"), WithDetail("class"),
688-
SymNameRange(Main.range("expansion1"))),
689-
AllOf(WithName("Test"), WithDetail("class"),
690-
SymNameRange(Main.range("expansion2"))),
691-
AllOf(WithName("waldo"), WithDetail("void ()"),
692-
SymRange(Main.range("fullDef")))));
755+
EXPECT_THAT(
756+
getSymbols(TU.build()),
757+
ElementsAre(
758+
AllOf(WithName("FF"), WithDetail("(abc)"),
759+
Children(AllOf(WithName("abc_Test"), WithDetail("class"),
760+
SymNameRange(Main.range("expansion1"))))),
761+
AllOf(WithName("FF2"), WithDetail("()"),
762+
SymNameRange(Main.range("expansion2")),
763+
SymRange(Main.range("expansion2parens")),
764+
Children(AllOf(WithName("Test"), WithDetail("class"),
765+
SymNameRange(Main.range("expansion2"))))),
766+
AllOf(WithName("FF3"), WithDetail("()"),
767+
SymRange(Main.range("fullDef")),
768+
Children(AllOf(WithName("waldo"), WithDetail("void ()"),
769+
SymRange(Main.range("fullDef")))))));
693770
}
694771

695772
TEST(DocumentSymbols, FuncTemplates) {

0 commit comments

Comments
 (0)