diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index d40d883a16d3..41c9cddfebbb 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -294,17 +294,6 @@ public: break; } } - void VisitOffsetOfExpr(const OffsetOfExpr *OOE) { - for (unsigned I = OOE->getNumComponents(); I != 0; --I) { - const OffsetOfNode &Component = OOE->getComponent(I - 1); - if (Component.getKind() == OffsetOfNode::Field) { - Outer.add(Component.getField(), Flags); - // We don't know which component was intended, we assume the - // innermost. - break; - } - } - } void VisitGotoStmt(const GotoStmt *Goto) { if (auto *LabelDecl = Goto->getLabel()) Outer.add(LabelDecl, Flags); @@ -563,6 +552,10 @@ allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) { Finder.add(PL->getProtocol(), Flags); else if (const ConceptReference *CR = N.get()) Finder.add(CR, Flags); + else if (const OffsetOfNode *OON = N.get()) { + if (OON->getKind() == OffsetOfNode::Field) + Finder.add(OON->getField(), Flags); + } return Finder.takeDecls(); } @@ -826,17 +819,6 @@ llvm::SmallVector refInStmt(const Stmt *S, } } - void VisitOffsetOfExpr(const OffsetOfExpr *OOE) { - for (unsigned I = 0, N = OOE->getNumComponents(); I < N; ++I) { - const OffsetOfNode &Component = OOE->getComponent(I); - if (Component.getKind() == OffsetOfNode::Field) - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), - Component.getEndLoc(), - /*IsDecl=*/false, - {Component.getField()}}); - } - } - void VisitGotoStmt(const GotoStmt *GS) { Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), GS->getLabelLoc(), @@ -1041,6 +1023,11 @@ public: return true; } + bool VisitOffsetOfNode(const OffsetOfNode *N) { + visitNode(DynTypedNode::create(*N)); + return true; + } + private: /// Obtain information about a reference directly defined in \p N. Does not /// recurse into child nodes, e.g. do not expect references for constructor @@ -1095,6 +1082,14 @@ private: CR->getConceptNameLoc(), /*IsDecl=*/false, {CR->getNamedConcept()}}}; + if (const OffsetOfNode *OON = N.get()) { + if (OON->getKind() == OffsetOfNode::Field) + return {ReferenceLoc{NestedNameSpecifierLoc(), + OON->getEndLoc(), + /*IsDecl=*/false, + {OON->getField()}}}; + return {}; + } // We do not have location information for other nodes (QualType, etc) return {}; diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index fb2fb052388f..b79ffc7d5a6e 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -669,6 +669,9 @@ public: bool TraverseConceptReference(ConceptReference *X) { return traverseNode(X, [&] { return Base::TraverseConceptReference(X); }); } + bool TraverseOffsetOfNode(const OffsetOfNode *N) { + return traverseNode(N, [&] { return Base::TraverseOffsetOfNode(N); }); + } // Stmt is the same, but this form allows the data recursion optimization. bool dataTraverseStmtPre(Stmt *X) { if (!X || isImplicit(X)) @@ -920,6 +923,15 @@ private: if (N.get()) return; + if (const auto *OON = N.get()) { + if (OON->getKind() == OffsetOfNode::Array) { + // Leave the array index expression to its own child nodes. + claimRange(OON->getBeginLoc(), Result); + claimRange(OON->getEndLoc(), Result); + return; + } + } + // Declarators nest "inside out", with parent types inside child ones. // Instead of claiming the whole range (clobbering parent tokens), carefully // claim the tokens owned by this node and non-declarator children. diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index ed7e21a7f2e4..d8c903cfcafe 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -347,14 +347,83 @@ TEST_F(TargetDeclTest, OffsetOf) { struct Foo { int bar; }; int x = __builtin_offsetof(Foo, [[bar]]); )cpp"; - EXPECT_DECLS("OffsetOfExpr", "int bar"); + EXPECT_DECLS("OffsetOfNode", "int bar"); - // For a nested designator, allTargetDecls reports the innermost field. Code = R"cpp( - struct A { struct { int c; } b; }; - int x = __builtin_offsetof(A, [[b.c]]); + struct Inner { int c; }; + struct Outer { Inner b; }; + int x = __builtin_offsetof(Outer, [[b]].c); )cpp"; - EXPECT_DECLS("OffsetOfExpr", "int c"); + EXPECT_DECLS("OffsetOfNode", "Inner b"); + + Code = R"cpp( + struct Inner { int c; }; + struct Outer { Inner b; }; + int x = __builtin_offsetof(Outer, b.[[c]]); + )cpp"; + EXPECT_DECLS("OffsetOfNode", "int c"); + + // Selection that spans multiple components doesn't match any single + // OffsetOfNode. + Code = R"cpp( + struct Inner { int c; }; + struct Outer { Inner b; }; + int x = __builtin_offsetof(Outer, [[b.c]]); + )cpp"; + EXPECT_THAT(assertNodeAndPrintDecls("OffsetOfExpr"), ::testing::IsEmpty()); + + Code = R"cpp( + struct Inner { int c; }; + struct Outer { Inner b; }; + int x = __builtin_offsetof(Outer, [[b.]]c); + )cpp"; + EXPECT_THAT(assertNodeAndPrintDecls("OffsetOfExpr"), ::testing::IsEmpty()); + + Code = R"cpp( + struct Inner { int c; }; + struct Outer { Inner b; }; + int x = __builtin_offsetof(Outer, b[[.c]]); + )cpp"; + EXPECT_DECLS("OffsetOfNode", "int c"); + + Code = R"cpp( + struct Foo { int bar; }; + int x = __builtin_[[offsetof]](Foo, bar); + )cpp"; + EXPECT_THAT(assertNodeAndPrintDecls("OffsetOfExpr"), ::testing::IsEmpty()); + + // Base-class component: the implicit Base node has no source range, so + // the cursor on `x` resolves precisely to the inherited field. + Code = R"cpp( + struct B { int x; }; + struct D : B {}; + int o = __builtin_offsetof(D, [[x]]); + )cpp"; + EXPECT_DECLS("OffsetOfNode", "int x"); + + // Dependent-type identifier component: no resolvable decl, but must not + // crash and must not produce spurious targets. + Code = R"cpp( + template int f() { return __builtin_offsetof(T, [[x]]); } + )cpp"; + EXPECT_THAT(assertNodeAndPrintDecls("OffsetOfNode"), ::testing::IsEmpty()); + + // C-style offsetof macro form resolves the same as __builtin_offsetof. + Code = R"cpp( + #define offsetof(t, m) __builtin_offsetof(t, m) + struct Foo { int bar; }; + int x = offsetof(Foo, [[bar]]); + )cpp"; + EXPECT_DECLS("OffsetOfNode", "int bar"); + + // Array-index expression in an offsetof designator should still resolve as + // the expression, not as the enclosing OffsetOfNode. + Code = R"cpp( + struct Foo { int bar[4]; }; + int i; + int x = __builtin_offsetof(Foo, bar[ [[i]] ]); + )cpp"; + EXPECT_DECLS("DeclRefExpr", "int i"); } TEST_F(TargetDeclTest, NestedNameSpecifier) { diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index b3d9ed54e8e6..7b168b0bdca6 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -1780,6 +1780,18 @@ TEST(Hover, NoHover) { } } +TEST(Hover, OffsetOfBuiltin) { + Annotations T(R"cpp( + struct Foo { int x; }; + int y = __builtin_o^ffsetof(Foo, x); + )cpp"); + auto AST = TestTU::withCode(T.code()).build(); + auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr); + ASSERT_TRUE(H); + EXPECT_EQ(H->Name, "expression"); + EXPECT_EQ(H->Kind, index::SymbolKind::Unknown); +} + TEST(Hover, All) { struct { const char *const Code; @@ -2007,6 +2019,50 @@ TEST(Hover, All) { HI.Type = "int"; HI.Definition = "int x"; }}, + { + R"cpp(// Field, offsetof + struct Foo { int x; int y; }; + int z = __builtin_offsetof(Foo, ^[[x]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "x"; + HI.Kind = index::SymbolKind::Field; + HI.NamespaceScope = ""; + HI.LocalScope = "Foo::"; + HI.Type = "int"; + HI.Definition = "int x"; + HI.Value = "0"; + }}, + { + R"cpp(// Outer field, nested offsetof designator + struct Inner { int c; }; + struct A { Inner B; }; + int z = __builtin_offsetof(A, ^[[B]].c); + )cpp", + [](HoverInfo &HI) { + HI.Name = "B"; + HI.Kind = index::SymbolKind::Field; + HI.NamespaceScope = ""; + HI.LocalScope = "A::"; + HI.Type = "Inner"; + HI.Definition = "Inner B"; + HI.Value = "0"; + }}, + { + R"cpp(// Inner field, nested offsetof designator + struct Inner { int c; }; + struct A { Inner B; }; + int z = __builtin_offsetof(A, B.^[[c]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "c"; + HI.Kind = index::SymbolKind::Field; + HI.NamespaceScope = ""; + HI.LocalScope = "Inner::"; + HI.Type = "int"; + HI.Definition = "int c"; + HI.Value = "0"; + }}, { R"cpp(// Method call struct Foo { int x(); }; diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index 00ead63050c8..7f31a1e97d5d 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -601,6 +601,40 @@ TEST(LocateSymbol, All) { } )cpp", + R"cpp(// Field in offsetof + struct Foo { int [[x]]; }; + int y = __builtin_offsetof(Foo, ^x); + )cpp", + + R"cpp(// Outer field in nested offsetof designator + struct Inner { int c; }; + struct A { Inner [[B]]; }; + int y = __builtin_offsetof(A, ^B.c); + )cpp", + + R"cpp(// Inner field in nested offsetof designator + struct Inner { int [[c]]; }; + struct A { Inner B; }; + int y = __builtin_offsetof(A, B.^c); + )cpp", + + R"cpp(// Field in offsetof macro form + #define offsetof(t, m) __builtin_offsetof(t, m) + struct Foo { int [[x]]; }; + int y = offsetof(Foo, ^x); + )cpp", + + R"cpp(// Inherited field in offsetof + struct B { int [[x]]; }; + struct D : B {}; + int y = __builtin_offsetof(D, ^x); + )cpp", + + R"cpp(// Builtin offsetof name is not a field reference. + struct Foo { int x; }; + int y = __builtin_o^ffsetof(Foo, x); + )cpp", + R"cpp(// Method call struct Foo { int $decl[[x]](); }; int main() {