Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -3,6 +3,7 @@ ) add_clang_library(clangDaemon + ClangdIndex.cpp ClangdLSPServer.cpp ClangdServer.cpp ClangdUnit.cpp @@ -17,6 +18,8 @@ Logger.cpp Protocol.cpp ProtocolHandlers.cpp + Symbol.cpp + SymbolCompletionInfo.cpp Trace.cpp LINK_LIBS Index: clangd/ClangdIndex.h =================================================================== --- /dev/null +++ clangd/ClangdIndex.h @@ -0,0 +1,111 @@ +//===--- ClangdIndex.h - Symbol indexes for clangd.---------------*- 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_CLANGDINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDINDEX_H + +#include "Path.h" +#include "Symbol.h" +#include "SymbolIndex.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { + +/// \brief This combines multiple indexes. It is responsible for merging and +/// (re-)scoring symbols from different indexes. +class CombinedSymbolIndex : public SymbolIndex { +public: + CombinedSymbolIndex() = default; + + struct WeightedIndex { + explicit WeightedIndex(std::unique_ptr Index) + : Index(std::move(Index)) {} + + std::unique_ptr Index; + double Weight; + }; + + void addSymbolIndex(llvm::StringRef Label, WeightedIndex Index); + + llvm::Expected + complete(const CompletionRequest &Req) const override; + +private: + /// A list of pairs managed by the combined index, + /// sorted by weight - the highest comes first. + std::vector> Indexes; +}; + +/// \brief An interface for in-memory symbol sources that can be used to build +/// indexes. All symbols should be able to fit into memory, e.g. AST symbol or +// relatively small symbol table built offline. +class InMemoryIndexSourcer { +public: + virtual ~InMemoryIndexSourcer() = default; + + /// \brief True if it is safe to take a reference of the symbol table. + virtual bool safeToReferenceSymbols() const = 0; + + /// \brief Returns a copy of the symbol table. + virtual std::set symbols() const = 0; + + /// \brief Returns a reference to the symbol table. + virtual const std::set &symbolsReference() const = 0; +}; + +/// \brief This sources all symbols for AST index. +class ASTIndexSourcer : public InMemoryIndexSourcer { + public: + ASTIndexSourcer() = default; + + /// \brief Updates symbols in the AST corresponding to `Path`. + /// \param TopLevelDecls Only symbols in these declarations will be + /// collected. + void update(PathRef Path, ASTContext &Context, + std::shared_ptr PP, + llvm::ArrayRef TopLevelDecls); + + /// \brief Removes all symbols corresponding to `Path` + void remove(PathRef Path); + + /// \brief Since multiple threads can read and update symbols, a reference is + /// not safe. + bool safeToReferenceSymbols() const override { return false; } + + std::set symbols() const override; + + const std::set &symbolsReference() const override { + llvm_unreachable("ASTIndexSourcer does not support getting reference to " + "the symbol index."); + } + + private: + llvm::StringMap> FileToSymbols; + mutable std::mutex Mutex; +}; + +/// \brief A simple SymbolIndex implementation which iterates over all symbols +/// and performs simple substring matching. +class SimpleSymbolIndex : public SymbolIndex { +public: + SimpleSymbolIndex(InMemoryIndexSourcer *Sourcer) : Sourcer(Sourcer) {} + + llvm::Expected + complete(const CompletionRequest &Req) const override; + +private: + InMemoryIndexSourcer *Sourcer; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDINDEX_H Index: clangd/ClangdIndex.cpp =================================================================== --- /dev/null +++ clangd/ClangdIndex.cpp @@ -0,0 +1,127 @@ +//===--- ClangdIndex.cpp - Symbol indexes for clangd. ------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-------------------------------------------------------------------===// + +#include "ClangdIndex.h" +#include "SymbolIndex.h" +#include "clang/Index/IndexingAction.h" + +namespace clang { +namespace clangd { + +void CombinedSymbolIndex::addSymbolIndex( + llvm::StringRef Label, CombinedSymbolIndex::WeightedIndex Index) { + // Keep the indexes sorted by weight. + auto I = Indexes.begin(); + for (auto E = Indexes.end(); I != E; ++I) { + if (I->second.Weight < Index.Weight) + break; + } + Indexes.insert(I, std::make_pair(Label.str(), std::move(Index))); +} + +llvm::Expected +CombinedSymbolIndex::complete(const CompletionRequest &Req) const { + // Add results from indexes that have highest weight first and ignore + // duplications in indexes with lower weight. + std::set AddedUSRs; + CompletionResult Result; + Result.AllMatched = true; + for (const auto &Pair : Indexes) { + const WeightedIndex &Index = Pair.second; + auto Res = Index.Index->complete(Req); + if (!Res) { + // FIXME: use logger. + llvm::errs() << "Failed to complete request " << Req.Query << " in index " + << Pair.first << "\n"; + continue; + } + // FIXME: do something about `AllMatched`. If an index didn't match all + // candidates, we might need to keep an internal state for this request so + // that we could know which indexes to query when users ask for more + // results with the same request. + Result.AllMatched &= Res->AllMatched; + + for (auto &Sym : Res->Symbols) { + if (AddedUSRs.find(Sym.USR) == AddedUSRs.end()) { + AddedUSRs.insert(Sym.USR); + // FIXME(ioeric): consider ranking signals. + Sym.SymbolScore = Index.Weight; + Result.Symbols.push_back(std::move(Sym)); + } + } + } + return Result; +} + +void ASTIndexSourcer::remove(PathRef Path) { + std::lock_guard Lock(Mutex); + FileToSymbols.erase(Path); +} + +void ASTIndexSourcer::update(PathRef Path, ASTContext &Ctx, + std::shared_ptr PP, + llvm::ArrayRef TopLevelDecls) { + auto Collector = std::make_shared(); + Collector->setPreprocessor(std::move(PP)); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = false; + + { + std::lock_guard Lock(Mutex); + + index::indexTopLevelDecls(Ctx, TopLevelDecls, Collector, IndexOpts); + FileToSymbols[Path.str()] = Collector->getSymbols(); + } +} + +std::set ASTIndexSourcer::symbols() const { + std::set Symbols; + { + std::lock_guard Lock(Mutex); + for (const auto &FileAndSymbols : FileToSymbols) + Symbols.insert(FileAndSymbols.second.begin(), + FileAndSymbols.second.end()); + } + return Symbols; +} + +/// FIXME: improve the indexing and matching algorithm. +/// FIXME: we might want to return only the next immediate segment of name. For +/// example, given query "a::", only return symbols "a::*" but "a::*::*". For +/// we simply do substring match on qualified names. +llvm::Expected +SimpleSymbolIndex::complete(const CompletionRequest &Req) const { + std::set CopiedSymbols; + if (!Sourcer->safeToReferenceSymbols()) + CopiedSymbols = Sourcer->symbols(); + const auto &SymbolsRef = Sourcer->safeToReferenceSymbols() + ? Sourcer->symbolsReference() + : CopiedSymbols; + + std::string LoweredQuery = llvm::StringRef(Req.Query).lower(); + CompletionResult Result; + for (const auto &Symbol : SymbolsRef) { + if (StringRef(StringRef(Symbol.QualifiedName).lower()) + .contains(LoweredQuery)) { + CompletionSymbol CS; + CS.QualifiedName = Symbol.QualifiedName; + CS.USR = Symbol.Identifier; + CS.CompletionInfo = Symbol.CompletionInfo; + CS.Kind = Symbol.SymInfo.Kind; + Result.Symbols.push_back(std::move(CS)); + } + } + Result.AllMatched = true; + return Result; +} + +} // namespace clangd +} // namespace clang Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -30,11 +30,15 @@ /// If \p CompileCommandsDir has a value, compile_commands.json will be /// loaded only from \p CompileCommandsDir. Otherwise, clangd will look /// for compile_commands.json in all parent directories of each file. - ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, - bool StorePreamblesInMemory, - const clangd::CodeCompleteOptions &CCOpts, - llvm::Optional ResourceDir, - llvm::Optional CompileCommandsDir); + ClangdLSPServer( + JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, + const clangd::CodeCompleteOptions &CCOpts, + llvm::Optional ResourceDir, + llvm::Optional CompileCommandsDir, + bool EnableIndexBasedCodeCompletion, + std::vector< + std::pair> + AdditionalIndexes); /// Run LSP server loop, receiving input for it from \p In. \p In must be /// opened in binary mode. Output will be written using Out variable passed to Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -235,15 +235,19 @@ C.reply(Result ? URI::fromFile(*Result).uri : ""); } -ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, - bool StorePreamblesInMemory, - const clangd::CodeCompleteOptions &CCOpts, - llvm::Optional ResourceDir, - llvm::Optional CompileCommandsDir) +ClangdLSPServer::ClangdLSPServer( + JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, + const clangd::CodeCompleteOptions &CCOpts, + llvm::Optional ResourceDir, + llvm::Optional CompileCommandsDir, + bool EnableIndexBasedCodeCompletion, + std::vector> + AdditionalIndexes) : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)), CCOpts(CCOpts), Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount, StorePreamblesInMemory, - /*Logger=*/Out, ResourceDir) {} + /*Logger=*/Out, EnableIndexBasedCodeCompletion, + std::move(AdditionalIndexes), ResourceDir) {} bool ClangdLSPServer::run(std::istream &In) { assert(!IsDone && "Run was called before"); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H +#include "ClangdIndex.h" #include "ClangdUnitStore.h" #include "DraftStore.h" #include "GlobalCompilationDatabase.h" @@ -207,12 +208,15 @@ /// clangd are stored in-memory or on disk. /// /// Various messages are logged using \p Logger. - ClangdServer(GlobalCompilationDatabase &CDB, - DiagnosticsConsumer &DiagConsumer, - FileSystemProvider &FSProvider, unsigned AsyncThreadsCount, - bool StorePreamblesInMemory, - clangd::Logger &Logger, - llvm::Optional ResourceDir = llvm::None); + ClangdServer( + GlobalCompilationDatabase &CDB, DiagnosticsConsumer &DiagConsumer, + FileSystemProvider &FSProvider, unsigned AsyncThreadsCount, + bool StorePreamblesInMemory, clangd::Logger &Logger, + bool EnableIndexBasedCodeCompletion = false, + std::vector< + std::pair> + AdditionalIndexes = {}, + llvm::Optional ResourceDir = llvm::None); /// Set the root path of the workspace. void setRootPath(PathRef RootPath); @@ -323,7 +327,10 @@ DiagnosticsConsumer &DiagConsumer; FileSystemProvider &FSProvider; DraftStore DraftMgr; + + std::unique_ptr IndexSourcer; CppFileCollection Units; + std::string ResourceDir; // If set, this represents the workspace path. llvm::Optional RootPath; @@ -339,6 +346,8 @@ // called before all other members to stop the worker thread that references // ClangdServer ClangdScheduler WorkScheduler; + + std::unique_ptr CombinedIndex; }; } // namespace clangd Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -169,18 +169,35 @@ Worker.join(); } -ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, - DiagnosticsConsumer &DiagConsumer, - FileSystemProvider &FSProvider, - unsigned AsyncThreadsCount, - bool StorePreamblesInMemory, clangd::Logger &Logger, - llvm::Optional ResourceDir) +ClangdServer::ClangdServer( + GlobalCompilationDatabase &CDB, DiagnosticsConsumer &DiagConsumer, + FileSystemProvider &FSProvider, unsigned AsyncThreadsCount, + bool StorePreamblesInMemory, clangd::Logger &Logger, + bool EnableIndexBasedCodeCompletion, + std::vector> + AdditionalIndexes, + llvm::Optional ResourceDir) : Logger(Logger), CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider), + IndexSourcer(EnableIndexBasedCodeCompletion ? new ASTIndexSourcer() + : nullptr), + Units(IndexSourcer.get()), ResourceDir(ResourceDir ? ResourceDir->str() : getStandardResourceDir()), PCHs(std::make_shared()), StorePreamblesInMemory(StorePreamblesInMemory), - WorkScheduler(AsyncThreadsCount) {} + WorkScheduler(AsyncThreadsCount) { + if (EnableIndexBasedCodeCompletion) { + assert(IndexSourcer.get() && "IndexSourcer must be set when index-based " + "code completion is enabled."); + CombinedSymbolIndex::WeightedIndex WeightedASTIndex( + llvm::make_unique(IndexSourcer.get())); + WeightedASTIndex.Weight = 10; + CombinedIndex.reset(new CombinedSymbolIndex()); + CombinedIndex->addSymbolIndex("AST", std::move(WeightedASTIndex)); + for (auto &Index : AdditionalIndexes) + CombinedIndex->addSymbolIndex(Index.first, std::move(Index.second)); + } +} void ClangdServer::setRootPath(PathRef RootPath) { std::string NewRootPath = llvm::sys::path::convert_to_slash( @@ -193,8 +210,9 @@ DocVersion Version = DraftMgr.updateDraft(File, Contents); auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = Units.getOrCreateFile( - File, ResourceDir, CDB, StorePreamblesInMemory, PCHs, Logger); + std::shared_ptr Resources = + Units.getOrCreateFile(File, ResourceDir, CDB, StorePreamblesInMemory, + PCHs, Logger, IndexSourcer.get()); return scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()}, std::move(Resources), std::move(TaggedFS)); } @@ -289,7 +307,7 @@ CompletionList Result = clangd::codeComplete( File, Resources->getCompileCommand(), Preamble ? &Preamble->Preamble : nullptr, Contents, Pos, - TaggedFS.Value, PCHs, CodeCompleteOpts, Logger); + TaggedFS.Value, PCHs, CodeCompleteOpts, Logger, CombinedIndex.get()); Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag))); }; Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H +#include "ClangdIndex.h" #include "Function.h" #include "Path.h" #include "Protocol.h" @@ -81,6 +82,7 @@ Preprocessor &getPreprocessor(); const Preprocessor &getPreprocessor() const; + std::shared_ptr getPreprocessorPtr(); /// This function returns all top-level decls, including those that come /// from Preamble. Decls, coming from Preamble, have to be deserialized, so @@ -146,12 +148,14 @@ static std::shared_ptr Create(PathRef FileName, tooling::CompileCommand Command, bool StorePreamblesInMemory, - std::shared_ptr PCHs, clangd::Logger &Logger); + std::shared_ptr PCHs, clangd::Logger &Logger, + ASTIndexSourcer *IndexSourcer); private: CppFile(PathRef FileName, tooling::CompileCommand Command, bool StorePreamblesInMemory, - std::shared_ptr PCHs, clangd::Logger &Logger); + std::shared_ptr PCHs, clangd::Logger &Logger, + ASTIndexSourcer *IndexSourcer); public: CppFile(CppFile const &) = delete; @@ -254,8 +258,9 @@ std::shared_ptr PCHs; /// Used for logging various messages. clangd::Logger &Logger; -}; + ASTIndexSourcer *IndexSourcer; +}; /// Get the beginning SourceLocation at a specified \p Pos. SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -382,6 +382,10 @@ Preprocessor &ParsedAST::getPreprocessor() { return Clang->getPreprocessor(); } +std::shared_ptr ParsedAST::getPreprocessorPtr() { + return Clang->getPreprocessorPtr(); +} + const Preprocessor &ParsedAST::getPreprocessor() const { return Clang->getPreprocessor(); } @@ -424,19 +428,20 @@ CppFile::Create(PathRef FileName, tooling::CompileCommand Command, bool StorePreamblesInMemory, std::shared_ptr PCHs, - clangd::Logger &Logger) { - return std::shared_ptr(new CppFile(FileName, std::move(Command), - StorePreamblesInMemory, - std::move(PCHs), Logger)); + clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer) { + return std::shared_ptr( + new CppFile(FileName, std::move(Command), StorePreamblesInMemory, + std::move(PCHs), Logger, IndexSourcer)); } CppFile::CppFile(PathRef FileName, tooling::CompileCommand Command, bool StorePreamblesInMemory, std::shared_ptr PCHs, - clangd::Logger &Logger) + clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer) : FileName(FileName), Command(std::move(Command)), StorePreamblesInMemory(StorePreamblesInMemory), RebuildCounter(0), - RebuildInProgress(false), PCHs(std::move(PCHs)), Logger(Logger) { + RebuildInProgress(false), PCHs(std::move(PCHs)), Logger(Logger), + IndexSourcer(IndexSourcer) { Logger.log("Opened file " + FileName + " with command [" + this->Command.Directory + "] " + llvm::join(this->Command.CommandLine, " ")); @@ -639,6 +644,10 @@ if (NewAST) { Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(), NewAST->getDiagnostics().end()); + if (That->IndexSourcer) + That->IndexSourcer->update(That->FileName, NewAST->getASTContext(), + NewAST->getPreprocessorPtr(), + NewAST->getTopLevelDecls()); } else { // Don't report even Preamble diagnostics if we coulnd't build AST. Diagnostics.clear(); Index: clangd/ClangdUnitStore.h =================================================================== --- clangd/ClangdUnitStore.h +++ clangd/ClangdUnitStore.h @@ -12,6 +12,7 @@ #include +#include "ClangdIndex.h" #include "ClangdUnit.h" #include "GlobalCompilationDatabase.h" #include "Path.h" @@ -25,11 +26,14 @@ /// Thread-safe mapping from FileNames to CppFile. class CppFileCollection { public: + explicit CppFileCollection(ASTIndexSourcer *IndexSourcer) + : IndexSourcer(IndexSourcer) {} + std::shared_ptr getOrCreateFile(PathRef File, PathRef ResourceDir, GlobalCompilationDatabase &CDB, bool StorePreamblesInMemory, std::shared_ptr PCHs, - clangd::Logger &Logger) { + clangd::Logger &Logger, ASTIndexSourcer *IndexSourcer) { std::lock_guard Lock(Mutex); auto It = OpenedFiles.find(File); @@ -39,7 +43,8 @@ It = OpenedFiles .try_emplace(File, CppFile::Create(File, std::move(Command), StorePreamblesInMemory, - std::move(PCHs), Logger)) + std::move(PCHs), Logger, + IndexSourcer)) .first; } return It->second; @@ -86,6 +91,7 @@ std::mutex Mutex; llvm::StringMap> OpenedFiles; + ASTIndexSourcer *IndexSourcer; }; } // namespace clangd } // namespace clang Index: clangd/ClangdUnitStore.cpp =================================================================== --- clangd/ClangdUnitStore.cpp +++ clangd/ClangdUnitStore.cpp @@ -23,6 +23,9 @@ std::shared_ptr Result = It->second; OpenedFiles.erase(It); + if (IndexSourcer) { + IndexSourcer->remove(File); + } return Result; } @@ -42,14 +45,15 @@ It = OpenedFiles .try_emplace(File, CppFile::Create(File, std::move(NewCommand), StorePreamblesInMemory, - std::move(PCHs), Logger)) + std::move(PCHs), Logger, + IndexSourcer)) .first; } else if (!compileCommandsAreEqual(It->second->getCompileCommand(), NewCommand)) { Result.RemovedFile = std::move(It->second); It->second = CppFile::Create(File, std::move(NewCommand), StorePreamblesInMemory, - std::move(PCHs), Logger); + std::move(PCHs), Logger, IndexSourcer); } Result.FileInCollection = It->second; return Result; Index: clangd/CodeComplete.h =================================================================== --- clangd/CodeComplete.h +++ clangd/CodeComplete.h @@ -18,6 +18,7 @@ #include "Logger.h" #include "Path.h" #include "Protocol.h" +#include "SymbolIndex.h" #include "clang/Frontend/PrecompiledPreamble.h" #include "clang/Sema/CodeCompleteOptions.h" #include "clang/Tooling/CompilationDatabase.h" @@ -46,9 +47,15 @@ /// Add globals to code completion results. bool IncludeGlobals = true; + /// Add symbols in namespace context (including global namespace) to code + /// completion. + /// FIXME(ioeric): this only affects qualified-id code completion at this + /// point. + unsigned IncludeNamespaceLevelDecls = true; + /// Add brief comments to completion items, if available. - /// FIXME(ibiryukov): it looks like turning this option on significantly slows - /// down completion, investigate if it can be made faster. + /// FIXME(ibiryukov): it looks like turning this option on significantly + /// slows down completion, investigate if it can be made faster. bool IncludeBriefComments = true; /// Include results that are not legal completions in the current context. @@ -61,13 +68,16 @@ }; /// Get code completions at a specified \p Pos in \p FileName. +/// If `Index` is not nullptr, this will use the index for global code +/// completion; otherwise, use sema code completion by default. CompletionList codeComplete(PathRef FileName, const tooling::CompileCommand &Command, PrecompiledPreamble const *Preamble, StringRef Contents, Position Pos, IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, - CodeCompleteOptions Opts, Logger &Logger); + CodeCompleteOptions Opts, Logger &Logger, + const SymbolIndex *Index); /// Get signature help at a specified \p Pos in \p FileName. SignatureHelp Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -15,6 +15,7 @@ //===---------------------------------------------------------------------===// #include "CodeComplete.h" +#include "ClangdServer.h" #include "Compiler.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" @@ -85,6 +86,53 @@ llvm_unreachable("Unhandled CodeCompletionResult::ResultKind."); } +CompletionItemKind getKindOfSymbol(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind){ + case SK::Unknown: + return CompletionItemKind::Missing; + case SK::Module: + case SK::Namespace: + case SK::NamespaceAlias: + return CompletionItemKind::Module; + case SK::Macro: + return CompletionItemKind::Text; + case SK::Enum: + return CompletionItemKind::Enum; + case SK::Struct: + case SK::Class: + case SK::Protocol: + case SK::Extension: + case SK::Union: + return CompletionItemKind::Class; + case SK::TypeAlias: + case SK::Using: + return CompletionItemKind::Reference; + case SK::Function: + case SK::ConversionFunction: + return CompletionItemKind::Function; + case SK::Variable: + case SK::Parameter: + return CompletionItemKind::Variable; + case SK::Field: + return CompletionItemKind::Field; + case SK::EnumConstant: + return CompletionItemKind::Value; + case SK::InstanceMethod: + case SK::ClassMethod: + case SK::StaticMethod: + return CompletionItemKind::Method; + case SK::InstanceProperty: + case SK::ClassProperty: + case SK::StaticProperty: + return CompletionItemKind::Property; + case SK::Constructor: + case SK::Destructor: + return CompletionItemKind::Constructor; + } + llvm_unreachable("Unhandled clang::index::SymbolKind."); +} + std::string escapeSnippet(const llvm::StringRef Text) { std::string Result; Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare. @@ -163,6 +211,22 @@ return Result; } +// Produces an integer that sorts in the same order as F. +// That is: a < b <==> encodeFloat(a) < encodeFloat(b). +static uint32_t encodeFloat(float F) { + static_assert(std::numeric_limits::is_iec559, ""); + static_assert(sizeof(float) == sizeof(uint32_t), ""); + constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1); + + // Get the bits of the float. Endianness is the same as for integers. + uint32_t U; + memcpy(&U, &F, sizeof(float)); + // IEEE 754 floats compare like sign-magnitude integers. + if (U & TopBit) // Negative float. + return 0 - U; // Map onto the low half of integers, order reversed. + return U + TopBit; // Positive floats map onto the high half of integers. +} + /// A scored code completion result. /// It may be promoted to a CompletionItem if it's among the top-ranked results. struct CompletionCandidate { @@ -210,38 +274,110 @@ } return Score; } +}; + +CompletionItem +completionSymbolToCompletionItem(const CompletionSymbol &Sym, + bool FullyQualified, SourceManager &SM, + SourceRange SpecifierRange) { + CompletionItem Item; + if (FullyQualified) + Item.label = "::"; + Item.label += Sym.CompletionInfo.Label.empty() ? Sym.QualifiedName + : Sym.CompletionInfo.Label; + Item.kind = getKindOfSymbol(Sym.Kind); + Item.detail = Sym.CompletionInfo.Detail; + Item.documentation = Sym.CompletionInfo.Documentation; + Item.insertTextFormat = InsertTextFormat::PlainText; + Item.sortText = encodeFloat(Sym.SymbolScore); + + + TextEdit Edit; + Edit.newText = + FullyQualified ? ("::" + Sym.QualifiedName) : Sym.QualifiedName; + // Get the range to be replaced. We simply replace the entire range of + // specifier. + FileID FID = SM.getFileID(SpecifierRange.getBegin()); + const auto *FE = SM.getFileEntryForID(FID); + llvm::MemoryBuffer *Buffer = SM.getMemoryBufferForFile(FE); + llvm::StringRef Code = Buffer->getBuffer(); + Edit.range.start = + offsetToPosition(Code, SM.getFileOffset(SpecifierRange.getBegin())); + Edit.range.end = + offsetToPosition(Code, SM.getFileOffset(SpecifierRange.getEnd())); + Item.textEdit = std::move(Edit); + return Item; +} - // Produces an integer that sorts in the same order as F. - // That is: a < b <==> encodeFloat(a) < encodeFloat(b). - static uint32_t encodeFloat(float F) { - static_assert(std::numeric_limits::is_iec559, ""); - static_assert(sizeof(float) == sizeof(uint32_t), ""); - constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1); - - // Get the bits of the float. Endianness is the same as for integers. - uint32_t U; - memcpy(&U, &F, sizeof(float)); - // IEEE 754 floats compare like sign-magnitude integers. - if (U & TopBit) // Negative float. - return 0 - U; // Map onto the low half of integers, order reversed. - return U + TopBit; // Positive floats map onto the high half of integers. +void qualifiedIdCompletionWithIndex(const SymbolIndex &Index, Sema &S, + const CXXScopeSpec &SS, + CompletionList *Items) { + std::string WrittenSS = + Lexer::getSourceText(CharSourceRange::getCharRange(SS.getRange()), + S.getSourceManager(), clang::LangOptions()); + std::string RecognizedSpecifier; + if (SS.isValid()) { + DeclContext *DC = S.computeDeclContext(SS); + if (auto *NS = llvm::dyn_cast(DC)) { + RecognizedSpecifier = NS->getQualifiedNameAsString(); + } else if (auto *TU = llvm::dyn_cast(DC)) { + RecognizedSpecifier = "::"; + // Sema does not include the suffix "::" in the range of SS, so we add it + // back here. + WrittenSS = "::"; + } } -}; + + CompletionRequest Req; + Req.Query = RecognizedSpecifier.empty() ? WrittenSS : RecognizedSpecifier; + auto Filter = S.getPreprocessor().getCodeCompletionFilter(); + llvm::errs() << " Specifier: [" << Req.Query << "], " + << "Filter: [" << Filter << "], Written: [" << WrittenSS + << "]\n"; + // FIXME: for now we simply cancatenate specifier with the typed filter. We + // might want to fix the specifier prefix if it is a recognized context (e.g. + // a known namespace in the AST). + if (!Filter.empty()) { + if (!Req.Query.empty()) + Req.Query += "::"; + Req.Query += Filter; + } + + llvm::Expected Result = Index.complete(Req); + if (!Result) { + // FIXME(ioeric): use logger to output error message. + llvm::consumeError(Result.takeError()); + // FIXME(ioeric): consider falling back to sema code completion. + return; + } + for (unsigned int i = 0; i < Result->Symbols.size(); ++i) + Items->items.push_back(completionSymbolToCompletionItem( + Result->Symbols[i], StringRef(WrittenSS).startswith("::"), + S.getSourceManager(), SS.getRange())); + Items->isIncomplete = true; +} class CompletionItemsCollector : public CodeCompleteConsumer { public: CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) + CompletionList &Items, + const SymbolIndex *Index = nullptr) : CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(), /*OutputIsBinary=*/false), ClangdOpts(CodeCompleteOpts), Items(Items), Allocator(std::make_shared()), - CCTUInfo(Allocator) {} + CCTUInfo(Allocator), Index(Index) {} void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, CodeCompletionResult *Results, unsigned NumResults) override final { StringRef Filter = S.getPreprocessor().getCodeCompletionFilter(); + if (Index) { + if (auto OptSS = Context.getCXXScopeSpecifier()) { + // FIXME: output a warning to logger if there are results from sema. + return qualifiedIdCompletionWithIndex(*Index, S, **OptSS, &Items); + } + } std::priority_queue Candidates; for (unsigned I = 0; I < NumResults; ++I) { auto &Result = Results[I]; @@ -336,7 +472,9 @@ CompletionList &Items; std::shared_ptr Allocator; CodeCompletionTUInfo CCTUInfo; - + // If set, use the index-based global code completion; otherwise, use sema + // code completion by default. + const SymbolIndex *Index; }; // CompletionItemsCollector bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { @@ -349,8 +487,9 @@ public: PlainTextCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) - : CompletionItemsCollector(CodeCompleteOpts, Items) {} + CompletionList &Items, + const SymbolIndex *Index) + : CompletionItemsCollector(CodeCompleteOpts, Items, Index) {} private: void ProcessChunks(const CodeCompletionString &CCS, @@ -385,8 +524,9 @@ public: SnippetCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) - : CompletionItemsCollector(CodeCompleteOpts, Items) {} + CompletionList &Items, + const SymbolIndex *Index) + : CompletionItemsCollector(CodeCompleteOpts, Items, Index) {} private: void ProcessChunks(const CodeCompletionString &CCS, @@ -655,6 +795,7 @@ Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns; Result.IncludeMacros = IncludeMacros; Result.IncludeGlobals = IncludeGlobals; + Result.IncludeNamespaceLevelDecls = IncludeNamespaceLevelDecls; Result.IncludeBriefComments = IncludeBriefComments; return Result; @@ -666,15 +807,16 @@ StringRef Contents, Position Pos, IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, - CodeCompleteOptions Opts, Logger &Logger) { + CodeCompleteOptions Opts, Logger &Logger, + const SymbolIndex *Index) { CompletionList Results; std::unique_ptr Consumer; if (Opts.EnableSnippets) { - Consumer = - llvm::make_unique(Opts, Results); + Consumer = llvm::make_unique(Opts, Results, + Index); } else { - Consumer = - llvm::make_unique(Opts, Results); + Consumer = llvm::make_unique( + Opts, Results, Index); } invokeCodeComplete(std::move(Consumer), Opts.getClangCompleteOpts(), FileName, Command, Preamble, Contents, Pos, std::move(VFS), Index: clangd/Symbol.h =================================================================== --- /dev/null +++ clangd/Symbol.h @@ -0,0 +1,91 @@ +//===--- 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 "SymbolCompletionInfo.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexSymbol.h" + +#include + +namespace clang { +namespace clangd { + +struct SymbolLocation { + // The path of the source file where a symbol occurs. + std::string FilePath; + // The offset to the first character of the symbol from the beginning of the + // source file. + unsigned StartOffset; + // The 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. +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; + // Information for code completion. + SymbolCompletionInfo CompletionInfo; + + bool operator<(const Symbol& S) const { + return Identifier < S.Identifier; + } + + // FIXME: add extra fields for index scoring signals. + // FIXME: add all occurrences of the symbol. +}; + +// 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 setPreprocessor(std::shared_ptr PP) override { + this->PP = std::move(PP); + } + void initialize(ASTContext &Ctx) override { ASTCtx = &Ctx; } + + const std::set &getSymbols() const { return Symbols; } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, + unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override; + +private: + std::set Symbols; + std::shared_ptr PP; + ASTContext *ASTCtx; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOL_H Index: clangd/Symbol.cpp =================================================================== --- /dev/null +++ clangd/Symbol.cpp @@ -0,0 +1,63 @@ +//===--- 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/Basic/SourceManager.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace clang { +namespace clangd { + +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 ID(Buff.data(), Buff.size()); + auto It = Symbols.find({ID}); + if (It != Symbols.end()) { + return true; + } + + auto &SM = ND->getASTContext().getSourceManager(); + SymbolLocation Location = {SM.getFilename(D->getLocation()), + SM.getFileOffset(D->getLocStart()), + SM.getFileOffset(D->getLocEnd())}; + assert(ASTCtx && "ASTContext must be set."); + assert(PP.get() && "Preprocessor must be set."); + SymbolCompletionInfo CompleteInfo = + SymbolCompletionInfo::create(*ASTCtx, *PP, ND); + Symbols.insert({std::move(ID), ND->getQualifiedNameAsString(), + index::getSymbolInfo(D), std::move(Location), + std::move(CompleteInfo)}); + } + + return true; +} + +} // clangd +} // clang Index: clangd/SymbolCompletionInfo.h =================================================================== --- /dev/null +++ clangd/SymbolCompletionInfo.h @@ -0,0 +1,31 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace clangd { + +struct SymbolCompletionInfo { + static SymbolCompletionInfo create(ASTContext &Ctx, Preprocessor &PP, + const NamedDecl *ND); + + SymbolCompletionInfo() = default; + /// Label that can be be displayed in the completion list. + std::string Label; + /// Symbol annotation and/or comment for the symbol declaration. + std::string Documentation; + /// Detail about the symbol like result type. + std::string Detail; + /// Function/method parameters. Useful for snippets. + std::vector Params; +}; + +} // clangd +} // clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLCOMPLETIONINFO_H Index: clangd/SymbolCompletionInfo.cpp =================================================================== --- /dev/null +++ clangd/SymbolCompletionInfo.cpp @@ -0,0 +1,116 @@ +#include "SymbolCompletionInfo.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "llvm/Support/YAMLTraits.h" +#include + +namespace clang { +namespace clangd { +namespace { + +std::string getDocumentation(const CodeCompletionString &CCS) { + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + std::string Result; + const unsigned AnnotationCount = CCS.getAnnotationCount(); + if (AnnotationCount > 0) { + Result += "Annotation"; + if (AnnotationCount == 1) { + Result += ": "; + } else /* AnnotationCount > 1 */ { + Result += "s: "; + } + for (unsigned I = 0; I < AnnotationCount; ++I) { + Result += CCS.getAnnotation(I); + Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); + } + } + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + if (!Result.empty()) { + // This means we previously added annotations. Add an extra newline + // character to make the annotations stand out. + Result.push_back('\n'); + } + Result += CCS.getBriefComment(); + } + return Result; +} + +void ProcessChunks(const CodeCompletionString &CCS, + SymbolCompletionInfo *Info) { + for (const auto &Chunk : CCS) { + // Informative qualifier chunks only clutter completion results, skip + // them. + if (Chunk.Kind == CodeCompletionString::CK_Informative && + StringRef(Chunk.Text).endswith("::")) + continue; + + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // There's always exactly one CK_TypedText chunk. + Info->Label += Chunk.Text; + break; + case CodeCompletionString::CK_ResultType: + Info->Detail = Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + Info->Params.push_back(Chunk.Text); + LLVM_FALLTHROUGH; + default: + Info->Label += Chunk.Text; + break; + } + } +} + +inline std::string +joinNamespaces(const llvm::SmallVectorImpl &Namespaces) { + if (Namespaces.empty()) + return ""; + std::string Result = Namespaces.front(); + for (auto I = Namespaces.begin() + 1, E = Namespaces.end(); I != E; ++I) + Result += ("::" + *I).str(); + return Result; +} + +// Given "a::b::c", returns {"a", "b", "c"}. +llvm::SmallVector splitSymbolName(llvm::StringRef Name) { + llvm::SmallVector Splitted; + Name.split(Splitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + return Splitted; +} + +} // namespace + +SymbolCompletionInfo SymbolCompletionInfo::create(ASTContext &Ctx, + Preprocessor &PP, + const NamedDecl *ND) { + CodeCompletionResult SymbolCompletion(ND, 0); + auto Allocator = std::make_shared(); + CodeCompletionTUInfo TUInfo(Allocator); + const auto *CCS = SymbolCompletion.CreateCodeCompletionString( + Ctx, PP, CodeCompletionContext::CCC_Name, *Allocator, TUInfo, + /*IncludeBriefComments*/ true); + SymbolCompletionInfo Info; + Info.Documentation = getDocumentation(*CCS); + + ProcessChunks(*CCS, &Info); + // Since symbol names in CCS labels are not qualified, we prepend a namespace + // qualfifier. + std::string QualifiedName = ND->getQualifiedNameAsString(); + auto SplittedNames = splitSymbolName(QualifiedName); + if (SplittedNames.size() > 1) { + std::string LabelPrefix = joinNamespaces(SmallVector( + SplittedNames.begin(), SplittedNames.end() - 1)); + Info.Label = LabelPrefix + "::" + Info.Label; + } + return Info; +} + +} // namespace clangd +} // namespace clang Index: clangd/SymbolIndex.h =================================================================== --- /dev/null +++ clangd/SymbolIndex.h @@ -0,0 +1,60 @@ +//===--- CompletionIndex.h - Index for code completion -----------*- 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_SYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLINDEX_H + +#include "SymbolCompletionInfo.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { + +struct CompletionRequest { + std::string Query; + size_t MaxCandidateCount = UINT_MAX; +}; + +/// \brief Signals for scoring completion candidates. +struct ScoreSignals { + // FIXME: add score signals like cross-reference count. +}; + +struct CompletionSymbol { + ScoreSignals Signals; + float SymbolScore; + + std::string USR; + index::SymbolKind Kind; + std::string QualifiedName; + + SymbolCompletionInfo CompletionInfo; +}; + +struct CompletionResult { + std::vector Symbols; + bool AllMatched; +}; + +class SymbolIndex { +public: + virtual ~SymbolIndex() = default; + + virtual llvm::Expected + complete(const CompletionRequest &Req) const = 0; + + // FIXME: add interfaces for more index use cases: + // - Symbol getSymbolInfo(llvm::StringRef USR); + // - getAllOccurrences(llvm::StringRef USR); +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLINDEX_H Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -90,6 +90,15 @@ "Trace internal events and timestamps in chrome://tracing JSON format"), llvm::cl::init(""), llvm::cl::Hidden); +static llvm::cl::opt EnableIndexBasedCompletion( + "enable-index-based-completion", + llvm::cl::desc( + "Enable index-based global code completion (experimental). Clangd will " + "use symbols built from ASTs of opened files and additional indexes " + "(e.g. offline built codebase-wide symbol table) to complete partial " + "symbols."), + llvm::cl::init(false)); + int main(int argc, char *argv[]) { llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); @@ -170,10 +179,15 @@ clangd::CodeCompleteOptions CCOpts; CCOpts.EnableSnippets = EnableSnippets; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; + if (EnableIndexBasedCompletion) { + // Disable sema code completion for qualified code completion and use global + // symbol index instead. + CCOpts.IncludeNamespaceLevelDecls = false; + } // Initialize and run ClangdLSPServer. ClangdLSPServer LSPServer(Out, WorkerThreadsCount, StorePreamblesInMemory, - CCOpts, ResourceDirRef, - CompileCommandsDirPath); + CCOpts, ResourceDirRef, CompileCommandsDirPath, + EnableIndexBasedCompletion, {}); constexpr int NoShutdownRequestErrorCode = 1; llvm::set_thread_name("clangd.main"); return LSPServer.run(std::cin) ? 0 : NoShutdownRequestErrorCode; 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,110 @@ +//===-- 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->getSymbols(); + return true; + } + +protected: + std::set 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