diff --git a/clang-tools-extra/clangd/Selection.h b/clang-tools-extra/clangd/Selection.h --- a/clang-tools-extra/clangd/Selection.h +++ b/clang-tools-extra/clangd/Selection.h @@ -103,15 +103,15 @@ Selection Selected; // Walk up the AST to get the DeclContext of this Node, // which is not the node itself. - const DeclContext& getDeclContext() const; + const DeclContext &getDeclContext() const; // Printable node kind, like "CXXRecordDecl" or "AutoTypeLoc". std::string kind() const; // If this node is a wrapper with no syntax (e.g. implicit cast), return // its contents. (If multiple wrappers are present, unwraps all of them). - const Node& ignoreImplicit() const; + const Node &ignoreImplicit() const; // If this node is inside a wrapper with no syntax (e.g. implicit cast), // return that wrapper. (If multiple are present, unwraps all of them). - const Node& outerImplicit() const; + const Node &outerImplicit() const; }; // The most specific common ancestor of all the selected nodes. // Returns nullptr if the common ancestor is the root. @@ -119,6 +119,9 @@ const Node *commonAncestor() const; // The selection node corresponding to TranslationUnitDecl. const Node &root() const { return *Root; } + // Get all individual selected nodes, in case the caller wants something + // other than their common ancestor. + std::vector allSelectedNodes() const; private: std::deque Nodes; // Stable-pointer storage. diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -52,8 +52,7 @@ // On traversing an AST node, its token range is erased from the unclaimed set. // The tokens actually removed are associated with that node, and hit-tested // against the selection to determine whether the node is selected. -template -class IntervalSet { +template class IntervalSet { public: IntervalSet(llvm::ArrayRef Range) { UnclaimedRanges.insert(Range); } @@ -78,7 +77,7 @@ --Overlap.first; // ...unless B isn't selected at all. if (Overlap.first->end() <= Claim.begin()) - ++Overlap.first; + ++Overlap.first; } if (Overlap.first == Overlap.second) return Out; @@ -118,8 +117,7 @@ }; // Disjoint sorted unclaimed ranges of expanded tokens. - std::set, RangeLess> - UnclaimedRanges; + std::set, RangeLess> UnclaimedRanges; }; // Sentinel value for the selectedness of a node where we've seen no tokens yet. @@ -142,7 +140,6 @@ Result = SelectionTree::Partial; } - // SelectionTester can determine whether a range of tokens from the PP-expanded // stream (corresponding to an AST node) is considered selected. // @@ -343,7 +340,7 @@ } #endif -bool isImplicit(const Stmt* S) { +bool isImplicit(const Stmt *S) { // Some Stmts are implicit and shouldn't be traversed, but there's no // "implicit" attribute on Stmt/Expr. // Unwrap implicit casts first if present (other nodes too?). @@ -587,7 +584,7 @@ // int (*[[s]])(); else if (auto *VD = llvm::dyn_cast(D)) return VD->getLocation(); - } else if (const auto* CCI = N.get()) { + } else if (const auto *CCI = N.get()) { // : [[b_]](42) return CCI->getMemberLocation(); } @@ -704,10 +701,29 @@ return Ancestor != Root ? Ancestor : nullptr; } -const DeclContext& SelectionTree::Node::getDeclContext() const { - for (const Node* CurrentNode = this; CurrentNode != nullptr; +namespace { +void gatherSelectedNodes(const Node *Root, std::vector &Result) { + if (Root->Selected) { + Result.push_back(Root); + return; + } + for (const Node *Child : Root->Children) { + gatherSelectedNodes(Child, Result); + } +} + +} // namespace + +std::vector SelectionTree::allSelectedNodes() const { + std::vector Result; + gatherSelectedNodes(Root, Result); + return Result; +} + +const DeclContext &SelectionTree::Node::getDeclContext() const { + for (const Node *CurrentNode = this; CurrentNode != nullptr; CurrentNode = CurrentNode->Parent) { - if (const Decl* Current = CurrentNode->ASTNode.get()) { + if (const Decl *Current = CurrentNode->ASTNode.get()) { if (CurrentNode != this) if (auto *DC = dyn_cast(Current)) return *DC; diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -140,6 +140,28 @@ auto Decls = targetDecl(N->ASTNode, Relations); Result.assign(Decls.begin(), Decls.end()); } + // If the common ancestor didn't have a target, the individual selected + // nodes still might. This can happen if the selection is in a macro + // invocation and is over a token that appears in multiple places in + // the macro expansion. As long as all occurrences map to the same target, + // return that target. + if (Result.empty()) { + auto SelectedNodes = Selection.allSelectedNodes(); + if (SelectedNodes.size() <= 1) + return {}; + for (const SelectionTree::Node *N : SelectedNodes) { + auto Decls = targetDecl(N->ASTNode, Relations); + if (Decls.size() != 1) { + return Result; + } + auto Candidate = Decls[0]; + if (Result.empty()) { + Result.push_back(Decls[0]); + } else if (Result[0] != Candidate) { + return {}; + } + } + } return Result; } diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -349,7 +349,18 @@ #define ADDRESSOF(X) &X; int *j = ADDRESSOF(^i); )cpp", - + R"cpp(// Macro argument appearing multiple times in expansion + #define VALIDATE_TYPE(x) (void)x; + #define ASSERT(expr) \ + do { \ + VALIDATE_TYPE(expr); \ + if (!expr); \ + } while (false) + bool [[waldo]]() { return true; } + void foo() { + ASSERT(wa^ldo()); + } + )cpp", R"cpp(// Symbol concatenated inside macro (not supported) int *pi; #define POINTER(X) p # X;