[clang-tools-extra] 83411bf - [clangd] Support for standard type hierarchy

Kadir Cetinkaya via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 17 00:33:12 PDT 2022


Author: Kadir Cetinkaya
Date: 2022-08-17T09:29:15+02:00
New Revision: 83411bf06f34ae06731008eeabfc53553c1a1f86

URL: https://github.com/llvm/llvm-project/commit/83411bf06f34ae06731008eeabfc53553c1a1f86
DIFF: https://github.com/llvm/llvm-project/commit/83411bf06f34ae06731008eeabfc53553c1a1f86.diff

LOG: [clangd] Support for standard type hierarchy

This is mostly a mechanical change to adapt standard type hierarchy
support proposed in LSP 3.17 on top of clangd's existing extension support.

This does mainly two things:
- Incorporate symbolids for all the parents inside resolution parameters, so
  that they can be retrieved from index later on. This is a new code path, as
  extension always resolved them eagerly.
- Propogate parent information when resolving children, so that at least one
  branch of parents is always preserved. This is to address a shortcoming in the
  extension.

This doesn't drop support for the extension, but it's deprecated from now on and
will be deleted in upcoming releases. Currently we use the same struct
internally but don't serialize extra fields.

Fixes https://github.com/clangd/clangd/issues/826.

Differential Revision: https://reviews.llvm.org/D131385

Added: 
    clang-tools-extra/clangd/test/type-hierarchy-ext.test

Modified: 
    clang-tools-extra/clangd/ClangdLSPServer.cpp
    clang-tools-extra/clangd/ClangdLSPServer.h
    clang-tools-extra/clangd/ClangdServer.cpp
    clang-tools-extra/clangd/ClangdServer.h
    clang-tools-extra/clangd/Protocol.cpp
    clang-tools-extra/clangd/Protocol.h
    clang-tools-extra/clangd/XRefs.cpp
    clang-tools-extra/clangd/XRefs.h
    clang-tools-extra/clangd/test/initialize-params.test
    clang-tools-extra/clangd/test/type-hierarchy.test
    clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 0cc3792a0fdd2..66bda05d00f71 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -26,6 +26,8 @@
 #include "support/Trace.h"
 #include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ADT/None.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringRef.h"
@@ -571,8 +573,12 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
       {"referencesProvider", true},
       {"astProvider", true}, // clangd extension
       {"typeHierarchyProvider", true},
-      {"memoryUsageProvider", true}, // clangd extension
-      {"compilationDatabase",        // clangd extension
+      // Unfortunately our extension made use of the same capability name as the
+      // standard. Advertise this capability to tell clients that implement our
+      // extension we really have support for the standardized one as well.
+      {"standardTypeHierarchyProvider", true}, // clangd extension
+      {"memoryUsageProvider", true},           // clangd extension
+      {"compilationDatabase",                  // clangd extension
        llvm::json::Object{{"automaticReload", true}}},
       {"callHierarchyProvider", true},
       {"clangdInlayHintsProvider", true},
@@ -1183,18 +1189,94 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
                     });
 }
 
-void ClangdLSPServer::onTypeHierarchy(
-    const TypeHierarchyParams &Params,
-    Callback<Optional<TypeHierarchyItem>> Reply) {
+// Our extension has a 
diff erent representation on the wire than the standard.
+// https://clangd.llvm.org/extensions#type-hierarchy
+llvm::json::Value serializeTHIForExtension(TypeHierarchyItem THI) {
+  llvm::json::Object Result{{
+      {"name", std::move(THI.name)},
+      {"kind", static_cast<int>(THI.kind)},
+      {"uri", std::move(THI.uri)},
+      {"range", THI.range},
+      {"selectionRange", THI.selectionRange},
+      {"data", std::move(THI.data)},
+  }};
+  if (THI.deprecated)
+    Result["deprecated"] = THI.deprecated;
+  if (THI.detail)
+    Result["detail"] = std::move(*THI.detail);
+
+  if (THI.parents) {
+    llvm::json::Array Parents;
+    for (auto &Parent : *THI.parents)
+      Parents.emplace_back(serializeTHIForExtension(std::move(Parent)));
+    Result["parents"] = std::move(Parents);
+  }
+
+  if (THI.children) {
+    llvm::json::Array Children;
+    for (auto &child : *THI.children)
+      Children.emplace_back(serializeTHIForExtension(std::move(child)));
+    Result["children"] = std::move(Children);
+  }
+  return Result;
+}
+
+void ClangdLSPServer::onTypeHierarchy(const TypeHierarchyPrepareParams &Params,
+                                      Callback<llvm::json::Value> Reply) {
+  auto Serialize =
+      [Reply = std::move(Reply)](
+          llvm::Expected<std::vector<TypeHierarchyItem>> Resp) mutable {
+        if (!Resp) {
+          Reply(Resp.takeError());
+          return;
+        }
+        if (Resp->empty()) {
+          Reply(nullptr);
+          return;
+        }
+        Reply(serializeTHIForExtension(std::move(Resp->front())));
+      };
   Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
-                        Params.resolve, Params.direction, std::move(Reply));
+                        Params.resolve, Params.direction, std::move(Serialize));
 }
 
 void ClangdLSPServer::onResolveTypeHierarchy(
     const ResolveTypeHierarchyItemParams &Params,
-    Callback<Optional<TypeHierarchyItem>> Reply) {
+    Callback<llvm::json::Value> Reply) {
+  auto Serialize =
+      [Reply = std::move(Reply)](
+          llvm::Expected<llvm::Optional<TypeHierarchyItem>> Resp) mutable {
+        if (!Resp) {
+          Reply(Resp.takeError());
+          return;
+        }
+        if (!*Resp) {
+          Reply(std::move(*Resp));
+          return;
+        }
+        Reply(serializeTHIForExtension(std::move(**Resp)));
+      };
   Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction,
-                               std::move(Reply));
+                               std::move(Serialize));
+}
+
+void ClangdLSPServer::onPrepareTypeHierarchy(
+    const TypeHierarchyPrepareParams &Params,
+    Callback<std::vector<TypeHierarchyItem>> Reply) {
+  Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
+                        Params.resolve, Params.direction, std::move(Reply));
+}
+
+void ClangdLSPServer::onSuperTypes(
+    const ResolveTypeHierarchyItemParams &Params,
+    Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> Reply) {
+  Server->superTypes(Params.item, std::move(Reply));
+}
+
+void ClangdLSPServer::onSubTypes(
+    const ResolveTypeHierarchyItemParams &Params,
+    Callback<std::vector<TypeHierarchyItem>> Reply) {
+  Server->subTypes(Params.item, std::move(Reply));
 }
 
 void ClangdLSPServer::onPrepareCallHierarchy(
@@ -1523,6 +1605,9 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
   Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo);
   Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy);
   Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy);
+  Bind.method("textDocument/prepareTypeHierarchy", this, &ClangdLSPServer::onPrepareTypeHierarchy);
+  Bind.method("typeHierarchy/supertypes", this, &ClangdLSPServer::onSuperTypes);
+  Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
   Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
   Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
   Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);

diff  --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 562f8e060161f..351b3d0da0be7 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -23,6 +23,7 @@
 #include <chrono>
 #include <cstddef>
 #include <memory>
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -132,10 +133,16 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onRename(const RenameParams &, Callback<WorkspaceEdit>);
   void onHover(const TextDocumentPositionParams &,
                Callback<llvm::Optional<Hover>>);
-  void onTypeHierarchy(const TypeHierarchyParams &,
-                       Callback<llvm::Optional<TypeHierarchyItem>>);
+  void onPrepareTypeHierarchy(const TypeHierarchyPrepareParams &,
+                              Callback<std::vector<TypeHierarchyItem>>);
+  void onSuperTypes(const ResolveTypeHierarchyItemParams &,
+                    Callback<llvm::Optional<std::vector<TypeHierarchyItem>>>);
+  void onSubTypes(const ResolveTypeHierarchyItemParams &,
+                  Callback<std::vector<TypeHierarchyItem>>);
+  void onTypeHierarchy(const TypeHierarchyPrepareParams &,
+                       Callback<llvm::json::Value>);
   void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &,
-                              Callback<llvm::Optional<TypeHierarchyItem>>);
+                              Callback<llvm::json::Value>);
   void onPrepareCallHierarchy(const CallHierarchyPrepareParams &,
                               Callback<std::vector<CallHierarchyItem>>);
   void onCallHierarchyIncomingCalls(

diff  --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index eccb2cd56defb..2997823e75fc3 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -751,7 +751,7 @@ void ClangdServer::findHover(PathRef File, Position Pos,
 
 void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
                                  TypeHierarchyDirection Direction,
-                                 Callback<Optional<TypeHierarchyItem>> CB) {
+                                 Callback<std::vector<TypeHierarchyItem>> CB) {
   auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),
                  this](Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
@@ -763,6 +763,22 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
   WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action));
 }
 
+void ClangdServer::superTypes(
+    const TypeHierarchyItem &Item,
+    Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> CB) {
+  WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",
+                     [=, CB = std::move(CB)]() mutable {
+                       CB(clangd::superTypes(Item, Index));
+                     });
+}
+
+void ClangdServer::subTypes(const TypeHierarchyItem &Item,
+                            Callback<std::vector<TypeHierarchyItem>> CB) {
+  WorkScheduler->run(
+      "typeHierarchy/subTypes", /*Path=*/"",
+      [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
+}
+
 void ClangdServer::resolveTypeHierarchy(
     TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
     Callback<llvm::Optional<TypeHierarchyItem>> CB) {

diff  --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 99c56ebe39740..b14391aab6680 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -253,7 +253,13 @@ class ClangdServer {
   /// Get information about type hierarchy for a given position.
   void typeHierarchy(PathRef File, Position Pos, int Resolve,
                      TypeHierarchyDirection Direction,
-                     Callback<llvm::Optional<TypeHierarchyItem>> CB);
+                     Callback<std::vector<TypeHierarchyItem>> CB);
+  /// Get direct parents of a type hierarchy item.
+  void superTypes(const TypeHierarchyItem &Item,
+                  Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> CB);
+  /// Get direct children of a type hierarchy item.
+  void subTypes(const TypeHierarchyItem &Item,
+                Callback<std::vector<TypeHierarchyItem>> CB);
 
   /// Resolve type hierarchy item in the given direction.
   void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,

diff  --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 979da9b2b99d3..0109fa01e34e5 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1207,12 +1207,13 @@ bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
   return true;
 }
 
-bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R,
+bool fromJSON(const llvm::json::Value &Params, TypeHierarchyPrepareParams &R,
               llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
   return O && O.map("textDocument", R.textDocument) &&
-         O.map("position", R.position) && O.map("resolve", R.resolve) &&
-         O.map("direction", R.direction);
+         O.map("position", R.position) &&
+         mapOptOrNull(Params, "resolve", R.resolve, P) &&
+         mapOptOrNull(Params, "direction", R.direction, P);
 }
 
 llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
@@ -1220,23 +1221,28 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
   return O << I.name << " - " << toJSON(I);
 }
 
+llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &RP) {
+  llvm::json::Object Result{{"symbolID", RP.symbolID}};
+  if (RP.parents)
+    Result["parents"] = RP.parents;
+  return std::move(Result);
+}
+bool fromJSON(const llvm::json::Value &Params,
+              TypeHierarchyItem::ResolveParams &RP, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("symbolID", RP.symbolID) &&
+         mapOptOrNull(Params, "parents", RP.parents, P);
+}
+
 llvm::json::Value toJSON(const TypeHierarchyItem &I) {
-  llvm::json::Object Result{{"name", I.name},
-                            {"kind", static_cast<int>(I.kind)},
-                            {"range", I.range},
-                            {"selectionRange", I.selectionRange},
-                            {"uri", I.uri}};
+  llvm::json::Object Result{
+      {"name", I.name},   {"kind", static_cast<int>(I.kind)},
+      {"range", I.range}, {"selectionRange", I.selectionRange},
+      {"uri", I.uri},     {"data", I.data},
+  };
 
   if (I.detail)
     Result["detail"] = I.detail;
-  if (I.deprecated)
-    Result["deprecated"] = I.deprecated;
-  if (I.parents)
-    Result["parents"] = I.parents;
-  if (I.children)
-    Result["children"] = I.children;
-  if (I.data)
-    Result["data"] = I.data;
   return std::move(Result);
 }
 
@@ -1258,8 +1264,9 @@ bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I,
 bool fromJSON(const llvm::json::Value &Params,
               ResolveTypeHierarchyItemParams &R, llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("item", R.item) && O.map("resolve", R.resolve) &&
-         O.map("direction", R.direction);
+  return O && O.map("item", R.item) &&
+         mapOptOrNull(Params, "resolve", R.resolve, P) &&
+         mapOptOrNull(Params, "direction", R.direction, P);
 }
 
 bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R,
@@ -1510,5 +1517,22 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) {
   return OS;
 }
 
+bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) {
+  auto Str = E.getAsString();
+  if (!Str) {
+    P.report("expected a string");
+    return false;
+  }
+  auto ID = SymbolID::fromStr(*Str);
+  if (!ID) {
+    elog("Malformed symbolid: {0}", ID.takeError());
+    P.report("malformed symbolid");
+    return false;
+  }
+  S = *ID;
+  return true;
+}
+llvm::json::Value toJSON(const SymbolID &S) { return S.str(); }
+
 } // namespace clangd
 } // namespace clang

diff  --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 648c7bd872519..9a68a33cfb334 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -77,6 +77,9 @@ class LSPError : public llvm::ErrorInfo<LSPError> {
   }
 };
 
+bool fromJSON(const llvm::json::Value &, SymbolID &, llvm::json::Path);
+llvm::json::Value toJSON(const SymbolID &);
+
 // URI in "file" scheme for a file.
 struct URIForFile {
   URIForFile() = default;
@@ -1379,58 +1382,66 @@ bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
 /// The type hierarchy params is an extension of the
 /// `TextDocumentPositionsParams` with optional properties which can be used to
 /// eagerly resolve the item when requesting from the server.
-struct TypeHierarchyParams : public TextDocumentPositionParams {
+struct TypeHierarchyPrepareParams : public TextDocumentPositionParams {
   /// The hierarchy levels to resolve. `0` indicates no level.
+  /// This is a clangd extension.
   int resolve = 0;
 
   /// The direction of the hierarchy levels to resolve.
+  /// This is a clangd extension.
   TypeHierarchyDirection direction = TypeHierarchyDirection::Parents;
 };
-bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &,
+bool fromJSON(const llvm::json::Value &, TypeHierarchyPrepareParams &,
               llvm::json::Path);
 
 struct TypeHierarchyItem {
-  /// The human readable name of the hierarchy item.
+  /// The name of this item.
   std::string name;
 
-  /// Optional detail for the hierarchy item. It can be, for instance, the
-  /// signature of a function or method.
-  llvm::Optional<std::string> detail;
-
-  /// The kind of the hierarchy item. For instance, class or interface.
+  /// The kind of this item.
   SymbolKind kind;
 
-  /// `true` if the hierarchy item is deprecated. Otherwise, `false`.
-  bool deprecated = false;
+  /// More detail for this item, e.g. the signature of a function.
+  llvm::Optional<std::string> detail;
 
-  /// The URI of the text document where this type hierarchy item belongs to.
+  /// The resource identifier of this item.
   URIForFile uri;
 
-  /// The range enclosing this type hierarchy item not including
-  /// leading/trailing whitespace but everything else like comments. This
-  /// information is typically used to determine if the client's cursor is
-  /// inside the type hierarch item to reveal in the symbol in the UI.
+  /// The range enclosing this symbol not including leading/trailing whitespace
+  /// but everything else, e.g. comments and code.
   Range range;
 
-  /// The range that should be selected and revealed when this type hierarchy
-  /// item is being picked, e.g. the name of a function. Must be contained by
-  /// the `range`.
+  /// The range that should be selected and revealed when this symbol is being
+  /// picked, e.g. the name of a function. Must be contained by the `range`.
   Range selectionRange;
 
-  /// If this type hierarchy item is resolved, it contains the direct parents.
-  /// Could be empty if the item does not have direct parents. If not defined,
-  /// the parents have not been resolved yet.
+  /// Used to resolve a client provided item back.
+  struct ResolveParams {
+    SymbolID symbolID;
+    /// None means parents aren't resolved and empty is no parents.
+    llvm::Optional<std::vector<ResolveParams>> parents;
+  };
+  /// A data entry field that is preserved between a type hierarchy prepare and
+  /// supertypes or subtypes requests. It could also be used to identify the
+  /// type hierarchy in the server, helping improve the performance on resolving
+  /// supertypes and subtypes.
+  ResolveParams data;
+
+  /// `true` if the hierarchy item is deprecated. Otherwise, `false`.
+  /// This is a clangd exntesion.
+  bool deprecated = false;
+
+  /// This is a clangd exntesion.
   llvm::Optional<std::vector<TypeHierarchyItem>> parents;
 
   /// If this type hierarchy item is resolved, it contains the direct children
   /// of the current item. Could be empty if the item does not have any
   /// descendants. If not defined, the children have not been resolved.
+  /// This is a clangd exntesion.
   llvm::Optional<std::vector<TypeHierarchyItem>> children;
-
-  /// An optional 'data' field, which can be used to identify a type hierarchy
-  /// item in a resolve request.
-  llvm::Optional<std::string> data;
 };
+llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &);
+bool fromJSON(const TypeHierarchyItem::ResolveParams &);
 llvm::json::Value toJSON(const TypeHierarchyItem &);
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &);
 bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &, llvm::json::Path);

diff  --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index c6a843ec1db43..25ddcbdbd7398 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -51,6 +51,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallSet.h"
@@ -60,6 +61,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -864,7 +866,8 @@ class ReferenceFinder : public index::IndexDataConsumer {
   };
 
   ReferenceFinder(const ParsedAST &AST,
-                  const llvm::ArrayRef<const NamedDecl *> Targets, bool PerToken)
+                  const llvm::ArrayRef<const NamedDecl *> Targets,
+                  bool PerToken)
       : PerToken(PerToken), AST(AST) {
     for (const NamedDecl *ND : Targets) {
       const Decl *CD = ND->getCanonicalDecl();
@@ -937,7 +940,7 @@ class ReferenceFinder : public index::IndexDataConsumer {
 };
 
 std::vector<ReferenceFinder::Reference>
-findRefs(const llvm::ArrayRef<const NamedDecl*> TargetDecls, ParsedAST &AST,
+findRefs(const llvm::ArrayRef<const NamedDecl *> TargetDecls, ParsedAST &AST,
          bool PerToken) {
   ReferenceFinder RefFinder(AST, TargetDecls, PerToken);
   index::IndexingOptions IndexOpts;
@@ -1225,8 +1228,8 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
     if (const SelectionTree::Node *N = ST.commonAncestor()) {
       DeclRelationSet Relations =
           DeclRelation::TemplatePattern | DeclRelation::Alias;
-      auto TargetDecls=
-           targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver());
+      auto TargetDecls =
+          targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver());
       if (!TargetDecls.empty()) {
         // FIXME: we may get multiple DocumentHighlights with the same location
         // and 
diff erent kinds, deduplicate them.
@@ -1595,29 +1598,32 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
 
   HI.uri = URIForFile::canonicalize(*FilePath, TUPath);
 
-  // Compute the SymbolID and store it in the 'data' field.
-  // This allows typeHierarchy/resolve to be used to
-  // resolve children of items returned in a previous request
-  // for parents.
-  if (auto ID = getSymbolID(&ND))
-    HI.data = ID.str();
-
   return HI;
 }
 
 static llvm::Optional<TypeHierarchyItem>
 declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   auto Result = declToHierarchyItem<TypeHierarchyItem>(ND, TUPath);
-  if (Result)
+  if (Result) {
     Result->deprecated = ND.isDeprecated();
+    // Compute the SymbolID and store it in the 'data' field.
+    // This allows typeHierarchy/resolve to be used to
+    // resolve children of items returned in a previous request
+    // for parents.
+    Result->data.symbolID = getSymbolID(&ND);
+  }
   return Result;
 }
 
 static llvm::Optional<CallHierarchyItem>
 declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   auto Result = declToHierarchyItem<CallHierarchyItem>(ND, TUPath);
-  if (Result && ND.isDeprecated())
+  if (!Result)
+    return Result;
+  if (ND.isDeprecated())
     Result->tags.push_back(SymbolTag::Deprecated);
+  if (auto ID = getSymbolID(&ND))
+    Result->data = ID.str();
   return Result;
 }
 
@@ -1637,10 +1643,6 @@ static llvm::Optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
   // (https://github.com/clangd/clangd/issues/59).
   HI.range = HI.selectionRange;
   HI.uri = Loc->uri;
-  // Store the SymbolID in the 'data' field. The client will
-  // send this back in requests to resolve additional levels
-  // of the hierarchy.
-  HI.data = S.ID.str();
 
   return HI;
 }
