Index: clang-tools-extra/trunk/clangd/ClangdLSPServer.h =================================================================== --- clang-tools-extra/trunk/clangd/ClangdLSPServer.h +++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h @@ -100,6 +100,8 @@ Callback>); void onTypeHierarchy(const TypeHierarchyParams &, Callback>); + void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, + Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); Index: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp @@ -926,6 +926,13 @@ Params.resolve, Params.direction, std::move(Reply)); } +void ClangdLSPServer::onResolveTypeHierarchy( + const ResolveTypeHierarchyItemParams &Params, + Callback> Reply) { + Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction, + std::move(Reply)); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -1021,6 +1028,7 @@ MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); + MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); // clang-format on } Index: clang-tools-extra/trunk/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.h +++ clang-tools-extra/trunk/clangd/ClangdServer.h @@ -210,6 +210,11 @@ TypeHierarchyDirection Direction, Callback> CB); + /// Resolve type hierarchy item in the given direction. + void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, + TypeHierarchyDirection Direction, + Callback> CB); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, Callback> CB); Index: clang-tools-extra/trunk/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdServer.cpp @@ -528,6 +528,13 @@ WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB))); } +void ClangdServer::resolveTypeHierarchy( + TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, + Callback> CB) { + clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index); + CB(Item); +} + void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { // FIXME: Do nothing for now. This will be used for indexing and potentially // invalidating other caches. Index: clang-tools-extra/trunk/clangd/Protocol.h =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.h +++ clang-tools-extra/trunk/clangd/Protocol.h @@ -1127,7 +1127,7 @@ SymbolKind kind; /// `true` if the hierarchy item is deprecated. Otherwise, `false`. - bool deprecated; + bool deprecated = false; /// The URI of the text document where this type hierarchy item belongs to. URIForFile uri; @@ -1153,13 +1153,26 @@ /// descendants. If not defined, the children have not been resolved. llvm::Optional> children; - /// The protocol has a slot here for an optional 'data' filed, which can - /// be used to identify a type hierarchy item in a resolve request. We don't - /// need this (the item itself is sufficient to identify what to resolve) - /// so don't declare it. + /// An optional 'data' filed, which can be used to identify a type hierarchy + /// item in a resolve request. + llvm::Optional data; }; llvm::json::Value toJSON(const TypeHierarchyItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &); +bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &); + +/// Parameters for the `typeHierarchy/resolve` request. +struct ResolveTypeHierarchyItemParams { + /// The item to resolve. + TypeHierarchyItem item; + + /// The hierarchy levels to resolve. `0` indicates no level. + int resolve; + + /// The direction of the hierarchy levels to resolve. + TypeHierarchyDirection direction; +}; +bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &); struct ReferenceParams : public TextDocumentPositionParams { // For now, no options like context.includeDeclaration are supported. Index: clang-tools-extra/trunk/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.cpp +++ clang-tools-extra/trunk/clangd/Protocol.cpp @@ -422,8 +422,7 @@ bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R) { llvm::json::ObjectMapper O(Params); - return O && O.map("textDocument", R.textDocument) && - O.map("range", R.range); + return O && O.map("textDocument", R.textDocument) && O.map("range", R.range); } bool fromJSON(const llvm::json::Value &Params, @@ -445,8 +444,8 @@ llvm::json::Value toJSON(const DiagnosticRelatedInformation &DRI) { return llvm::json::Object{ - {"location", DRI.location}, - {"message", DRI.message}, + {"location", DRI.location}, + {"message", DRI.message}, }; } @@ -978,6 +977,8 @@ Result["parents"] = I.parents; if (I.children) Result["children"] = I.children; + if (I.data) + Result["data"] = I.data; return std::move(Result); } @@ -996,10 +997,18 @@ O.map("deprecated", I.deprecated); O.map("parents", I.parents); O.map("children", I.children); + O.map("data", I.data); return true; } +bool fromJSON(const llvm::json::Value &Params, + ResolveTypeHierarchyItemParams &P) { + llvm::json::ObjectMapper O(Params); + return O && O.map("item", P.item) && O.map("resolve", P.resolve) && + O.map("direction", P.direction); +} + bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) { TextDocumentPositionParams &Base = R; return fromJSON(Params, Base); Index: clang-tools-extra/trunk/clangd/XRefs.h =================================================================== --- clang-tools-extra/trunk/clangd/XRefs.h +++ clang-tools-extra/trunk/clangd/XRefs.h @@ -141,6 +141,10 @@ ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction, const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); +void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, + TypeHierarchyDirection Direction, + const SymbolIndex *Index); + /// Retrieves the deduced type at a given location (auto, decltype). /// Retuns None unless SourceLocationBeg starts an auto/decltype token. /// It will return the underlying type. Index: clang-tools-extra/trunk/clangd/XRefs.cpp =================================================================== --- clang-tools-extra/trunk/clangd/XRefs.cpp +++ clang-tools-extra/trunk/clangd/XRefs.cpp @@ -893,7 +893,7 @@ /// Retrieves the deduced type at a given location (auto, decltype). bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) { - return (bool) getDeducedType(AST, SourceLocationBeg); + return (bool)getDeducedType(AST, SourceLocationBeg); } llvm::Optional getHover(ParsedAST &AST, Position Pos, @@ -1104,6 +1104,10 @@ // (https://github.com/clangd/clangd/issues/59). THI.range = THI.selectionRange; THI.uri = Loc->uri; + // Store the SymbolID in the 'data' field. The client will + // send this back in typeHierarchy/resolve, allowing us to + // continue resolving additional levels of the type hierarchy. + THI.data = S.ID.str(); return std::move(THI); } @@ -1247,6 +1251,25 @@ return Result; } +void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, + TypeHierarchyDirection Direction, + const SymbolIndex *Index) { + // We only support typeHierarchy/resolve for children, because for parents + // we ignore ResolveLevels and return all levels of parents eagerly. + if (Direction == TypeHierarchyDirection::Parents || ResolveLevels == 0) + return; + + Item.children.emplace(); + + if (Index && Item.data) { + // We store the item's SymbolID in the 'data' field, and the client + // passes it back to us in typeHierarchy/resolve. + if (Expected ID = SymbolID::fromStr(*Item.data)) { + fillSubTypes(*ID, *Item.children, Index, ResolveLevels, Item.uri.file()); + } + } +} + FormattedString HoverInfo::present() const { FormattedString Output; if (NamespaceScope) { Index: clang-tools-extra/trunk/clangd/test/type-hierarchy.test =================================================================== --- clang-tools-extra/trunk/clangd/test/type-hierarchy.test +++ clang-tools-extra/trunk/clangd/test/type-hierarchy.test @@ -1,7 +1,7 @@ # 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 {};\nstruct Child3 : Child2 {};"}}} +{"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 {};\nstruct Child4 : Child3 {};"}}} --- {"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 @@ -9,6 +9,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "children": [ # CHECK-NEXT: { +# CHECK-NEXT: "data": "A6576FE083F2949A", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child3", # CHECK-NEXT: "range": { @@ -114,6 +115,64 @@ # CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" # CHECK-NEXT: } --- -{"jsonrpc":"2.0","id":2,"method":"shutdown"} +{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":"A6576FE083F2949A","name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}} +# CHECK: "id": 2 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "children": [ +# CHECK-NEXT: { +# CHECK-NEXT: "data": "5705B382DFC77CBC", +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child4", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 4 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 4 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 4 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 4 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "data": "A6576FE083F2949A", +# 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: } +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} --- {"jsonrpc":"2.0","method":"exit"} Index: clang-tools-extra/trunk/clangd/unittests/TypeHierarchyTests.cpp =================================================================== --- clang-tools-extra/trunk/clangd/unittests/TypeHierarchyTests.cpp +++ clang-tools-extra/trunk/clangd/unittests/TypeHierarchyTests.cpp @@ -42,8 +42,17 @@ MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; } template ::testing::Matcher Parents(ParentMatchers... ParentsM) { - return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...))); + return Field(&TypeHierarchyItem::parents, + HasValue(UnorderedElementsAre(ParentsM...))); } +template +::testing::Matcher Children(ChildMatchers... ChildrenM) { + return Field(&TypeHierarchyItem::children, + HasValue(UnorderedElementsAre(ChildrenM...))); +} +// Note: "not resolved" is differnt from "resolved but empty"! +MATCHER(ParentsNotResolved, "") { return !arg.parents; } +MATCHER(ChildrenNotResolved, "") { return !arg.children; } TEST(FindRecordTypeAt, TypeOrVariable) { Annotations Source(R"cpp( @@ -603,6 +612,41 @@ EXPECT_THAT(collectSubtypes(Parent, Index.get()), ElementsAre(Child)); } +TEST(Subtypes, LazyResolution) { + Annotations Source(R"cpp( +struct P^arent {}; +struct Child1 : Parent {}; +struct Child2a : Child1 {}; +struct Child2b : Child1 {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + llvm::Optional Result = getTypeHierarchy( + AST, Source.point(), /*ResolveLevels=*/1, + TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT( + *Result, + AllOf(WithName("Parent"), WithKind(SymbolKind::Struct), Parents(), + Children(AllOf(WithName("Child1"), WithKind(SymbolKind::Struct), + ParentsNotResolved(), ChildrenNotResolved())))); + + resolveTypeHierarchy((*Result->children)[0], /*ResolveLevels=*/1, + TypeHierarchyDirection::Children, Index.get()); + + EXPECT_THAT( + (*Result->children)[0], + AllOf(WithName("Child1"), WithKind(SymbolKind::Struct), + ParentsNotResolved(), + Children(AllOf(WithName("Child2a"), WithKind(SymbolKind::Struct), + ParentsNotResolved(), ChildrenNotResolved()), + AllOf(WithName("Child2b"), WithKind(SymbolKind::Struct), + ParentsNotResolved(), ChildrenNotResolved())))); +} + } // namespace } // namespace clangd } // namespace clang