Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -17,6 +17,7 @@ Logger.cpp Protocol.cpp ProtocolHandlers.cpp + Symbol.cpp Trace.cpp LINK_LIBS Index: clangd/Symbol.h =================================================================== --- /dev/null +++ clangd/Symbol.h @@ -0,0 +1,133 @@ +//===--- Symbol.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_SYMBOL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H + +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexSymbol.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringExtras.h" + +#include + +namespace clang Identifier{ +namespace clangd { + +struct SymbolLocation { + // The path of the source file where a symbol occurs. + // + // The path could be absolute or relative to the build working directory, + // depends on the build system. It is usually relative for project-related + // header files, and absolute for ".cc" files. + std::string FilePath; + // The 0-based offset to the first character of the symbol from the beginning + // of the source file. + unsigned StartOffset; + // The 0-based offset to the last character of the symbol from the beginning + // of the source file. + unsigned EndOffset; +}; + +// The class presents a C++ symbol, e.g. class, function. +// +// FIXME: instead of having own copy fields for each symbol, we can share +// storage from SymbolSlab. +struct Symbol { + // The symbol identifier, using USR. + std::string Identifier; + // The qualified name of the symbol, e.g. Foo::bar. + std::string QualifiedName; + // The symbol information, like symbol kind. + index::SymbolInfo SymInfo; + // The canonical location of the symbol. + // + // A C++ symbol might have multiple locations (e.g. a function is declared in + // ".h" file, and is defined in ".cc" file). + // * For non-inline functions and class methods, the canonical location is + // where they are declared. + // * For classes, the canonical location is where they are defined. + SymbolLocation CanonicalLocation; + + // FIXME: add all occurrences support. + // FIXME: add extra fields for index scoring signals. + // FIXME: add code completion information. +}; + +// A symbol container that stores a set of symbols. The container will maintain +// the lifetime of the symbols. +// +// FIXME: Use a space-efficient implementation, a lot of Symbol fields could +// share the same storage. +class SymbolSlab { + public: + using Iterator = llvm::DenseSet::const_iterator; + + void addSymbol(Symbol S); + bool hasSymbol(StringRef Identifier) const; + + Iterator begin() const; + Iterator end() const; + + private: + llvm::DenseSet Symbols; +}; + +// Collect all symbols from an AST. +// +// Clients (e.g. clangd) can use SymbolCollector together with +// index::indexTopLevelDecls to retrieve all symbols when the source file is +// changed. +class SymbolCollector : public index::IndexDataConsumer { +public: + SymbolCollector() = default; + + void initialize(ASTContext &Ctx) override; + + // Always return true to continue indexing. + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, + unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override; + + StringRef getFilename() const { + return Filename; + } + + SymbolSlab takeSymbols() const { return std::move(Symbols); } + +private: + // The file path where the AST comes from. + std::string Filename; + + // All Symbols collected from the AST. + SymbolSlab Symbols; +}; + +} // namespace clangd +} // namespace clang + +namespace llvm { +template <> struct DenseMapInfo { + static inline clang::clangd::Symbol getEmptyKey() { return {"EMPTYKEY"}; } + static inline clang::clangd::Symbol getTombstoneKey() { + return {"TOMBSTONEKEY"}; + } + static unsigned getHashValue(const clang::clangd::Symbol &Sym) { + return llvm::HashString(Sym.Identifier); + } + static bool isEqual(const clang::clangd::Symbol &LHS, + const clang::clangd::Symbol &RHS) { + return LHS.Identifier == RHS.Identifier; + } +}; +} // namespace llvm + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H Index: clangd/Symbol.cpp =================================================================== --- /dev/null +++ clangd/Symbol.cpp @@ -0,0 +1,80 @@ +//===--- Symbol.cpp ---------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "Symbol.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace clang { +namespace clangd { + +SymbolSlab::Iterator SymbolSlab::begin() const { + return Symbols.begin(); +} + +SymbolSlab::Iterator SymbolSlab::end() const { + return Symbols.end(); +} + +void SymbolSlab::addSymbol(Symbol S) { + Symbols.insert(std::move(S)); +} + +bool SymbolSlab::hasSymbol(StringRef Identifier) const { + auto It = Symbols.find({Identifier}); + return It != Symbols.end(); +} + +void SymbolCollector::initialize(ASTContext &Ctx) { + auto FID = Ctx.getSourceManager().getMainFileID(); + const auto *Entry = Ctx.getSourceManager().getFileEntryForID(FID); + Filename = Entry->tryGetRealPathName(); +} + +bool SymbolCollector::handleDeclOccurence( + const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) { + // FIXME: collect all symbol references. + if (!(Roles & static_cast(index::SymbolRole::Declaration) || + Roles & static_cast(index::SymbolRole::Definition))) + return true; + + if (const NamedDecl *ND = llvm::dyn_cast(D)) { + // FIXME: Should we include the internal linkage symbols? + if (!ND->hasExternalFormalLinkage() || ND->isInAnonymousNamespace()) + return true; + + llvm::SmallVector Buff; + if (index::generateUSRForDecl(ND, Buff)) + return true; + + std::string USR(Buff.data(), Buff.size()); + if (Symbols.hasSymbol(USR)) + return true; + + auto &SM = ND->getASTContext().getSourceManager(); + SymbolLocation Location = {SM.getFilename(D->getLocation()), + SM.getFileOffset(D->getLocStart()), + SM.getFileOffset(D->getLocEnd())}; + Symbols.addSymbol({std::move(USR), ND->getQualifiedNameAsString(), + index::getSymbolInfo(D), std::move(Location)}); + } + + return true; +} + +} // clangd +} // clang Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -15,6 +15,7 @@ JSONExprTests.cpp TestFS.cpp TraceTests.cpp + SymbolCollectorTests.cpp ) target_link_libraries(ClangdTests Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/SymbolCollectorTests.cpp @@ -0,0 +1,112 @@ +//===-- SymbolCollectorTests.cpp -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Symbol.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/FileSystemOptions.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include +#include + +using testing::ElementsAre; +using testing::Eq; +using testing::Field; + +namespace clang { +namespace clangd { + +namespace { +class SymbolIndexActionFactory : public tooling::FrontendActionFactory { + public: + SymbolIndexActionFactory() = default; + + clang::FrontendAction *create() override { + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = false; + Collector = std::make_shared(); + FrontendAction *Action = + index::createIndexingAction(Collector, IndexOpts, nullptr).release(); + return Action; + } + + std::shared_ptr Collector; +}; + +class SymbolCollectorTest : public ::testing::Test { +public: + bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode) { + llvm::IntrusiveRefCntPtr InMemoryFileSystem( + new vfs::InMemoryFileSystem); + llvm::IntrusiveRefCntPtr Files( + new FileManager(FileSystemOptions(), InMemoryFileSystem)); + + const std::string FileName = "symbol.cc"; + const std::string HeaderName = "symbols.h"; + auto Factory = llvm::make_unique(); + + tooling::ToolInvocation Invocation( + {"symbol_collector", "-fsyntax-only", "-std=c++11", FileName}, + Factory->create(), Files.get(), + std::make_shared()); + + InMemoryFileSystem->addFile(HeaderName, 0, + llvm::MemoryBuffer::getMemBuffer(HeaderCode)); + + std::string Content = "#include\"" + std::string(HeaderName) + "\""; + Content += "\n" + MainCode.str(); + InMemoryFileSystem->addFile(FileName, 0, + llvm::MemoryBuffer::getMemBuffer(Content)); + Invocation.run(); + Symbols = Factory->Collector->takeSymbols(); + + EXPECT_EQ(FileName, Factory->Collector->getFilename()); + return true; + } + +protected: + SymbolSlab Symbols; +}; + +TEST_F(SymbolCollectorTest, CollectSymbol) { + const std::string Header = R"( + class Foo { + void f(); + }; + void f1(); + inline void f2() {} + )"; + const std::string Main = R"( + namespace { + void ff() {} // ignore + } + void f1() {} + )"; + runSymbolCollector(Header, Main); + EXPECT_THAT(Symbols, + UnorderedElementsAre(Field(&Symbol::QualifiedName, Eq("Foo")), + Field(&Symbol::QualifiedName, Eq("Foo::f")), + Field(&Symbol::QualifiedName, Eq("f1")), + Field(&Symbol::QualifiedName, Eq("f2")))); +} + +} // namespace +} // namespace clangd +} // namespace clang