@@ -1648,15 +1650,20 @@ static llvm::Optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
 static llvm::Optional<TypeHierarchyItem>
 symbolToTypeHierarchyItem(const Symbol &S, PathRef TUPath) {
   auto Result = symbolToHierarchyItem<TypeHierarchyItem>(S, TUPath);
-  if (Result)
+  if (Result) {
     Result->deprecated = (S.Flags & Symbol::Deprecated);
+    Result->data.symbolID = S.ID;
+  }
   return Result;
 }
 
 static llvm::Optional<CallHierarchyItem>
 symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) {
   auto Result = symbolToHierarchyItem<CallHierarchyItem>(S, TUPath);
-  if (Result && (S.Flags & Symbol::Deprecated))
+  if (!Result)
+    return Result;
+  Result->data = S.ID.str();
+  if (S.Flags & Symbol::Deprecated)
     Result->tags.push_back(SymbolTag::Deprecated);
   return Result;
 }
@@ -1681,9 +1688,12 @@ static void fillSubTypes(const SymbolID &ID,
 
 using RecursionProtectionSet = llvm::SmallSet<const CXXRecordDecl *, 4>;
 
+// Extracts parents from AST and populates the type hierarchy item.
 static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
-                           std::vector<TypeHierarchyItem> &SuperTypes,
+                           TypeHierarchyItem &Item,
                            RecursionProtectionSet &RPSet) {
+  Item.parents.emplace();
+  Item.data.parents.emplace();
   // typeParents() will replace dependent template specializations
   // with their class template, so to avoid infinite recursion for
   // certain types of hierarchies, keep the templates encountered
@@ -1699,9 +1709,9 @@ static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
   for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) {
     if (Optional<TypeHierarchyItem> ParentSym =
             declToTypeHierarchyItem(*ParentDecl, TUPath)) {
-      ParentSym->parents.emplace();
-      fillSuperTypes(*ParentDecl, TUPath, *ParentSym->parents, RPSet);
-      SuperTypes.emplace_back(std::move(*ParentSym));
+      fillSuperTypes(*ParentDecl, TUPath, *ParentSym, RPSet);
+      Item.data.parents->emplace_back(ParentSym->data);
+      Item.parents->emplace_back(std::move(*ParentSym));
     }
   }
 
@@ -1710,11 +1720,12 @@ static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
   }
 }
 
-const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) {
-  auto RecordFromNode =
-      [&AST](const SelectionTree::Node *N) -> const CXXRecordDecl * {
+std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST,
+                                                    Position Pos) {
+  auto RecordFromNode = [&AST](const SelectionTree::Node *N) {
+    std::vector<const CXXRecordDecl *> Records;
     if (!N)
-      return nullptr;
+      return Records;
 
     // Note: explicitReferenceTargets() will search for both template
     // instantiations and template patterns, and prefer the former if available
@@ -1722,30 +1733,32 @@ const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) {
     // class template).
     auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying,
                                           AST.getHeuristicResolver());
-    if (Decls.empty())
-      return nullptr;
-
-    const NamedDecl *D = Decls[0];
+    for (const NamedDecl *D : Decls) {
 
-    if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
-      // If this is a variable, use the type of the variable.
-      return VD->getType().getTypePtr()->getAsCXXRecordDecl();
-    }
+      if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
+        // If this is a variable, use the type of the variable.
+        Records.push_back(VD->getType().getTypePtr()->getAsCXXRecordDecl());
+        continue;
+      }
 
-    if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) {
-      // If this is a method, use the type of the class.
-      return Method->getParent();
-    }
+      if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) {
+        // If this is a method, use the type of the class.
+        Records.push_back(Method->getParent());
+        continue;
+      }
 
-    // We don't handle FieldDecl because it's not clear what behaviour
-    // the user would expect: the enclosing class type (as with a
-    // method), or the field's type (as with a variable).
+      // We don't handle FieldDecl because it's not clear what behaviour
+      // the user would expect: the enclosing class type (as with a
+      // method), or the field's type (as with a variable).
 
-    return dyn_cast<CXXRecordDecl>(D);
+      if (auto *RD = dyn_cast<CXXRecordDecl>(D))
+        Records.push_back(RD);
+    }
+    return Records;
   };
 
   const SourceManager &SM = AST.getSourceManager();
-  const CXXRecordDecl *Result = nullptr;
+  std::vector<const CXXRecordDecl *> Result;
   auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos);
   if (!Offset) {
     llvm::consumeError(Offset.takeError());
@@ -1754,7 +1767,7 @@ const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) {
   SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset,
                             *Offset, [&](SelectionTree ST) {
                               Result = RecordFromNode(ST.commonAncestor());
-                              return Result != nullptr;
+                              return !Result.empty();
                             });
   return Result;
 }
@@ -1998,53 +2011,79 @@ std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD) {
   return Result;
 }
 
-llvm::Optional<TypeHierarchyItem>
+std::vector<TypeHierarchyItem>
 getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
                  TypeHierarchyDirection Direction, const SymbolIndex *Index,
                  PathRef TUPath) {
-  const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos);
-  if (!CXXRD)
-    return llvm::None;
+  std::vector<TypeHierarchyItem> Results;
+  for (const auto *CXXRD : findRecordTypeAt(AST, Pos)) {
+
+    bool WantChildren = Direction == TypeHierarchyDirection::Children ||
+                        Direction == TypeHierarchyDirection::Both;
+
+    // If we're looking for children, we're doing the lookup in the index.
+    // The index does not store relationships between implicit
+    // specializations, so if we have one, use the template pattern instead.
+    // Note that this needs to be done before the declToTypeHierarchyItem(),
+    // otherwise the type hierarchy item would misleadingly contain the
+    // specialization parameters, while the children would involve classes
+    // that derive from other specializations of the template.
+    if (WantChildren) {
+      if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
+        CXXRD = CTSD->getTemplateInstantiationPattern();
+    }
 
-  bool WantParents = Direction == TypeHierarchyDirection::Parents ||
-                     Direction == TypeHierarchyDirection::Both;
-  bool WantChildren = Direction == TypeHierarchyDirection::Children ||
-                      Direction == TypeHierarchyDirection::Both;
-
-  // If we're looking for children, we're doing the lookup in the index.
-  // The index does not store relationships between implicit
-  // specializations, so if we have one, use the template pattern instead.
-  // Note that this needs to be done before the declToTypeHierarchyItem(),
-  // otherwise the type hierarchy item would misleadingly contain the
-  // specialization parameters, while the children would involve classes
-  // that derive from other specializations of the template.
-  if (WantChildren) {
-    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
-      CXXRD = CTSD->getTemplateInstantiationPattern();
-  }
+    Optional<TypeHierarchyItem> Result =
+        declToTypeHierarchyItem(*CXXRD, AST.tuPath());
+    if (!Result)
+      continue;
 
-  Optional<TypeHierarchyItem> Result =
-      declToTypeHierarchyItem(*CXXRD, AST.tuPath());
-  if (!Result)
-    return Result;
+    RecursionProtectionSet RPSet;
+    fillSuperTypes(*CXXRD, AST.tuPath(), *Result, RPSet);
 
-  if (WantParents) {
-    Result->parents.emplace();
+    if (WantChildren && ResolveLevels > 0) {
+      Result->children.emplace();
 
-    RecursionProtectionSet RPSet;
-    fillSuperTypes(*CXXRD, AST.tuPath(), *Result->parents, RPSet);
+      if (Index) {
+        if (auto ID = getSymbolID(CXXRD))
+          fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath);
+      }
+    }
+    Results.emplace_back(std::move(*Result));
   }
 
-  if (WantChildren && ResolveLevels > 0) {
-    Result->children.emplace();
+  return Results;
+}
 
-    if (Index) {
-      if (auto ID = getSymbolID(CXXRD))
-        fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath);
-    }
+llvm::Optional<std::vector<TypeHierarchyItem>>
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
+  std::vector<TypeHierarchyItem> Results;
+  if (!Item.data.parents)
+    return llvm::None;
+  if (Item.data.parents->empty())
+    return Results;
+  LookupRequest Req;
+  llvm::DenseMap<SymbolID, const TypeHierarchyItem::ResolveParams *> IDToData;
+  for (const auto &Parent : *Item.data.parents) {
+    Req.IDs.insert(Parent.symbolID);
+    IDToData[Parent.symbolID] = &Parent;
   }
+  Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) {
+    if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) {
+      THI->data = *IDToData.lookup(S.ID);
+      Results.emplace_back(std::move(*THI));
+    }
+  });
+  return Results;
+}
 
-  return Result;
+std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
+                                        const SymbolIndex *Index) {
+  std::vector<TypeHierarchyItem> Results;
+  fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file());
+  for (auto &ChildSym : Results)
+    ChildSym.data.parents = {Item.data};
+  return Results;
 }
 
 void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
@@ -2052,18 +2091,13 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                           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)
+  if (!Index || 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<SymbolID> ID = SymbolID::fromStr(*Item.data)) {
-      fillSubTypes(*ID, *Item.children, Index, ResolveLevels, Item.uri.file());
-    }
-  }
+  fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels,
+               Item.uri.file());
 }
 
 std::vector<CallHierarchyItem>

diff  --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 4ccb24cd7bd6d..7667bbd558326 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -117,17 +117,26 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
 /// Get info about symbols at \p Pos.
 std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos);
 
-/// Find the record type references at \p Pos.
-const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos);
+/// Find the record types referenced at \p Pos.
+std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST,
+                                                    Position Pos);
 
 /// Given a record type declaration, find its base (parent) types.
 std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD);
 
 /// Get type hierarchy information at \p Pos.
-llvm::Optional<TypeHierarchyItem> getTypeHierarchy(
+std::vector<TypeHierarchyItem> getTypeHierarchy(
     ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction,
     const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
 
+/// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside
+/// the item.
+llvm::Optional<std::vector<TypeHierarchyItem>>
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
+/// Returns direct children of a TypeHierarchyItem.
+std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
+                                        const SymbolIndex *Index);
+
 void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                           TypeHierarchyDirection Direction,
                           const SymbolIndex *Index);

diff  --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test
index c795ab5940432..8387fb9b504be 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -88,6 +88,7 @@
 # CHECK-NEXT:          ","
 # CHECK-NEXT:        ]
 # CHECK-NEXT:      },
+# CHECK-NEXT:      "standardTypeHierarchyProvider": true,
 # CHECK-NEXT:      "textDocumentSync": {
 # CHECK-NEXT:        "change": 2,
 # CHECK-NEXT:        "openClose": true,

