nridge updated this revision to Diff 186137.
nridge marked 4 inline comments as done.
nridge added a comment.
Address Kadir's review comments
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D56370/new/
https://reviews.llvm.org/D56370
Files:
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/FindSymbols.cpp
clang-tools-extra/clangd/FindSymbols.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/index/SymbolCollector.cpp
clang-tools-extra/clangd/index/SymbolCollector.h
clang-tools-extra/unittests/clangd/Matchers.h
clang-tools-extra/unittests/clangd/XRefsTests.cpp
Index: clang-tools-extra/unittests/clangd/XRefsTests.cpp
===================================================================
--- clang-tools-extra/unittests/clangd/XRefsTests.cpp
+++ clang-tools-extra/unittests/clangd/XRefsTests.cpp
@@ -25,9 +25,13 @@
namespace clangd {
namespace {
+using testing::AllOf;
using testing::ElementsAre;
+using testing::Eq;
+using testing::Field;
using testing::IsEmpty;
using testing::Matcher;
+using testing::Pointee;
using testing::UnorderedElementsAreArray;
class IgnoreDiagnostics : public DiagnosticsConsumer {
@@ -39,6 +43,15 @@
return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg;
}
+// GMock helpers for matching TypeHierarchyItem.
+MATCHER_P(WithName, N, "") { return arg.name == N; }
+MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
+MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; }
+template <class... ParentMatchers>
+testing::Matcher<TypeHierarchyItem> Parents(ParentMatchers... ParentsM) {
+ return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...)));
+}
+
// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
Matcher<const std::vector<DocumentHighlight> &>
@@ -1365,6 +1378,277 @@
}
}
+TEST(SuperTypes, SimpleInheritanceOnTypeOrVariable) {
+ Annotations Source(R"cpp(
+struct $ParentDef[[Parent]] {
+ int a;
+};
+
+struct $Child1Def[[Child1]] : Parent {
+ int b;
+};
+
+struct Ch^ild2 : Child1 {
+ int c;
+};
+
+struct Child3 : Child2 {
+ int d;
+};
+
+int main() {
+ Ch^ild2 ch^ild2;
+
+ parent.a = 1;
+ ch^ild2.c = 1;
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ for (Position Pt : Source.points()) {
+ llvm::Optional<TypeHierarchyItem> Result =
+ getTypeHierarchy(AST, Pt, 10, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(
+ WithName("Child2"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(
+ WithName("Child1"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Child1Def")),
+ Parents(AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("ParentDef")),
+ Parents()))))));
+ }
+}
+
+TEST(SuperTypes, MultipleInheritanceOnTypeOrVariable) {
+ Annotations Source(R"cpp(
+struct $Parent1Def[[Parent1]] {
+ int a;
+};
+
+struct $Parent2Def[[Parent2]] {
+ int b;
+};
+
+struct $Parent3Def[[Parent3]] : Parent2 {
+ int c;
+};
+
+struct Ch^ild : Parent1, Parent3 {
+ int d;
+};
+
+int main() {
+ Ch^ild ch$c3^ild;
+
+ ch^ild.a = 1;
+}
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ for (Position Pt : Source.points()) {
+ llvm::Optional<TypeHierarchyItem> Result =
+ getTypeHierarchy(AST, Pt, 10, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(
+ WithName("Child"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(WithName("Parent1"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent1Def")),
+ Parents()),
+ AllOf(WithName("Parent3"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent3Def")),
+ Parents(AllOf(
+ WithName("Parent2"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Parent2Def")),
+ Parents()))))));
+ }
+}
+
+TEST(SuperTypes, OnMethod) {
+ Annotations Source(R"cpp(
+struct $ParentDef[[Parent]] {
+ void method ();
+ void method () const;
+ void method (int x);
+ void method (char x);
+};
+
+struct $Child1Def[[Child1]] : Parent {
+ void method ();
+ void method (char x);
+};
+
+struct Child2 : Child1 {
+ void met^hod ();
+ void met^hod (int x);
+};
+
+struct Child3 : Child2 {
+ void method (int x);
+};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ for (Position Pt : Source.points()) {
+ llvm::Optional<TypeHierarchyItem> Result =
+ getTypeHierarchy(AST, Pt, 10, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(
+ WithName("Child2"), WithKind(SymbolKind::Struct),
+ Parents(AllOf(
+ WithName("Child1"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("Child1Def")),
+ Parents(AllOf(WithName("Parent"), WithKind(SymbolKind::Struct),
+ SelectionRangeIs(Source.range("ParentDef")),
+ Parents()))))));
+ }
+}
+
+TEST(SuperTypes, ChildTemplateSpec) {
+ Annotations Source(R"cpp(
+template <typename T>
+struct $ParentDef[[Parent]] {};
+
+template <>
+struct $ParentSpecDef[[Parent]]<int> {};
+
+struct Chi$p1^ld1 : Parent<float> {};
+
+struct Chi$p2^ld2 : Parent<int> {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.point("p1"), 1, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("Child1"),
+ Parents(AllOf(WithName("Parent<float>"),
+ SelectionRangeIs(Source.range("ParentDef"))))));
+
+ Result = getTypeHierarchy(AST, Source.point("p2"), 1,
+ TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("Child2"),
+ Parents(AllOf(WithName("Parent<int>"),
+ SelectionRangeIs(Source.range("ParentSpecDef"))))));
+}
+
+TEST(SuperTypes, ParentTemplateSpec) {
+ Annotations Source(R"cpp(
+struct $ParentDef[[Parent]] {};
+
+template <typename T>
+struct Child {};
+
+template <>
+struct Chi$p^ld<int> : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.point("p"), 1, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ // This doesn't work yet, because there's no way to select "Child<int>"
+ // rather than "Child" as the entity we're querying.
+ // EXPECT_THAT(
+ // *Result,
+ // AllOf(WithName("Child<int>"),
+ // Parents(AllOf(WithName("Parent"),
+ // SelectionRangeIs(Source.range("ParentDef"))))));
+}
+
+TEST(SuperTypes, ClassTemplate) {
+ Annotations Source(R"cpp(
+struct $ParentDef[[Parent]] {};
+
+template <typename T>
+struct Chi$p^ld : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.point("p"), 1, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("Child"),
+ Parents(AllOf(WithName("Parent"),
+ SelectionRangeIs(Source.range("ParentDef"))))));
+}
+
+TEST(SuperTypes, DependentBase) {
+ Annotations Source(R"cpp(
+template <typename T>
+struct $ParentDef[[Parent]] {};
+
+template <typename T>
+struct Chi$p1^ld1 : Parent<T> {};
+
+template <typename T>
+struct Chi$p2^ld2 : Parent<T>::Type {};
+
+template <typename T>
+struct Chi$p3^ld3 : T {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ // For "Parent<T>", use the primary template as a best-effort guess.
+ llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy(
+ AST, Source.point("p1"), 1, TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(
+ *Result,
+ AllOf(WithName("Child1"),
+ Parents(AllOf(WithName("Parent"),
+ SelectionRangeIs(Source.range("ParentDef"))))));
+
+ // For "Parent<T>::Type", there is nothing we can do.
+ Result = getTypeHierarchy(AST, Source.point("p2"), 1,
+ TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(*Result, AllOf(WithName("Child2"), Parents()));
+
+ // Likewise for "T".
+ Result = getTypeHierarchy(AST, Source.point("p3"), 1,
+ TypeHierarchyDirection::Parents);
+ ASSERT_TRUE(bool(Result));
+ EXPECT_THAT(*Result, AllOf(WithName("Child3"), Parents()));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/unittests/clangd/Matchers.h
===================================================================
--- clang-tools-extra/unittests/clangd/Matchers.h
+++ clang-tools-extra/unittests/clangd/Matchers.h
@@ -127,6 +127,73 @@
llvm::consumeError(ComputedValue.takeError()); \
} while (false)
+// Implements the HasValue(m) matcher for matching an Optional whose
+// value matches matcher m.
+template <typename InnerMatcher> class OptionalMatcher {
+public:
+ explicit OptionalMatcher(const InnerMatcher &matcher) : matcher_(matcher) {}
+
+ // This type conversion operator template allows Optional(m) to be
+ // used as a matcher for any Optional type whose value type is
+ // compatible with the inner matcher.
+ //
+ // The reason we do this instead of relying on
+ // MakePolymorphicMatcher() is that the latter is not flexible
+ // enough for implementing the DescribeTo() method of Optional().
+ template <typename Optional> operator Matcher<Optional>() const {
+ return MakeMatcher(new Impl<Optional>(matcher_));
+ }
+
+private:
+ // The monomorphic implementation that works for a particular optional type.
+ template <typename Optional>
+ class Impl : public ::testing::MatcherInterface<Optional> {
+ public:
+ using Value = typename std::remove_const<
+ typename std::remove_reference<Optional>::type>::type::value_type;
+
+ explicit Impl(const InnerMatcher &matcher)
+ : matcher_(::testing::MatcherCast<const Value &>(matcher)) {}
+
+ virtual void DescribeTo(::std::ostream *os) const {
+ *os << "has a value that ";
+ matcher_.DescribeTo(os);
+ }
+
+ virtual void DescribeNegationTo(::std::ostream *os) const {
+ *os << "does not have a value that ";
+ matcher_.DescribeTo(os);
+ }
+
+ virtual bool
+ MatchAndExplain(Optional optional,
+ ::testing::MatchResultListener *listener) const {
+ if (!optional.hasValue())
+ return false;
+
+ *listener << "which has a value ";
+ return MatchPrintAndExplain(*optional, matcher_, listener);
+ }
+
+ private:
+ const Matcher<const Value &> matcher_;
+
+ GTEST_DISALLOW_ASSIGN_(Impl);
+ };
+
+ const InnerMatcher matcher_;
+
+ GTEST_DISALLOW_ASSIGN_(OptionalMatcher);
+};
+
+// Creates a matcher that matches an Optional that has a value
+// that matches inner_matcher.
+template <typename InnerMatcher>
+inline OptionalMatcher<InnerMatcher>
+HasValue(const InnerMatcher &inner_matcher) {
+ return OptionalMatcher<InnerMatcher>(inner_matcher);
+}
+
} // namespace clangd
} // namespace clang
#endif
Index: clang-tools-extra/clangd/index/SymbolCollector.h
===================================================================
--- clang-tools-extra/clangd/index/SymbolCollector.h
+++ clang-tools-extra/clangd/index/SymbolCollector.h
@@ -137,6 +137,18 @@
llvm::DenseMap<FileID, bool> FilesToIndexCache;
};
+// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the
+// current working directory of the given SourceManager if the Path is not an
+// absolute path. If failed, this resolves relative paths against \p FallbackDir
+// to get an absolute path. Then, this tries creating an URI for the absolute
+// path with schemes specified in \p Opts. This returns an URI with the first
+// working scheme, if there is any; otherwise, this returns None.
+//
+// The Path can be a path relative to the build directory, or retrieved from
+// the SourceManager.
+std::string toURI(const SourceManager &SM, llvm::StringRef Path,
+ llvm::StringRef FallbackDir);
+
} // namespace clangd
} // namespace clang
#endif
Index: clang-tools-extra/clangd/index/SymbolCollector.cpp
===================================================================
--- clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -30,27 +30,9 @@
namespace clang {
namespace clangd {
-namespace {
-
-/// If \p ND is a template specialization, returns the described template.
-/// Otherwise, returns \p ND.
-const NamedDecl &getTemplateOrThis(const NamedDecl &ND) {
- if (auto T = ND.getDescribedTemplate())
- return *T;
- return ND;
-}
-// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the
-// current working directory of the given SourceManager if the Path is not an
-// absolute path. If failed, this resolves relative paths against \p FallbackDir
-// to get an absolute path. Then, this tries creating an URI for the absolute
-// path with schemes specified in \p Opts. This returns an URI with the first
-// working scheme, if there is any; otherwise, this returns None.
-//
-// The Path can be a path relative to the build directory, or retrieved from
-// the SourceManager.
std::string toURI(const SourceManager &SM, llvm::StringRef Path,
- const SymbolCollector::Options &Opts) {
+ llvm::StringRef FallbackDir) {
llvm::SmallString<128> AbsolutePath(Path);
if (auto CanonPath =
getCanonicalPath(SM.getFileManager().getFile(Path), SM)) {
@@ -58,12 +40,22 @@
}
// We don't perform is_absolute check in an else branch because makeAbsolute
// might return a relative path on some InMemoryFileSystems.
- if (!llvm::sys::path::is_absolute(AbsolutePath) && !Opts.FallbackDir.empty())
- llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath);
+ if (!llvm::sys::path::is_absolute(AbsolutePath) && !FallbackDir.empty())
+ llvm::sys::fs::make_absolute(FallbackDir, AbsolutePath);
llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
return URI::create(AbsolutePath).toString();
}
+namespace {
+
+/// If \p ND is a template specialization, returns the described template.
+/// Otherwise, returns \p ND.
+const NamedDecl &getTemplateOrThis(const NamedDecl &ND) {
+ if (auto T = ND.getDescribedTemplate())
+ return *T;
+ return ND;
+}
+
// All proto generated headers should start with this line.
static const char *PROTO_HEADER_COMMENT =
"// Generated by the protocol buffer compiler. DO NOT EDIT!";
@@ -152,7 +144,7 @@
if (Header.startswith("<") || Header.startswith("\""))
return Header.str();
}
- return toURI(SM, Header, Opts);
+ return toURI(SM, Header, Opts.FallbackDir);
}
// Return the symbol range of the token at \p TokLoc.
@@ -192,7 +184,7 @@
auto Path = SM.getFilename(TokLoc);
if (Path.empty())
return None;
- FileURIStorage = toURI(SM, Path, Opts);
+ FileURIStorage = toURI(SM, Path, Opts.FallbackDir);
SymbolLocation Result;
Result.FileURI = FileURIStorage.c_str();
auto Range = getTokenRange(TokLoc, SM, LangOpts);
@@ -481,7 +473,7 @@
auto Found = URICache.find(FID);
if (Found == URICache.end()) {
if (auto *FileEntry = SM.getFileEntryForID(FID)) {
- auto FileURI = toURI(SM, FileEntry->getName(), Opts);
+ auto FileURI = toURI(SM, FileEntry->getName(), Opts.FallbackDir);
Found = URICache.insert({FID, FileURI}).first;
} else {
// Ignore cases where we can not find a corresponding file entry
Index: clang-tools-extra/clangd/XRefs.h
===================================================================
--- clang-tools-extra/clangd/XRefs.h
+++ clang-tools-extra/clangd/XRefs.h
@@ -58,6 +58,16 @@
/// Get info about symbols at \p Pos.
std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos);
+/// Get type hierarchy information at \p Pos.
+llvm::Optional<TypeHierarchyItem>
+getTypeHierarchy(ParsedAST &AST, Position Pos, int Resolve,
+ llvm::Optional<TypeHierarchyDirection> Direction);
+
+/// Convert a a URI (such as that returned by toURI()) into a form suitable
+/// for use in protocol replies (e.g. Location.uri, DocumentSymbol.uri).
+/// TUPath is used to resolve the path of URI.
+Optional<URIForFile> getURIForFile(StringRef FileURI, StringRef TUPath);
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/XRefs.cpp
===================================================================
--- clang-tools-extra/clangd/XRefs.cpp
+++ clang-tools-extra/clangd/XRefs.cpp
@@ -7,11 +7,14 @@
//===----------------------------------------------------------------------===//
#include "XRefs.h"
#include "AST.h"
+#include "FindSymbols.h"
#include "Logger.h"
#include "SourceCode.h"
#include "URI.h"
+#include "index/SymbolCollector.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Type.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Index/USRGeneration.h"
@@ -19,6 +22,22 @@
namespace clang {
namespace clangd {
+
+Optional<URIForFile> getURIForFile(StringRef FileURI, StringRef TUPath) {
+ auto Uri = URI::parse(FileURI);
+ if (!Uri) {
+ elog("Could not parse URI {0}: {1}", FileURI, Uri.takeError());
+ return None;
+ }
+ auto U = URIForFile::fromURI(*Uri, TUPath);
+ if (!U) {
+ elog("Could not resolve URI {0}: {1}", FileURI, U.takeError());
+ return None;
+ }
+
+ return *U;
+}
+
namespace {
// Returns the single definition of the entity declared by D, if visible.
@@ -56,16 +75,10 @@
llvm::StringRef TUPath) {
if (!Loc)
return None;
- auto Uri = URI::parse(Loc.FileURI);
- if (!Uri) {
- elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
- return None;
- }
- auto U = URIForFile::fromURI(*Uri, TUPath);
- if (!U) {
- elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
+
+ auto U = getURIForFile(Loc.FileURI, TUPath);
+ if (!U)
return None;
- }
Location LSPLoc;
LSPLoc.uri = std::move(*U);
@@ -789,5 +802,140 @@
return OS;
}
+// TODO: Reduce duplication between this function and declToSym().
+static llvm::Optional<TypeHierarchyItem>
+declToTypeHierarchyItem(ASTContext &Ctx, const NamedDecl &ND) {
+ auto &SM = Ctx.getSourceManager();
+
+ SourceLocation NameLoc = findNameLoc(&ND);
+ // getFileLoc is a good choice for us, but we also need to make sure
+ // sourceLocToPosition won't switch files, so we call getSpellingLoc on top of
+ // that to make sure it does not switch files.
+ // FIXME: sourceLocToPosition should not switch files!
+ SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc()));
+ SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc()));
+ if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid())
+ return llvm::None;
+
+ Position NameBegin = sourceLocToPosition(SM, NameLoc);
+ Position NameEnd = sourceLocToPosition(
+ SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts()));
+
+ index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
+ // FIXME: this is not classifying constructors, destructors and operators
+ // correctly (they're all "methods").
+ SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
+
+ TypeHierarchyItem THI;
+ THI.name = printName(Ctx, ND);
+ THI.kind = SK;
+ THI.deprecated = ND.isDeprecated();
+ THI.range =
+ Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)};
+ THI.selectionRange = Range{NameBegin, NameEnd};
+ if (!THI.range.contains(THI.selectionRange)) {
+ // 'selectionRange' must be contained in 'range', so in cases where clang
+ // reports unrelated ranges we need to reconcile somehow.
+ THI.range = THI.selectionRange;
+ }
+
+ StringRef Filename = SM.getFilename(BeginLoc);
+ std::string FileURI = toURI(SM, Filename, {});
+ std::string TUPath;
+ const FileEntry *FE = SM.getFileEntryForID(SM.getMainFileID());
+ if (auto Path = getCanonicalPath(FE, SM))
+ TUPath = *Path;
+ auto U = getURIForFile(FileURI, TUPath);
+ if (!U)
+ return llvm::None; // Not useful without a uri.
+ THI.uri = std::move(*U);
+
+ return THI;
+}
+
+static Optional<TypeHierarchyItem>
+getTypeHierarchy(const CXXRecordDecl &CXXRD, ASTContext &ASTCtx, int Levels,
+ TypeHierarchyDirection Direction) {
+ Optional<TypeHierarchyItem> Result = declToTypeHierarchyItem(ASTCtx, CXXRD);
+ if (!Result || Levels <= 0)
+ return Result;
+
+ if (Direction == TypeHierarchyDirection::Parents ||
+ Direction == TypeHierarchyDirection::Both) {
+ Result->parents.emplace();
+ for (auto It = CXXRD.bases_begin(); It != CXXRD.bases_end(); It++) {
+ const CXXRecordDecl *ParentDecl = nullptr;
+
+ const Type *Type = It->getType().getTypePtr();
+ if (const RecordType *RT = Type->getAs<RecordType>()) {
+ ParentDecl = RT->getAsCXXRecordDecl();
+ }
+
+ if (!ParentDecl) {
+ // Handle a dependent base such as "Base<T>".
+ // The type hierarchy will use the primary template.
+ if (const TemplateSpecializationType *TS =
+ Type->getAs<TemplateSpecializationType>()) {
+ TemplateName TN = TS->getTemplateName();
+ if (TemplateDecl *TD = TN.getAsTemplateDecl()) {
+ ParentDecl = dyn_cast<CXXRecordDecl>(TD->getTemplatedDecl());
+ }
+ }
+ }
+
+ if (!ParentDecl)
+ continue;
+
+ if (Optional<TypeHierarchyItem> ParentSym =
+ getTypeHierarchy(*ParentDecl, ASTCtx, Levels - 1,
+ TypeHierarchyDirection::Parents)) {
+ Result->parents->emplace_back(std::move(*ParentSym));
+ }
+ }
+ }
+
+ if (Direction == TypeHierarchyDirection::Children ||
+ Direction == TypeHierarchyDirection::Both) {
+ Result->children.emplace();
+
+ // TODO: Populate subtypes.
+ }
+
+ return Result;
+}
+
+llvm::Optional<TypeHierarchyItem>
+getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
+ llvm::Optional<TypeHierarchyDirection> Direction) {
+ ASTContext &ASTCtx = AST.getASTContext();
+ const SourceManager &SourceMgr = ASTCtx.getSourceManager();
+ SourceLocation SourceLocationBeg =
+ getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
+ IdentifiedSymbol Symbols = getSymbolAtPosition(AST, SourceLocationBeg);
+
+ if (Symbols.Decls.empty())
+ return llvm::None;
+
+ const Decl *D = Symbols.Decls[0].D;
+ const CXXRecordDecl *CXXRD;
+
+ if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
+ // If this is a variable, use the type of the variable.
+ CXXRD = VD->getType().getTypePtr()->getAsCXXRecordDecl();
+ } else if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) {
+ // If this is a method, use the type of the class.
+ CXXRD = Method->getParent();
+ } else {
+ CXXRD = dyn_cast<CXXRecordDecl>(D);
+ }
+
+ if (!CXXRD)
+ return llvm::None;
+
+ TypeHierarchyDirection ResolveDirection =
+ Direction.getValueOr(TypeHierarchyDirection::Parents);
+ return getTypeHierarchy(*CXXRD, ASTCtx, ResolveLevels, ResolveDirection);
+}
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ clang-tools-extra/clangd/Protocol.h
@@ -1014,6 +1014,80 @@
llvm::json::Value toJSON(const DocumentHighlight &DH);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &);
+enum class TypeHierarchyDirection { Children = 0, Parents = 1, Both = 2 };
+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 {
+ /// The hierarchy levels to resolve. `0` indicates no level.
+ int resolve;
+
+ /// The direction of the hierarchy levels to resolve.
+ llvm::Optional<TypeHierarchyDirection> direction;
+};
+bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &);
+
+struct TypeHierarchyItem {
+ /// The human readable name of the hierarchy 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.
+ SymbolKind kind;
+
+ /// `true` if the hierarchy item is deprecated. Otherwise, `false`.
+ bool deprecated;
+
+ /// The URI of the text document where this type hierarchy item belongs to.
+ 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.
+ 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`.
+ 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.
+ 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.
+ llvm::Optional<std::vector<TypeHierarchyItem>> 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.
+};
+llvm::json::Value toJSON(const TypeHierarchyItem &);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &, const 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/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ clang-tools-extra/clangd/Protocol.cpp
@@ -813,6 +813,83 @@
return true;
}
+bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) {
+ auto T = E.getAsInteger();
+ if (!T)
+ return false;
+ if (*T < static_cast<int>(TypeHierarchyDirection::Children) ||
+ *T > static_cast<int>(TypeHierarchyDirection::Both))
+ return false;
+ Out = static_cast<TypeHierarchyDirection>(*T);
+ return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R) {
+ if (!fromJSON(Params, static_cast<TextDocumentPositionParams &>(R)))
+ return false;
+ if (auto *Resolve = Params.getAsObject()->get("resolve")) {
+ if (!fromJSON(*Resolve, R.resolve)) {
+ R.resolve = 0; // default value if not specified
+ }
+ }
+ if (auto *Direction = Params.getAsObject()->get("direction"))
+ return fromJSON(*Direction, R.direction);
+ return true;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
+ const TypeHierarchyItem &I) {
+ return O << I.name << " - " << toJSON(I);
+}
+
+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}};
+
+ 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;
+ // Older gcc cannot compile 'return Result', even though it is legal.
+ return llvm::json::Value(std::move(Result));
+}
+
+bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I) {
+ llvm::json::ObjectMapper O(Params);
+ if (!O)
+ return true; // 'any' type in LSP.
+
+ O.map("name", I.name);
+ O.map("detail", I.detail);
+ O.map("kind", I.kind);
+ O.map("deprecated", I.deprecated);
+ O.map("range", I.range);
+ O.map("selectionRange", I.selectionRange);
+ O.map("uri", I.uri);
+ O.map("parents", I.parents);
+ O.map("children", I.children);
+ return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params,
+ ResolveTypeHierarchyItemParams &R) {
+ llvm::json::ObjectMapper O(Params);
+ if (!O)
+ return true; // 'any' type in LSP.
+
+ O.map("item", R.item);
+ O.map("resolve", R.resolve);
+ O.map("direction", R.direction);
+ return true;
+}
+
bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) {
TextDocumentPositionParams &Base = R;
return fromJSON(Params, Base);
Index: clang-tools-extra/clangd/FindSymbols.h
===================================================================
--- clang-tools-extra/clangd/FindSymbols.h
+++ clang-tools-extra/clangd/FindSymbols.h
@@ -13,17 +13,28 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H
#include "Protocol.h"
+#include "index/Index.h"
#include "llvm/ADT/StringRef.h"
namespace clang {
+class ASTContext;
+class NamedDecl;
+
namespace clangd {
class ParsedAST;
class SymbolIndex;
+// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
+// Note, some are not perfect matches and should be improved when this LSP
+// issue is addressed:
+// https://github.com/Microsoft/language-server-protocol/issues/344
+SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind);
+
/// 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 match it.
-/// Direct children of scopes (namepaces, etc) can be listed with a trailing
+/// the non-qualified name or fully qualified of a symbol. For example,
+/// "vector" will match the symbol std::vector and "std::vector" would also
+/// match it. Direct children of scopes (namepaces, etc) can be listed with a
+/// trailing
/// "::". For example, "std::" will list all children of the std namespace and
/// "::" alone will list all children of the global namespace.
/// \p Limit limits the number of results returned (0 means no limit).
Index: clang-tools-extra/clangd/FindSymbols.cpp
===================================================================
--- clang-tools-extra/clangd/FindSymbols.cpp
+++ clang-tools-extra/clangd/FindSymbols.cpp
@@ -26,12 +26,7 @@
namespace clang {
namespace clangd {
-namespace {
-// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
-// Note, some are not perfect matches and should be improved when this LSP
-// issue is addressed:
-// https://github.com/Microsoft/language-server-protocol/issues/344
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
switch (Kind) {
case index::SymbolKind::Unknown:
@@ -87,6 +82,7 @@
llvm_unreachable("invalid symbol kind");
}
+namespace {
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
struct ScoredSymbolGreater {
bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
Index: clang-tools-extra/clangd/ClangdServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdServer.h
+++ clang-tools-extra/clangd/ClangdServer.h
@@ -184,6 +184,16 @@
void findHover(PathRef File, Position Pos,
Callback<llvm::Optional<Hover>> CB);
+ /// Get information about type hierarchy for a given position.
+ void findTypeHierarchy(PathRef File, Position Pos, int Resolve,
+ llvm::Optional<TypeHierarchyDirection> Direction,
+ Callback<llvm::Optional<TypeHierarchyItem>> CB);
+
+ /// Resolve the given type hierarchy item in the given direction.
+ void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,
+ TypeHierarchyDirection Direction,
+ Callback<llvm::Optional<TypeHierarchyItem>> CB);
+
/// Retrieve the top symbols from the workspace matching a query.
void workspaceSymbols(StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB);
Index: clang-tools-extra/clangd/ClangdServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdServer.cpp
+++ clang-tools-extra/clangd/ClangdServer.cpp
@@ -517,6 +517,37 @@
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
}
+void ClangdServer::findTypeHierarchy(
+ PathRef File, Position Pos, int Resolve,
+ llvm::Optional<TypeHierarchyDirection> Direction,
+ Callback<Optional<TypeHierarchyItem>> CB) {
+ auto Action = [Pos, Resolve,
+ Direction](Callback<Optional<TypeHierarchyItem>> CB,
+ Expected<InputsAndAST> InpAST) {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction));
+ };
+
+ WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB)));
+}
+
+void ClangdServer::resolveTypeHierarchy(
+ TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
+ Callback<llvm::Optional<TypeHierarchyItem>> CB) {
+ auto Action = [Item, Resolve,
+ Direction](Callback<Optional<TypeHierarchyItem>> CB,
+ Expected<InputsAndAST> InpAST) {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::getTypeHierarchy(InpAST->AST, Item.range.start, Resolve,
+ Direction));
+ };
+
+ WorkScheduler.runWithAST("Type Hierarchy Resolve", Item.uri.file(),
+ Bind(Action, std::move(CB)));
+}
+
tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) {
trace::Span Span("GetCompileCommand");
llvm::Optional<tooling::CompileCommand> C = CDB.getCompileCommand(File);
Index: clang-tools-extra/clangd/ClangdLSPServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.h
+++ clang-tools-extra/clangd/ClangdLSPServer.h
@@ -93,6 +93,10 @@
void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &,
Callback<llvm::Optional<Hover>>);
+ void onTypeHierarchy(const TypeHierarchyParams &,
+ Callback<llvm::Optional<TypeHierarchyItem>>);
+ void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &,
+ Callback<llvm::Optional<TypeHierarchyItem>>);
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -368,6 +368,7 @@
{ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
ExecuteCommandParams::CLANGD_APPLY_TWEAK}},
}},
+ {"typeHierarchyProvider", true},
}}}});
}
@@ -806,6 +807,20 @@
std::move(Reply));
}
+void ClangdLSPServer::onTypeHierarchy(
+ const TypeHierarchyParams &Params,
+ Callback<Optional<TypeHierarchyItem>> Reply) {
+ Server->findTypeHierarchy(Params.textDocument.uri.file(), Params.position,
+ Params.resolve, Params.direction, std::move(Reply));
+}
+
+void ClangdLSPServer::onResolveTypeHierarchy(
+ const ResolveTypeHierarchyItemParams &Params,
+ Callback<llvm::Optional<TypeHierarchyItem>> 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.
@@ -885,6 +900,8 @@
MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
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
}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits