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 @@ -443,6 +443,15 @@ if (auto *CTI = llvm::dyn_cast(S)) if (CTI->isImplicit()) return true; + // Make sure implicit access of anonymous structs don't end up owning tokens. + if (auto *ME = llvm::dyn_cast(S)) { + if (auto *FD = llvm::dyn_cast(ME->getMemberDecl())) + if (FD->isAnonymousStructOrUnion()) + // If Base is an implicit CXXThis, then the whole MemberExpr has no + // tokens. If it's a normal e.g. DeclRef, we treat the MemberExpr like + // an implicit cast. + return isImplicit(ME->getBase()); + } // Refs to operator() and [] are (almost?) always implicit as part of calls. if (auto *DRE = llvm::dyn_cast(S)) { if (auto *FD = llvm::dyn_cast(DRE->getDecl())) { 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 @@ -365,6 +365,27 @@ ElementsAre(Sym("Forward", SymbolHeader.range("forward"), Test.range()))); } +TEST(LocateSymbol, AnonymousStructFields) { + auto Code = Annotations(R"cpp( + struct $2[[Foo]] { + struct { int $1[[x]]; }; + void foo() { + // Make sure the implicit base is skipped. + $1^x = 42; + } + }; + // Check that we don't skip explicit bases. + int a = $2^Foo{}.x; + )cpp"); + TestTU TU = TestTU::withCode(Code.code()); + auto AST = TU.build(); + EXPECT_THAT(locateSymbolAt(AST, Code.point("1"), TU.index().get()), + UnorderedElementsAre(Sym("x", Code.range("1"), Code.range("1")))); + EXPECT_THAT( + locateSymbolAt(AST, Code.point("2"), TU.index().get()), + UnorderedElementsAre(Sym("Foo", Code.range("2"), Code.range("2")))); +} + TEST(LocateSymbol, FindOverrides) { auto Code = Annotations(R"cpp( class Foo {