diff  --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
new file mode 100644
index 0000000000000..ddb9a014be0c7
--- /dev/null
+++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
@@ -0,0 +1,211 @@
+# 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 {};\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
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": {
+# CHECK-NEXT:    "children": [
+# CHECK-NEXT:      {
+# CHECK-NEXT:        "data": {
+# CHECK-NEXT:           "symbolID": "A6576FE083F2949A"
+# 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:    "data": {
+# CHECK-NEXT:        "parents": [
+# CHECK-NEXT:         {
+# CHECK-NEXT:          "parents": [
+# CHECK-NEXT:            {
+# CHECK-NEXT:             "parents": [],
+# CHECK-NEXT:             "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:            }
+# CHECK-NEXT:          ],
+# CHECK-NEXT:          "symbolID": "ECDC0C46D75120F4"
+# CHECK-NEXT:         }
+# CHECK-NEXT:        ],
+# CHECK-NEXT:        "symbolID": "8A991335E4E67D08"
+# CHECK-NEXT:    },
+# CHECK-NEXT:    "kind": 23,
+# CHECK-NEXT:    "name": "Child2",
+# CHECK-NEXT:    "parents": [
+# CHECK-NEXT:      {
+# CHECK-NEXT:        "data": {
+# CHECK-NEXT:          "parents": [
+# CHECK-NEXT:            {
+# CHECK-NEXT:             "parents": [],
+# CHECK-NEXT:             "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:            }
+# CHECK-NEXT:          ],
+# CHECK-NEXT:          "symbolID": "ECDC0C46D75120F4"
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "kind": 23,
+# CHECK-NEXT:        "name": "Child1",
+# CHECK-NEXT:        "parents": [
+# CHECK-NEXT:          {
+# CHECK-NEXT:            "data": {
+# CHECK-NEXT:             "parents": [],
+# CHECK-NEXT:             "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "kind": 23,
+# CHECK-NEXT:            "name": "Parent",
+# CHECK-NEXT:            "parents": [],
+# CHECK-NEXT:            "range": {
+# CHECK-NEXT:              "end": {
+# CHECK-NEXT:                "character": 16,
+# CHECK-NEXT:                "line": 0
+# CHECK-NEXT:              },
+# CHECK-NEXT:              "start": {
+# CHECK-NEXT:                "character": 0,
+# CHECK-NEXT:                "line": 0
+# CHECK-NEXT:              }
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "selectionRange": {
+# CHECK-NEXT:              "end": {
+# CHECK-NEXT:                "character": 13,
+# CHECK-NEXT:                "line": 0
+# CHECK-NEXT:              },
+# CHECK-NEXT:              "start": {
+# CHECK-NEXT:                "character": 7,
+# CHECK-NEXT:                "line": 0
+# CHECK-NEXT:              }
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:          }
+# CHECK-NEXT:        ],
+# CHECK-NEXT:        "range": {
+# CHECK-NEXT:          "end": {
+# CHECK-NEXT:            "character": 25,
+# CHECK-NEXT:            "line": 1
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "start": {
+# CHECK-NEXT:            "character": 0,
+# CHECK-NEXT:            "line": 1
+# CHECK-NEXT:          }
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "selectionRange": {
+# CHECK-NEXT:          "end": {
+# CHECK-NEXT:            "character": 13,
+# CHECK-NEXT:            "line": 1
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "start": {
+# CHECK-NEXT:            "character": 7,
+# CHECK-NEXT:            "line": 1
+# CHECK-NEXT:          }
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:      }
+# CHECK-NEXT:    ],
+# CHECK-NEXT:    "range": {
+# CHECK-NEXT:      "end": {
+# CHECK-NEXT:        "character": 25,
+# CHECK-NEXT:        "line": 2
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "start": {
+# CHECK-NEXT:        "character": 0,
+# CHECK-NEXT:        "line": 2
+# CHECK-NEXT:      }
+# CHECK-NEXT:    },
+# CHECK-NEXT:    "selectionRange": {
+# CHECK-NEXT:      "end": {
+# CHECK-NEXT:        "character": 13,
+# CHECK-NEXT:        "line": 2
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "start": {
+# CHECK-NEXT:        "character": 7,
+# CHECK-NEXT:        "line": 2
+# CHECK-NEXT:      }
+# CHECK-NEXT:    },
+# CHECK-NEXT:    "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:  }
+---
+{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":{"symbolID":"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": {
+# CHECK-NEXT:          "symbolID": "5705B382DFC77CBC"
+# CHECK-NEXT:        },
+# 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": {
+# CHECK-NEXT:      "symbolID": "A6576FE083F2949A"
+# 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:  }
+---
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}

diff  --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index cb38e49cf61e3..69751000a7c6c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -3,178 +3,140 @@
 ---
 {"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}}
+{"jsonrpc":"2.0","id":1,"method":"textDocument/prepareTypeHierarchy","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:        "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:      }
-# CHECK-NEXT:    ],
-# CHECK-NEXT:    "data": "8A991335E4E67D08",
-# CHECK-NEXT:    "kind": 23,
-# CHECK-NEXT:    "name": "Child2",
-# CHECK-NEXT:    "parents": [
-# CHECK-NEXT:      {
-# CHECK-NEXT:        "data": "ECDC0C46D75120F4",
-# CHECK-NEXT:        "kind": 23,
-# CHECK-NEXT:        "name": "Child1",
-# CHECK-NEXT:        "parents": [
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:     {
+# CHECK-NEXT:       "data": {
+# CHECK-NEXT:         "parents": [
 # CHECK-NEXT:          {
-# CHECK-NEXT:            "data": "FE546E7B648D69A7",
-# CHECK-NEXT:            "kind": 23,
-# CHECK-NEXT:            "name": "Parent",
-# CHECK-NEXT:            "parents": [],
-# CHECK-NEXT:            "range": {
-# CHECK-NEXT:              "end": {
-# CHECK-NEXT:                "character": 16,
-# CHECK-NEXT:                "line": 0
-# CHECK-NEXT:              },
-# CHECK-NEXT:              "start": {
-# CHECK-NEXT:                "character": 0,
-# CHECK-NEXT:                "line": 0
-# CHECK-NEXT:              }
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "selectionRange": {
-# CHECK-NEXT:              "end": {
-# CHECK-NEXT:                "character": 13,
-# CHECK-NEXT:                "line": 0
-# CHECK-NEXT:              },
-# CHECK-NEXT:              "start": {
-# CHECK-NEXT:                "character": 7,
-# CHECK-NEXT:                "line": 0
-# CHECK-NEXT:              }
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:           "parents": [
+# CHECK-NEXT:             {
+# CHECK-NEXT:              "parents": [],
+# CHECK-NEXT:              "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:             }
+# CHECK-NEXT:           ],
+# CHECK-NEXT:           "symbolID": "ECDC0C46D75120F4"
 # CHECK-NEXT:          }
 # CHECK-NEXT:        ],
-# CHECK-NEXT:        "range": {
-# CHECK-NEXT:          "end": {
-# CHECK-NEXT:            "character": 25,
-# CHECK-NEXT:            "line": 1
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "start": {
-# CHECK-NEXT:            "character": 0,
-# CHECK-NEXT:            "line": 1
-# CHECK-NEXT:          }
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "selectionRange": {
-# CHECK-NEXT:          "end": {
-# CHECK-NEXT:            "character": 13,
-# CHECK-NEXT:            "line": 1
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "start": {
-# CHECK-NEXT:            "character": 7,
-# CHECK-NEXT:            "line": 1
-# CHECK-NEXT:          }
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "uri": "file://{{.*}}/clangd-test/main.cpp"
-# CHECK-NEXT:      }
-# CHECK-NEXT:    ],
-# CHECK-NEXT:    "range": {
-# CHECK-NEXT:      "end": {
-# CHECK-NEXT:        "character": 25,
-# CHECK-NEXT:        "line": 2
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "start": {
-# CHECK-NEXT:        "character": 0,
-# CHECK-NEXT:        "line": 2
-# CHECK-NEXT:      }
-# CHECK-NEXT:    },
-# CHECK-NEXT:    "selectionRange": {
-# CHECK-NEXT:      "end": {
-# CHECK-NEXT:        "character": 13,
-# CHECK-NEXT:        "line": 2
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "start": {
-# CHECK-NEXT:        "character": 7,
-# CHECK-NEXT:        "line": 2
-# CHECK-NEXT:      }
-# CHECK-NEXT:    },
-# CHECK-NEXT:    "uri": "file://{{.*}}/clangd-test/main.cpp"
-# CHECK-NEXT:  }
+# CHECK-NEXT:        "symbolID": "8A991335E4E67D08"
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "kind": 23,
+# CHECK-NEXT:       "name": "Child2",
+# CHECK-NEXT:       "range": {
+# CHECK-NEXT:         "end": {
+# CHECK-NEXT:           "character": 25,
+# CHECK-NEXT:           "line": 2
+# CHECK-NEXT:         },
+# CHECK-NEXT:         "start": {
+# CHECK-NEXT:           "character": 0,
+# CHECK-NEXT:           "line": 2
+# CHECK-NEXT:         }
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "selectionRange": {
+# CHECK-NEXT:         "end": {
+# CHECK-NEXT:           "character": 13,
+# CHECK-NEXT:           "line": 2
+# CHECK-NEXT:         },
+# CHECK-NEXT:         "start": {
+# CHECK-NEXT:           "character": 7,
+# CHECK-NEXT:           "line": 2
+# CHECK-NEXT:         }
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:     }
+# CHECK-NEXT:   ]
 ---
-{"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}}
+{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/supertypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}}
 #      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:  }
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:     {
+# CHECK-NEXT:       "data": {
+# CHECK-NEXT:         "parents": [
+# CHECK-NEXT:           {
+# CHECK-NEXT:            "parents": [],
+# CHECK-NEXT:            "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:           }
+# CHECK-NEXT:         ],
+# CHECK-NEXT:         "symbolID": "ECDC0C46D75120F4"
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "kind": 23,
+# CHECK-NEXT:       "name": "Child1",
+# CHECK-NEXT:       "range": {
+# CHECK-NEXT:         "end": {
+# CHECK-NEXT:           "character": 13,
+# CHECK-NEXT:           "line": 1
+# CHECK-NEXT:         },
+# CHECK-NEXT:         "start": {
+# CHECK-NEXT:           "character": 7,
+# CHECK-NEXT:           "line": 1
+# CHECK-NEXT:         }
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "selectionRange": {
+# CHECK-NEXT:         "end": {
+# CHECK-NEXT:           "character": 13,
+# CHECK-NEXT:           "line": 1
+# CHECK-NEXT:         },
+# CHECK-NEXT:         "start": {
+# CHECK-NEXT:           "character": 7,
+# CHECK-NEXT:           "line": 1
+# CHECK-NEXT:         }
+# CHECK-NEXT:       },
+# CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
+# CHECK-NEXT:     }
+# CHECK-NEXT:  ]
+---
+{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/subtypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}}
+#      CHECK:  "id": 2
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:     {
+# CHECK-NEXT:       "data": {
+# CHECK-NEXT:         "parents": [
+# CHECK-NEXT:          {
+# CHECK-NEXT:           "parents": [
+# CHECK-NEXT:            {
+# CHECK-NEXT:             "parents": [
+# CHECK-NEXT:               {
+# CHECK-NEXT:                "parents": [],
+# CHECK-NEXT:                "symbolID": "FE546E7B648D69A7"
+# CHECK-NEXT:               }
+# CHECK-NEXT:             ],
+# CHECK-NEXT:             "symbolID": "ECDC0C46D75120F4"
+# CHECK-NEXT:            }
+# CHECK-NEXT:           ],
+# CHECK-NEXT:           "symbolID": "8A991335E4E67D08"
+# CHECK-NEXT:         }
+# CHECK-NEXT:        ],
+# CHECK-NEXT:        "symbolID": "A6576FE083F2949A"
+# 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:  ]
 ---
 {"jsonrpc":"2.0","id":3,"method":"shutdown"}
 ---

diff  --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index 7cdb6dcc03b61..fba7f3ea2155f 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -5,6 +5,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+#include "AST.h"
 #include "Annotations.h"
 #include "Matchers.h"
 #include "ParsedAST.h"
@@ -16,6 +17,7 @@
 #include "llvm/Support/Path.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <vector>
 
 namespace clang {
 namespace clangd {
@@ -26,6 +28,7 @@ using ::testing::ElementsAre;
 using ::testing::Field;
 using ::testing::IsEmpty;
 using ::testing::Matcher;
+using ::testing::SizeIs;
 using ::testing::UnorderedElementsAre;
 
 // GMock helpers for matching TypeHierarchyItem.
@@ -45,6 +48,10 @@ ::testing::Matcher<TypeHierarchyItem> children(ChildMatchers... ChildrenM) {
 // Note: "not resolved" is 
diff erent from "resolved but empty"!
 MATCHER(parentsNotResolved, "") { return !arg.parents; }
 MATCHER(childrenNotResolved, "") { return !arg.children; }
+MATCHER_P(withResolveID, SID, "") { return arg.symbolID.str() == SID; }
+MATCHER_P(withResolveParents, M, "") {
+  return testing::ExplainMatchResult(M, arg.data.parents, result_listener);
+}
 
 TEST(FindRecordTypeAt, TypeOrVariable) {
   Annotations Source(R"cpp(
@@ -64,8 +71,10 @@ int main() {
   auto AST = TU.build();
 
   for (Position Pt : Source.points()) {
-    const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
-    EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD));
+    auto Records = findRecordTypeAt(AST, Pt);
+    ASSERT_THAT(Records, SizeIs(1));
+    EXPECT_EQ(&findDecl(AST, "Child2"),
+              static_cast<const NamedDecl *>(Records.front()));
   }
 }
 
@@ -86,8 +95,10 @@ int main() {
   auto AST = TU.build();
 
   for (Position Pt : Source.points()) {
-    const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
-    EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD));
+    auto Records = findRecordTypeAt(AST, Pt);
+    ASSERT_THAT(Records, SizeIs(1));
+    EXPECT_EQ(&findDecl(AST, "Child2"),
+              static_cast<const NamedDecl *>(Records.front()));
   }
 }
 
@@ -107,11 +118,10 @@ int main() {
   auto AST = TU.build();
 
   for (Position Pt : Source.points()) {
-    const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
     // A field does not unambiguously specify a record type
     // (possible associated reocrd types could be the field's type,
     // or the type of the record that the field is a member of).
-    EXPECT_EQ(nullptr, RD);
+    EXPECT_THAT(findRecordTypeAt(AST, Pt), SizeIs(0));
   }
 }
 
@@ -359,11 +369,11 @@ int main() {
   for (Position Pt : Source.points()) {
     // Set ResolveLevels to 0 because it's only used for Children;
     // for Parents, getTypeHierarchy() always returns all levels.
-    llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-        AST, Pt, /*ResolveLevels=*/0, TypeHierarchyDirection::Parents);
-    ASSERT_TRUE(bool(Result));
+    auto Result = getTypeHierarchy(AST, Pt, /*ResolveLevels=*/0,
+                                   TypeHierarchyDirection::Parents);
+    ASSERT_THAT(Result, SizeIs(1));
     EXPECT_THAT(
-        *Result,
+        Result.front(),
         AllOf(
             withName("Child"), withKind(SymbolKind::Struct),
             parents(AllOf(withName("Parent1"), withKind(SymbolKind::Struct),
@@ -398,11 +408,11 @@ TEST(TypeHierarchy, RecursiveHierarchyUnbounded) {
   // The parent is reported as "S" because "S<0>" is an invalid instantiation.
   // We then iterate once more and find "S" again before detecting the
   // recursion.
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.points()[0], 0, TypeHierarchyDirection::Parents);
-  ASSERT_TRUE(bool(Result));
+  auto Result = getTypeHierarchy(AST, Source.points()[0], 0,
+                                 TypeHierarchyDirection::Parents);
+  ASSERT_THAT(Result, SizeIs(1));
   EXPECT_THAT(
-      *Result,
+      Result.front(),
       AllOf(withName("S<0>"), withKind(SymbolKind::Struct),
             parents(
                 AllOf(withName("S"), withKind(SymbolKind::Struct),
@@ -432,11 +442,11 @@ TEST(TypeHierarchy, RecursiveHierarchyBounded) {
 
   // Make sure getTypeHierarchy() doesn't get into an infinite recursion
   // for either a concrete starting point or a dependent starting point.
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.point("SRefConcrete"), 0, TypeHierarchyDirection::Parents);
-  ASSERT_TRUE(bool(Result));
+  auto Result = getTypeHierarchy(AST, Source.point("SRefConcrete"), 0,
+                                 TypeHierarchyDirection::Parents);
+  ASSERT_THAT(Result, SizeIs(1));
   EXPECT_THAT(
-      *Result,
+      Result.front(),
       AllOf(withName("S<2>"), withKind(SymbolKind::Struct),
             parents(AllOf(
                 withName("S<1>"), withKind(SymbolKind::Struct),
@@ -445,9 +455,9 @@ TEST(TypeHierarchy, RecursiveHierarchyBounded) {
                               parents()))))));
   Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0,
                             TypeHierarchyDirection::Parents);
-  ASSERT_TRUE(bool(Result));
+  ASSERT_THAT(Result, SizeIs(1));
   EXPECT_THAT(
-      *Result,
+      Result.front(),
       AllOf(withName("S"), withKind(SymbolKind::Struct),
             parents(AllOf(withName("S"), withKind(SymbolKind::Struct),
                           selectionRangeIs(Source.range("SDef")), parents()))));
@@ -469,11 +479,11 @@ TEST(TypeHierarchy, DeriveFromImplicitSpec) {
   auto AST = TU.build();
   auto Index = TU.index();
 
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
-      testPath(TU.Filename));
-  ASSERT_TRUE(bool(Result));
-  EXPECT_THAT(*Result,
+  auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
+  EXPECT_THAT(Result.front(),
               AllOf(withName("Parent"), withKind(SymbolKind::Struct),
                     children(AllOf(withName("Child1"),
                                    withKind(SymbolKind::Struct), children()),
@@ -495,12 +505,12 @@ TEST(TypeHierarchy, DeriveFromPartialSpec) {
   auto AST = TU.build();
   auto Index = TU.index();
 
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
-      testPath(TU.Filename));
-  ASSERT_TRUE(bool(Result));
-  EXPECT_THAT(*Result, AllOf(withName("Parent"), withKind(SymbolKind::Struct),
-                             children()));
+  auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
+  EXPECT_THAT(Result.front(), AllOf(withName("Parent"),
+                                    withKind(SymbolKind::Struct), children()));
 }
 
 TEST(TypeHierarchy, DeriveFromTemplate) {
@@ -521,11 +531,11 @@ TEST(TypeHierarchy, DeriveFromTemplate) {
   // FIXME: We'd like this to show the implicit specializations Parent<int>
   //        and Child<int>, but currently libIndex does not expose relationships
   //        between implicit specializations.
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(),
-      testPath(TU.Filename));
-  ASSERT_TRUE(bool(Result));
-  EXPECT_THAT(*Result,
+  auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
+  EXPECT_THAT(Result.front(),
               AllOf(withName("Parent"), withKind(SymbolKind::Struct),
                     children(AllOf(withName("Child"),
                                    withKind(SymbolKind::Struct), children()))));
@@ -546,12 +556,12 @@ struct [[Parent]] {
   TU.HeaderCode = HeaderInPreambleAnnotations.code().str();
   auto AST = TU.build();
 
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+  std::vector<TypeHierarchyItem> Result = getTypeHierarchy(
       AST, SourceAnnotations.point(), 1, TypeHierarchyDirection::Parents);
 
-  ASSERT_TRUE(Result);
+  ASSERT_THAT(Result, SizeIs(1));
   EXPECT_THAT(
-      *Result,
+      Result.front(),
       AllOf(withName("Child"),
             parents(AllOf(withName("Parent"),
                           selectionRangeIs(HeaderInPreambleAnnotations.range()),
@@ -722,22 +732,21 @@ struct Child2b : Child1 {};
   auto AST = TU.build();
   auto Index = TU.index();
 
-  llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
-      AST, Source.point(), /*ResolveLevels=*/1,
-      TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename));
-  ASSERT_TRUE(bool(Result));
+  auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
   EXPECT_THAT(
-      *Result,
-      AllOf(withName("Parent"), withKind(SymbolKind::Struct),
-            parentsNotResolved(),
+      Result.front(),
+      AllOf(withName("Parent"), withKind(SymbolKind::Struct), parents(),
             children(AllOf(withName("Child1"), withKind(SymbolKind::Struct),
                            parentsNotResolved(), childrenNotResolved()))));
 
-  resolveTypeHierarchy((*Result->children)[0], /*ResolveLevels=*/1,
+  resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1,
                        TypeHierarchyDirection::Children, Index.get());
 
   EXPECT_THAT(
-      (*Result->children)[0],
+      (*Result.front().children)[0],
       AllOf(withName("Child1"), withKind(SymbolKind::Struct),
             parentsNotResolved(),
             children(AllOf(withName("Child2a"), withKind(SymbolKind::Struct),
@@ -746,6 +755,53 @@ struct Child2b : Child1 {};
                            parentsNotResolved(), childrenNotResolved()))));
 }
 
+TEST(Standard, SubTypes) {
+  Annotations Source(R"cpp(
+struct Pare^nt1 {};
+struct Parent2 {};
+struct Child : Parent1, Parent2 {};
+)cpp");
+
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+  auto Index = TU.index();
+
+  auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
+  auto Children = subTypes(Result.front(), Index.get());
+
+  // Make sure parents are populated when getting children.
+  // FIXME: This is partial.
+  EXPECT_THAT(
+      Children,
+      UnorderedElementsAre(
+          AllOf(withName("Child"),
+                withResolveParents(HasValue(UnorderedElementsAre(withResolveID(
+                    getSymbolID(&findDecl(AST, "Parent1")).str())))))));
+}
+
+TEST(Standard, SuperTypes) {
+  Annotations Source(R"cpp(
+struct Parent {};
+struct Chil^d : Parent {};
+)cpp");
+
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+  auto Index = TU.index();
+
+  auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
+                                 TypeHierarchyDirection::Children, Index.get(),
+                                 testPath(TU.Filename));
+  ASSERT_THAT(Result, SizeIs(1));
+  auto Parents = superTypes(Result.front(), Index.get());
+
+  EXPECT_THAT(Parents, HasValue(UnorderedElementsAre(
+                           AllOf(withName("Parent"),
+                                 withResolveParents(HasValue(IsEmpty()))))));
+}
 } // namespace
 } // namespace clangd
 } // namespace clang


        


More information about the cfe-commits mailing list