Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -44,6 +44,7 @@ index/IndexAction.cpp index/MemIndex.cpp index/Merge.cpp + index/SymbolID.cpp index/Serialization.cpp index/SymbolCollector.cpp index/YAMLSerialization.cpp Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -88,6 +88,8 @@ void onHover(const TextDocumentPositionParams &, Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); + void onSymbolInfo(const TextDocumentPositionParams &, + Callback>); std::vector getFixes(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -685,6 +685,12 @@ std::move(Reply)); } +void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params, + Callback> Reply) { + Server->symbolInfo(Params.textDocument.uri.file(), Params.position, + std::move(Reply)); +} + ClangdLSPServer::ClangdLSPServer(class Transport &Transp, const clangd::CodeCompleteOptions &CCOpts, Optional CompileCommandsDir, @@ -719,6 +725,7 @@ MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); + MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); // clang-format on } Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -201,6 +201,11 @@ /// Called when an event occurs for a watched file in the workspace. void onFileEvent(const DidChangeWatchedFilesParams &Params); + /// Get symbol info for given position. + /// Clangd extension - not part of official LSP. + void symbolInfo(PathRef File, Position Pos, + Callback> CB); + /// Returns estimated memory usage for each of the currently open files. /// The order of results is unspecified. /// Overall memory usage of clangd may be significantly more than reported Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -517,6 +517,18 @@ WorkScheduler.runWithAST("References", File, Bind(Action, std::move(CB))); } +void ClangdServer::symbolInfo(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos](Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getSymbolInfo(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("SymbolInfo", File, Bind(Action, std::move(CB))); +} + std::vector> ClangdServer::getUsedBytesPerFile() const { return WorkScheduler.getUsedBytesPerFile(); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -25,6 +25,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H #include "URI.h" +#include "index/SymbolID.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" #include @@ -673,6 +674,26 @@ llvm::json::Value toJSON(const SymbolInformation &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &); +/// Represents information about identifier. +/// This is returned from textDocument/symbolInfo, which is a clangd extension. +struct SymbolDetails { + std::string name; + + std::string containerName; + + /// Unified Symbol Resolution identifier + /// This is an opaque string uniquely identifying a symbol. + /// Unlike SymbolID, it is variable-length and somewhat human-readable. + /// It is a common representation across several clang tools. + /// (See USRGeneration.h) + std::string USR; + + llvm::Optional ID; +}; +llvm::json::Value toJSON(const SymbolDetails &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolDetails &); +bool operator==(const SymbolDetails &, const SymbolDetails &); + /// The parameters of a Workspace Symbol Request. struct WorkspaceSymbolParams { /// A non-empty query string Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -14,7 +14,9 @@ #include "Protocol.h" #include "Logger.h" #include "URI.h" +#include "index/Index.h" #include "clang/Basic/LLVM.h" +#include "llvm/ADT/Hashing.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" @@ -422,6 +424,44 @@ return O; } +bool operator==(const SymbolDetails &LHS, const SymbolDetails &RHS) { + return LHS.name == RHS.name && LHS.containerName == RHS.containerName && + LHS.USR == RHS.USR && LHS.ID == RHS.ID; +} + +llvm::json::Value toJSON(const SymbolDetails &P) { + json::Object result{{"name", llvm::json::Value(nullptr)}, + {"containerName", llvm::json::Value(nullptr)}, + {"usr", llvm::json::Value(nullptr)}, + {"id", llvm::json::Value(nullptr)}}; + + if (!P.name.empty()) + result["name"] = P.name; + + if (!P.containerName.empty()) + result["containerName"] = P.containerName; + + if (!P.USR.empty()) + result["usr"] = P.USR; + + if (P.ID.hasValue()) + result["id"] = P.ID.getValue().str(); + + return result; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const SymbolDetails &S) { + if (!S.containerName.empty()) { + O << S.containerName; + StringRef ContNameRef; + if (!ContNameRef.endswith("::")) { + O << " "; + } + } + O << S.name << " - " << toJSON(S); + return O; +} + bool fromJSON(const json::Value &Params, WorkspaceSymbolParams &R) { json::ObjectMapper O(Params); return O && O.map("query", R.query); Index: clangd/XRefs.h =================================================================== --- clangd/XRefs.h +++ clangd/XRefs.h @@ -38,6 +38,9 @@ std::vector findReferences(ParsedAST &AST, Position Pos, const SymbolIndex *Index = nullptr); +/// Get info about symbols at \p Pos. +std::vector getSymbolInfo(ParsedAST &AST, Position Pos); + } // namespace clangd } // namespace clang Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -749,5 +749,48 @@ return Results; } +std::vector getSymbolInfo(ParsedAST &AST, Position Pos) { + const SourceManager &SM = AST.getASTContext().getSourceManager(); + + auto Loc = getBeginningOfIdentifier(AST, Pos, SM.getMainFileID()); + auto Symbols = getSymbolAtPosition(AST, Loc); + + std::vector Results; + + for (const auto &Sym : Symbols.Decls) { + SymbolDetails NewSymbol; + if (const NamedDecl *ND = dyn_cast(Sym.D)) { + std::string QName = printQualifiedName(*ND); + std::tie(NewSymbol.containerName, NewSymbol.name) = + splitQualifiedName(QName); + + if (NewSymbol.containerName.empty()) { + if (const auto *ParentND = + dyn_cast_or_null(ND->getDeclContext())) + NewSymbol.containerName = printQualifiedName(*ParentND); + } + } + llvm::SmallString<32> USR; + if (!index::generateUSRForDecl(Sym.D, USR)) { + NewSymbol.USR = USR.str(); + NewSymbol.ID = SymbolID(NewSymbol.USR); + } + Results.push_back(std::move(NewSymbol)); + } + + for (const auto &Macro : Symbols.Macros) { + SymbolDetails NewMacro; + NewMacro.name = Macro.Name; + llvm::SmallString<32> USR; + if (!index::generateUSRForMacro(NewMacro.name, Loc, SM, USR)) { + NewMacro.USR = USR.str(); + NewMacro.ID = SymbolID(NewMacro.USR); + } + Results.push_back(std::move(NewMacro)); + } + + return Results; +} + } // namespace clangd } // namespace clang Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -10,11 +10,11 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H +#include "SymbolID.h" #include "clang/Index/IndexSymbol.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/Hashing.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -94,54 +94,6 @@ } llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); -// The class identifies a particular C++ symbol (class, function, method, etc). -// -// As USRs (Unified Symbol Resolution) could be large, especially for functions -// with long type arguments, SymbolID is using truncated SHA1(USR) values to -// guarantee the uniqueness of symbols while using a relatively small amount of -// memory (vs storing USRs directly). -// -// SymbolID can be used as key in the symbol indexes to lookup the symbol. -class SymbolID { -public: - SymbolID() = default; - explicit SymbolID(llvm::StringRef USR); - - bool operator==(const SymbolID &Sym) const { - return HashValue == Sym.HashValue; - } - bool operator<(const SymbolID &Sym) const { - return HashValue < Sym.HashValue; - } - - // The stored hash is truncated to RawSize bytes. - // This trades off memory against the number of symbols we can handle. - // FIXME: can we reduce this further to 8 bytes? - constexpr static size_t RawSize = 16; - llvm::StringRef raw() const { - return StringRef(reinterpret_cast(HashValue.data()), RawSize); - } - static SymbolID fromRaw(llvm::StringRef); - - // Returns a hex encoded string. - std::string str() const; - static llvm::Expected fromStr(llvm::StringRef); - -private: - std::array HashValue; -}; - -inline llvm::hash_code hash_value(const SymbolID &ID) { - // We already have a good hash, just return the first bytes. - assert(sizeof(size_t) <= SymbolID::RawSize && "size_t longer than SHA1!"); - size_t Result; - memcpy(&Result, ID.raw().data(), sizeof(size_t)); - return llvm::hash_code(Result); -} - -// Write SymbolID into the given stream. SymbolID is encoded as ID.str(). -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); - } // namespace clangd } // namespace clang namespace llvm { Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -12,7 +12,6 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" -#include "llvm/Support/SHA1.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; @@ -43,34 +42,6 @@ << "-" << L.End.line() << ":" << L.End.column() << ")"; } -SymbolID::SymbolID(StringRef USR) { - auto Hash = SHA1::hash(arrayRefFromStringRef(USR)); - static_assert(sizeof(Hash) >= RawSize, "RawSize larger than SHA1"); - memcpy(HashValue.data(), Hash.data(), RawSize); -} - -raw_ostream &operator<<(raw_ostream &OS, const SymbolID &ID) { - return OS << toHex(ID.raw()); -} - -SymbolID SymbolID::fromRaw(StringRef Raw) { - SymbolID ID; - assert(Raw.size() == RawSize); - memcpy(ID.HashValue.data(), Raw.data(), RawSize); - return ID; -} - -std::string SymbolID::str() const { return toHex(raw()); } - -Expected SymbolID::fromStr(StringRef Str) { - if (Str.size() != RawSize * 2) - return createStringError(inconvertibleErrorCode(), "Bad ID length"); - for (char C : Str) - if (!isHexDigit(C)) - return createStringError(inconvertibleErrorCode(), "Bad hex ID"); - return fromRaw(fromHex(Str)); -} - raw_ostream &operator<<(raw_ostream &OS, SymbolOrigin O) { if (O == SymbolOrigin::Unknown) return OS << "unknown"; Index: clangd/index/SymbolID.h =================================================================== --- /dev/null +++ clangd/index/SymbolID.h @@ -0,0 +1,67 @@ +//===--- SymbolID.h ----------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOLID_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOLID_H + +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +namespace clang { +namespace clangd { + +// The class identifies a particular C++ symbol (class, function, method, etc). +// +// As USRs (Unified Symbol Resolution) could be large, especially for functions +// with long type arguments, SymbolID is using truncated SHA1(USR) values to +// guarantee the uniqueness of symbols while using a relatively small amount of +// memory (vs storing USRs directly). +// +// SymbolID can be used as key in the symbol indexes to lookup the symbol. +class SymbolID { +public: + SymbolID() = default; + explicit SymbolID(llvm::StringRef USR); + + bool operator==(const SymbolID &Sym) const { + return HashValue == Sym.HashValue; + } + bool operator<(const SymbolID &Sym) const { + return HashValue < Sym.HashValue; + } + + // The stored hash is truncated to RawSize bytes. + // This trades off memory against the number of symbols we can handle. + // FIXME: can we reduce this further to 8 bytes? + constexpr static size_t RawSize = 16; + llvm::StringRef raw() const; + + static SymbolID fromRaw(llvm::StringRef); + + // Returns a hex encoded string. + std::string str() const; + static llvm::Expected fromStr(llvm::StringRef); + +private: + std::array HashValue; +}; + +llvm::hash_code hash_value(const SymbolID &ID); + +// Write SymbolID into the given stream. SymbolID is encoded as ID.str(). +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOLID_H Index: clangd/index/SymbolID.cpp =================================================================== --- /dev/null +++ clangd/index/SymbolID.cpp @@ -0,0 +1,58 @@ +//===--- SymbolID.cpp --------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SymbolID.h" +#include "llvm/Support/SHA1.h" + +using namespace llvm; +namespace clang { +namespace clangd { + +SymbolID::SymbolID(StringRef USR) { + auto Hash = llvm::SHA1::hash(arrayRefFromStringRef(USR)); + static_assert(sizeof(Hash) >= RawSize, "RawSize larger than SHA1"); + memcpy(HashValue.data(), Hash.data(), RawSize); +} + +llvm::StringRef SymbolID::raw() const { + return StringRef(reinterpret_cast(HashValue.data()), RawSize); +} + +SymbolID SymbolID::fromRaw(StringRef Raw) { + SymbolID ID; + assert(Raw.size() == RawSize); + memcpy(ID.HashValue.data(), Raw.data(), RawSize); + return ID; +} + +std::string SymbolID::str() const { return toHex(raw()); } + +Expected SymbolID::fromStr(StringRef Str) { + if (Str.size() != RawSize * 2) + return createStringError(inconvertibleErrorCode(), "Bad ID length"); + for (char C : Str) + if (!isHexDigit(C)) + return createStringError(inconvertibleErrorCode(), "Bad hex ID"); + return fromRaw(fromHex(Str)); +} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolID &ID) { + return OS << toHex(ID.raw()); +} + +llvm::hash_code hash_value(const SymbolID &ID) { + // We already have a good hash, just return the first bytes. + assert(sizeof(size_t) <= SymbolID::RawSize && "size_t longer than SHA1!"); + size_t Result; + memcpy(&Result, ID.raw().data(), sizeof(size_t)); + return llvm::hash_code(Result); +} + +} // namespace clangd +} // namespace clang Index: test/clangd/symbol-info.test =================================================================== --- /dev/null +++ test/clangd/symbol-info.test @@ -0,0 +1,14 @@ +# RUN: clangd -lit-test < %s | FileCheck %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:///simple.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///simple.cpp"},"position":{"line":0,"character":27}}} +# CHECK: "containerName": null, +# CHECK-NEXT: "id": "CA2EBE44A1D76D2A1547D47BC6D51EBF", +# CHECK-NEXT: "name": "foo", +# CHECK-NEXT: "usr": "c:@F@foo#" +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -33,6 +33,7 @@ SerializationTests.cpp SourceCodeTests.cpp SymbolCollectorTests.cpp + SymbolInfoTests.cpp SyncAPI.cpp TUSchedulerTests.cpp TestFS.cpp Index: unittests/clangd/SymbolInfoTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/SymbolInfoTests.cpp @@ -0,0 +1,357 @@ +//===-- SymbolInfoTests.cpp -----------------------*- C++ -*--------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "Annotations.h" +#include "ClangdUnit.h" +#include "Compiler.h" +#include "Matchers.h" +#include "SyncAPI.h" +#include "TestFS.h" +#include "TestTU.h" +#include "XRefs.h" +#include "index/FileIndex.h" +#include "index/SymbolCollector.h" +#include "clang/Index/IndexingAction.h" +#include "llvm/Support/Path.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace llvm; +namespace clang { +namespace clangd { +namespace { + +using testing::ElementsAreArray; + +auto CreateExpectedSymbolDetails = [](const std::string &name, + const std::string &container, + const std::string &USR) { + return SymbolDetails{name, container, USR, SymbolID(USR)}; +}; + +TEST(SymbolInfoTests, All) { + std::pair> + TestInputExpectedOutput[] = { + { + R"cpp( // Simple function reference - declaration + void foo(); + int bar() { + fo^o(); + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")} + }, + { + R"cpp( // Simple function reference - definition + void foo() {} + int bar() { + fo^o(); + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")} + }, + { + R"cpp( // Function in namespace reference + namespace bar { + void foo(); + int baz() { + fo^o(); + } + } + )cpp", + {CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")} + }, + { + R"cpp( // Function in different namespace reference + namespace bar { + void foo(); + } + namespace barbar { + int baz() { + bar::fo^o(); + } + } + )cpp", + {CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")} + }, + { + R"cpp( // Function in global namespace reference + void foo(); + namespace Nbar { + namespace Nbaz { + int baz() { + ::fo^o(); + } + } + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")} + }, + { + R"cpp( // Function in anonymous namespace reference + namespace { + void foo(); + } + namespace barbar { + int baz() { + fo^o(); + } + } + )cpp", + {CreateExpectedSymbolDetails("foo", "(anonymous)", "c:TestTU.cpp@aN@F@foo#")} + }, + { + R"cpp( // Function reference - ADL + namespace bar { + struct BarType {}; + void foo(const BarType&); + } + namespace barbar { + int baz() { + bar::BarType b; + fo^o(b); + } + } + )cpp", + {CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#&1$@N@bar@S@BarType#")} + }, + { + R"cpp( // Global value reference + int value; + void foo(int) { } + void bar() { + foo(val^ue); + } + )cpp", + {CreateExpectedSymbolDetails("value", "", "c:@value")} + }, + { + R"cpp( // Local value reference + void foo() { int aaa; int bbb = aa^a; } + )cpp", + {CreateExpectedSymbolDetails("aaa", "foo", "c:TestTU.cpp@49@F@foo#@aaa")} + }, + { + R"cpp( // Function param + void bar(int aaa) { + int bbb = a^aa; + } + )cpp", + {CreateExpectedSymbolDetails("aaa", "bar", "c:TestTU.cpp@38@F@bar#I#@aaa")} + }, + { + R"cpp( // Lambda capture + int ii; + auto lam = [ii]() { + return i^i; + }; + )cpp", + {CreateExpectedSymbolDetails("ii", "", "c:@ii")} + }, + { + R"cpp( // Macro reference + #define MACRO 5\nint i = MAC^RO; + )cpp", + {CreateExpectedSymbolDetails("MACRO", "", "c:TestTU.cpp@55@macro@MACRO")} + }, + { + R"cpp( // Multiple symbols returned - using overloaded function name + void foo() {} + void foo(bool) {} + void foo(int) {} + namespace bar { + using ::fo^o; + } + )cpp", + { + CreateExpectedSymbolDetails("foo", "", "c:@F@foo#"), + CreateExpectedSymbolDetails("foo", "", "c:@F@foo#b#"), + CreateExpectedSymbolDetails("foo", "", "c:@F@foo#I#") + } + }, + { + R"cpp( // Multiple symbols returned - implicit conversion + struct foo {}; + struct bar { + bar(const foo&) {} + }; + void func_baz1(bar) {} + void func_baz2() { + foo ff; + func_baz1(f^f); + } + )cpp", + { + CreateExpectedSymbolDetails("ff", "func_baz2", "c:TestTU.cpp@218@F@func_baz2#@ff"), + CreateExpectedSymbolDetails("bar", "bar::", "c:@S@bar@F@bar#&1$@S@foo#"), + } + }, + { + R"cpp( // Type reference - declaration + struct foo; + void bar(fo^o*); + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")} + }, + { + R"cpp( // Type reference - definition + struct foo {}; + void bar(fo^o*); + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")} + }, + { + R"cpp( // Type Reference - template argumen + struct foo {}; + template struct bar {}; + void baz() { + bar b; + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")} + }, + { + R"cpp( // Template parameter reference - type param + template struct bar { + T^T t; + }; + )cpp", + { /* not implemented */ } + }, + { + R"cpp( // Template parameter reference - type param + template struct bar { + int a = N^N; + }; + )cpp", + { /* not implemented */ } + }, + { + R"cpp( // Class member reference - objec + struct foo { + int aa; + }; + void bar() { + foo f; + f.a^a; + } + )cpp", + {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")} + }, + { + R"cpp( // Class member reference - pointer + struct foo { + int aa; + }; + void bar() { + &foo::a^a; + } + )cpp", + {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")} + }, + { + R"cpp( // Class method reference - objec + struct foo { + void aa() {} + }; + void bar() { + foo f; + f.a^a(); + } + )cpp", + {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")} + }, + { + R"cpp( // Class method reference - pointer + struct foo { + void aa() {} + }; + void bar() { + &foo::a^a; + } + )cpp", + {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")} + }, + { + R"cpp( // Typedef + typedef int foo; + void bar() { + fo^o a; + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:TestTU.cpp@T@foo")} + }, + { + R"cpp( // Type alias + using foo = int; + void bar() { + fo^o a; + } + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@foo")} + }, + { + R"cpp( // Namespace reference + namespace foo {} + using namespace fo^o; + )cpp", + {CreateExpectedSymbolDetails("foo", "", "c:@N@foo")} + }, + { + R"cpp( // Enum value reference + enum foo { bar, baz }; + void f() { + foo fff = ba^r; + } + )cpp", + {CreateExpectedSymbolDetails("bar", "foo", "c:@E@foo@bar")} + }, + { + R"cpp( // Enum class value reference + enum class foo { bar, baz }; + void f() { + foo fff = foo::ba^r; + } + )cpp", + {CreateExpectedSymbolDetails("bar", "foo::", "c:@E@foo@bar")} + }, + { + R"cpp( // Type inferrence with auto keyword + struct foo {}; + foo getfoo() { return foo{}; } + void f() { + au^to a = getfoo(); + } + )cpp", + {/* not implemented */} + }, + { + R"cpp( // decltype + struct foo {}; + void f() { + foo f; + declt^ype(f); + } + )cpp", + {/* not implemented */} + }, + }; + + for (const auto &T : TestInputExpectedOutput) { + Annotations TestInput(T.first); + auto AST = TestTU::withCode(TestInput.code()).build(); + + EXPECT_THAT(getSymbolInfo(AST, TestInput.point()), + ElementsAreArray(T.second)) + << T.first; + } +} + +} // namespace +} // namespace clangd +} // namespace clang