diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -480,11 +480,13 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, TypeHierarchyDirection Direction, Callback> CB) { - auto Action = [Pos, Resolve, Direction](decltype(CB) CB, - Expected InpAST) { + std::string FileCopy = File; // copy will be captured by the lambda + auto Action = [FileCopy, Pos, Resolve, Direction, + this](decltype(CB) CB, Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); - CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction)); + CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction, FileCopy, + Index)); }; WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB))); diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h --- a/clang-tools-extra/clangd/FindSymbols.h +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -18,8 +18,13 @@ namespace clang { namespace clangd { class ParsedAST; +struct Symbol; class SymbolIndex; +/// Helper function for deriving an LSP Location for a Symbol. +llvm::Optional symbolToLocation(const Symbol &Sym, + llvm::StringRef HintPath); + /// Searches for the symbols matching \p Query. The syntax of \p Query can be /// the non-qualified name or fully qualified of a symbol. For example, /// "vector" will match the symbol std::vector and "std::vector" would also diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -39,6 +39,34 @@ } // namespace +llvm::Optional symbolToLocation(const Symbol &Sym, + llvm::StringRef HintPath) { + // Prefer the definition over e.g. a function declaration in a header + auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; + auto Uri = URI::parse(CD.FileURI); + if (!Uri) { + log("Could not parse URI '{0}' for symbol '{1}'.", CD.FileURI, Sym.Name); + return llvm::None; + } + auto Path = URI::resolve(*Uri, HintPath); + if (!Path) { + log("Could not resolve path for URI '{0}' for symbol '{1}'.", + Uri->toString(), Sym.Name); + return llvm::None; + } + Location L; + // Use HintPath as TUPath since there is no TU associated with this + // request. + L.uri = URIForFile::canonicalize(*Path, HintPath); + Position Start, End; + Start.line = CD.Start.line(); + Start.character = CD.Start.column(); + End.line = CD.End.line(); + End.character = CD.End.column(); + L.range = {Start, End}; + return L; +} + llvm::Expected> getWorkspaceSymbols(llvm::StringRef Query, int Limit, const SymbolIndex *const Index, llvm::StringRef HintPath) { @@ -65,37 +93,16 @@ Req.Limit ? *Req.Limit : std::numeric_limits::max()); FuzzyMatcher Filter(Req.Query); Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) { - // Prefer the definition over e.g. a function declaration in a header - auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; - auto Uri = URI::parse(CD.FileURI); - if (!Uri) { - log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", - CD.FileURI, Sym.Name); - return; - } - auto Path = URI::resolve(*Uri, HintPath); - if (!Path) { - log("Workspace symbol: Could not resolve path for URI '{0}' for symbol " - "'{1}'.", - Uri->toString(), Sym.Name); + auto Loc = symbolToLocation(Sym, HintPath); + if (!Loc) return; - } - Location L; - // Use HintPath as TUPath since there is no TU associated with this - // request. - L.uri = URIForFile::canonicalize(*Path, HintPath); - Position Start, End; - Start.line = CD.Start.line(); - Start.character = CD.Start.column(); - End.line = CD.End.line(); - End.character = CD.End.column(); - L.range = {Start, End}; + SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); std::string Scope = Sym.Scope; llvm::StringRef ScopeRef = Scope; ScopeRef.consume_back("::"); SymbolInformation Info = {(Sym.Name + Sym.TemplateSpecializationArgs).str(), - SK, L, ScopeRef}; + SK, *Loc, ScopeRef}; SymbolQualitySignals Quality; Quality.merge(Sym); diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -136,7 +136,8 @@ /// Get type hierarchy information at \p Pos. llvm::Optional getTypeHierarchy(ParsedAST &AST, Position Pos, int Resolve, - TypeHierarchyDirection Direction); + TypeHierarchyDirection Direction, PathRef TUPath = PathRef{}, + const SymbolIndex *Index = nullptr); } // namespace clangd } // namespace clang 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 @@ -1046,6 +1046,45 @@ return THI; } +static Optional +symbolToTypeHierarchyItem(const Symbol &S, const SymbolIndex *Index, + PathRef TUPath) { + // TODO: Pass in ClangdServer::WorkspaceRoot as a HintPath. + StringRef HintPath; + auto Loc = symbolToLocation(S, HintPath); + if (!Loc) + return llvm::None; + TypeHierarchyItem THI; + THI.name = S.Name; + THI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind); + THI.deprecated = (S.Flags & Symbol::Deprecated); + THI.selectionRange = Loc->range; + // FIXME: Populate 'range' correctly + // (https://github.com/clangd/clangd/issues/59). + THI.range = THI.selectionRange; + THI.uri = Loc->uri; + + return std::move(THI); +} + +static void fillSubTypes(const SymbolID &ID, + std::vector &SubTypes, + const SymbolIndex *Index, int Levels, PathRef TUPath) { + RelationsRequest Req; + Req.Subjects.insert(ID); + Req.Predicate = index::SymbolRole::RelationBaseOf; + Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { + if (Optional ChildSym = + symbolToTypeHierarchyItem(Object, Index, TUPath)) { + if (Levels > 1) { + ChildSym->children.emplace(); + fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath); + } + SubTypes.emplace_back(std::move(*ChildSym)); + } + }); +} + using RecursionProtectionSet = llvm::SmallSet; static Optional @@ -1141,7 +1180,8 @@ llvm::Optional getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, - TypeHierarchyDirection Direction) { + TypeHierarchyDirection Direction, PathRef TUPath, + const SymbolIndex *Index) { const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos); if (!CXXRD) return llvm::None; @@ -1150,8 +1190,16 @@ Optional Result = getTypeAncestors(*CXXRD, AST.getASTContext(), RPSet); - // FIXME(nridge): Resolve type descendants if direction is Children or Both, - // and ResolveLevels > 0. + if ((Direction == TypeHierarchyDirection::Children || + Direction == TypeHierarchyDirection::Both) && + ResolveLevels > 0) { + Result->children.emplace(); + + if (Index) { + if (Optional ID = getSymbolID(CXXRD)) + fillSubTypes(*ID, *Result->children, Index, ResolveLevels, TUPath); + } + } return Result; } diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test --- a/clang-tools-extra/clangd/test/type-hierarchy.test +++ b/clang-tools-extra/clangd/test/type-hierarchy.test @@ -1,12 +1,39 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};\nstruct Child3 : Child2 {};"}}} --- -{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":1,"resolve":1}} +{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}} # CHECK: "id": 1 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { +# CHECK-NEXT: "children": [ +# CHECK-NEXT: { +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child3", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ], # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "parents": [ diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp --- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp @@ -34,7 +34,7 @@ using ::testing::IsEmpty; using ::testing::Matcher; using ::testing::Pointee; -using ::testing::UnorderedElementsAreArray; +using ::testing::UnorderedElementsAre; // GMock helpers for matching TypeHierarchyItem. MATCHER_P(WithName, N, "") { return arg.name == N; } @@ -450,6 +450,158 @@ SelectionRangeIs(Source.range("SDef")), Parents())))); } +SymbolID findSymbolIDByName(SymbolIndex *Index, llvm::StringRef Name, + llvm::StringRef TemplateArgs = "") { + SymbolID Result; + FuzzyFindRequest Request; + Request.Query = Name; + Request.AnyScope = true; + bool GotResult = false; + Index->fuzzyFind(Request, [&](const Symbol &S) { + if (TemplateArgs == S.TemplateSpecializationArgs) { + EXPECT_FALSE(GotResult); + Result = S.ID; + GotResult = true; + } + }); + EXPECT_TRUE(GotResult); + return Result; +} + +std::vector collectSubtypes(SymbolID Subject, SymbolIndex *Index) { + std::vector Result; + RelationsRequest Req; + Req.Subjects.insert(Subject); + Req.Predicate = index::SymbolRole::RelationBaseOf; + Index->relations(Req, + [&Result](const SymbolID &Subject, const Symbol &Object) { + Result.push_back(Object.ID); + }); + return Result; +} + +TEST(Subtypes, SimpleInheritance) { + Annotations Source(R"cpp( +struct Parent {}; +struct Child1a : Parent {}; +struct Child1b : Parent {}; +struct Child2 : Child1a {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent = findSymbolIDByName(Index.get(), "Parent"); + SymbolID Child1a = findSymbolIDByName(Index.get(), "Child1a"); + SymbolID Child1b = findSymbolIDByName(Index.get(), "Child1b"); + SymbolID Child2 = findSymbolIDByName(Index.get(), "Child2"); + + EXPECT_THAT(collectSubtypes(Parent, Index.get()), + UnorderedElementsAre(Child1a, Child1b)); + EXPECT_THAT(collectSubtypes(Child1a, Index.get()), ElementsAre(Child2)); +} + +TEST(Subtypes, MultipleInheritance) { + Annotations Source(R"cpp( +struct Parent1 {}; +struct Parent2 {}; +struct Parent3 : Parent2 {}; +struct Child : Parent1, Parent3 {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent1 = findSymbolIDByName(Index.get(), "Parent1"); + SymbolID Parent2 = findSymbolIDByName(Index.get(), "Parent2"); + SymbolID Parent3 = findSymbolIDByName(Index.get(), "Parent3"); + SymbolID Child = findSymbolIDByName(Index.get(), "Child"); + + EXPECT_THAT(collectSubtypes(Parent1, Index.get()), ElementsAre(Child)); + EXPECT_THAT(collectSubtypes(Parent2, Index.get()), ElementsAre(Parent3)); + EXPECT_THAT(collectSubtypes(Parent3, Index.get()), ElementsAre(Child)); +} + +TEST(Subtypes, ClassTemplate) { + Annotations Source(R"cpp( +struct Parent {}; + +template +struct Child : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent = findSymbolIDByName(Index.get(), "Parent"); + SymbolID Child = findSymbolIDByName(Index.get(), "Child"); + + EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child)); +} + +TEST(Subtypes, TemplateSpec1) { + Annotations Source(R"cpp( +template +struct Parent {}; + +template <> +struct Parent {}; + +struct Child1 : Parent {}; + +struct Child2 : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent = findSymbolIDByName(Index.get(), "Parent"); + SymbolID ParentSpec = findSymbolIDByName(Index.get(), "Parent", ""); + SymbolID Child1 = findSymbolIDByName(Index.get(), "Child1"); + SymbolID Child2 = findSymbolIDByName(Index.get(), "Child2"); + + EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child1)); + EXPECT_THAT(collectSubtypes(ParentSpec, Index.get()), ElementsAre(Child2)); +} + +TEST(Subtypes, TemplateSpec2) { + Annotations Source(R"cpp( +struct Parent {}; + +template +struct Child {}; + +template <> +struct Child : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent = findSymbolIDByName(Index.get(), "Parent"); + SymbolID ChildSpec = findSymbolIDByName(Index.get(), "Child", ""); + + EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(ChildSpec)); +} + +TEST(Subtypes, DependentBase) { + Annotations Source(R"cpp( +template +struct Parent {}; + +template +struct Child : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto Index = TU.index(); + + SymbolID Parent = findSymbolIDByName(Index.get(), "Parent"); + SymbolID Child = findSymbolIDByName(Index.get(), "Child"); + + EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child)); +} + } // namespace } // namespace clangd } // namespace clang