diff --git a/clang-tools-extra/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp b/clang-tools-extra/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp index b4f7f0baeb80..7d62cf12d3c0 100644 --- a/clang-tools-extra/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp +++ b/clang-tools-extra/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -1,172 +1,173 @@ //===--- GlobalSymbolBuilderMain.cpp -----------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // GlobalSymbolBuilder is a tool to generate YAML-format symbols across the // whole project. This tools is for **experimental** only. Don't use it in // production code. // //===---------------------------------------------------------------------===// #include "index/CanonicalIncludes.h" #include "index/Index.h" #include "index/Merge.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Execution.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/YAMLTraits.h" using namespace llvm; using namespace clang::tooling; using clang::clangd::SymbolSlab; namespace clang { namespace clangd { namespace { static llvm::cl::opt AssumedHeaderDir( "assume-header-dir", llvm::cl::desc("The index includes header that a symbol is defined in. " "If the absolute path cannot be determined (e.g. an " "in-memory VFS) then the relative path is resolved against " "this directory, which must be absolute. If this flag is " "not given, such headers will have relative paths."), llvm::cl::init("")); class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: SymbolIndexActionFactory(tooling::ExecutionContext *Ctx) : Ctx(Ctx) {} clang::FrontendAction *create() override { // Wraps the index action and reports collected symbols to the execution // context at the end of each translation unit. class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, std::unique_ptr Includes, const index::IndexingOptions &Opts, tooling::ExecutionContext *Ctx) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), Ctx(Ctx), Collector(C), Includes(std::move(Includes)), PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); return WrapperFrontendAction::CreateASTConsumer(CI, InFile); } void EndSourceFileAction() override { WrapperFrontendAction::EndSourceFileAction(); auto Symbols = Collector->takeSymbols(); for (const auto &Sym : Symbols) { std::string IDStr; llvm::raw_string_ostream OS(IDStr); OS << Sym.ID; Ctx->reportResult(OS.str(), SymbolToYAML(Sym)); } } private: tooling::ExecutionContext *Ctx; std::shared_ptr Collector; std::unique_ptr Includes; std::unique_ptr PragmaHandler; }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; auto CollectorOpts = SymbolCollector::Options(); CollectorOpts.FallbackDir = AssumedHeaderDir; CollectorOpts.CollectIncludePath = true; + CollectorOpts.CountReferences = true; auto Includes = llvm::make_unique(); addSystemHeadersMapping(Includes.get()); CollectorOpts.Includes = Includes.get(); return new WrappedIndexAction( std::make_shared(std::move(CollectorOpts)), std::move(Includes), IndexOpts, Ctx); } tooling::ExecutionContext *Ctx; }; // Combine occurrences of the same symbol across translation units. SymbolSlab mergeSymbols(tooling::ToolResults *Results) { SymbolSlab::Builder UniqueSymbols; llvm::BumpPtrAllocator Arena; Symbol::Details Scratch; Results->forEachResult([&](llvm::StringRef Key, llvm::StringRef Value) { Arena.Reset(); llvm::yaml::Input Yin(Value, &Arena); auto Sym = clang::clangd::SymbolFromYAML(Yin, Arena); clang::clangd::SymbolID ID; Key >> ID; if (const auto *Existing = UniqueSymbols.find(ID)) UniqueSymbols.insert(mergeSymbol(*Existing, Sym, &Scratch)); else UniqueSymbols.insert(Sym); }); return std::move(UniqueSymbols).build(); } } // namespace } // namespace clangd } // namespace clang int main(int argc, const char **argv) { llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); const char* Overview = "This is an **experimental** tool to generate YAML-format " "project-wide symbols for clangd (global code completion). It would be " "changed and deprecated eventually. Don't use it in production code!"; auto Executor = clang::tooling::createExecutorFromCommandLineArgs( argc, argv, cl::GeneralCategory, Overview); if (!Executor) { llvm::errs() << llvm::toString(Executor.takeError()) << "\n"; return 1; } if (!clang::clangd::AssumedHeaderDir.empty() && !llvm::sys::path::is_absolute(clang::clangd::AssumedHeaderDir)) { llvm::errs() << "--assume-header-dir must be an absolute path.\n"; return 1; } // Map phase: emit symbols found in each translation unit. auto Err = Executor->get()->execute( llvm::make_unique( Executor->get()->getExecutionContext())); if (Err) { llvm::errs() << llvm::toString(std::move(Err)) << "\n"; } // Reduce phase: combine symbols using the ID as a key. auto UniqueSymbols = clang::clangd::mergeSymbols(Executor->get()->getToolResults()); // Output phase: emit YAML for result symbols. SymbolsToYAML(UniqueSymbols, llvm::outs()); return 0; } diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp index 4f1f10a44c0e..db7a8ac43bd1 100644 --- a/clang-tools-extra/clangd/index/FileIndex.cpp +++ b/clang-tools-extra/clangd/index/FileIndex.cpp @@ -1,95 +1,97 @@ //===--- FileIndex.cpp - Indexes for files. ------------------------ C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "FileIndex.h" #include "SymbolCollector.h" #include "clang/Index/IndexingAction.h" namespace clang { namespace clangd { namespace { /// Retrieves namespace and class level symbols in \p Decls. std::unique_ptr indexAST(ASTContext &Ctx, std::shared_ptr PP, llvm::ArrayRef Decls) { SymbolCollector::Options CollectorOpts; // FIXME(ioeric): we might also want to collect include headers. We would need // to make sure all includes are canonicalized (with CanonicalIncludes), which // is not trivial given the current way of collecting symbols: we only have // AST at this point, but we also need preprocessor callbacks (e.g. // CommentHandler for IWYU pragma) to canonicalize includes. CollectorOpts.CollectIncludePath = false; + CollectorOpts.CountReferences = false; auto Collector = std::make_shared(std::move(CollectorOpts)); Collector->setPreprocessor(std::move(PP)); index::IndexingOptions IndexOpts; + // We only need declarations, because we don't count references. IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::All; + index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; IndexOpts.IndexFunctionLocals = false; index::indexTopLevelDecls(Ctx, Decls, Collector, IndexOpts); auto Symbols = llvm::make_unique(); *Symbols = Collector->takeSymbols(); return Symbols; } } // namespace void FileSymbols::update(PathRef Path, std::unique_ptr Slab) { std::lock_guard Lock(Mutex); if (!Slab) FileToSlabs.erase(Path); else FileToSlabs[Path] = std::move(Slab); } std::shared_ptr> FileSymbols::allSymbols() { // The snapshot manages life time of symbol slabs and provides pointers of all // symbols in all slabs. struct Snapshot { std::vector Pointers; std::vector> KeepAlive; }; auto Snap = std::make_shared(); { std::lock_guard Lock(Mutex); for (const auto &FileAndSlab : FileToSlabs) { Snap->KeepAlive.push_back(FileAndSlab.second); for (const auto &Iter : *FileAndSlab.second) Snap->Pointers.push_back(&Iter); } } auto *Pointers = &Snap->Pointers; // Use aliasing constructor to keep the snapshot alive along with the // pointers. return {std::move(Snap), Pointers}; } void FileIndex::update(PathRef Path, ParsedAST *AST) { if (!AST) { FSymbols.update(Path, nullptr); } else { auto Slab = indexAST(AST->getASTContext(), AST->getPreprocessorPtr(), AST->getTopLevelDecls()); FSymbols.update(Path, std::move(Slab)); } auto Symbols = FSymbols.allSymbols(); Index.build(std::move(Symbols)); } bool FileIndex::fuzzyFind( const FuzzyFindRequest &Req, llvm::function_ref Callback) const { return Index.fuzzyFind(Req, Callback); } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/Index.h b/clang-tools-extra/clangd/index/Index.h index 629db5309283..efd18d9b9401 100644 --- a/clang-tools-extra/clangd/index/Index.h +++ b/clang-tools-extra/clangd/index/Index.h @@ -1,270 +1,273 @@ //===--- 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_INDEX_INDEX_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_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/StringExtras.h" #include #include namespace clang { namespace clangd { struct SymbolLocation { // The URI of the source file where a symbol occurs. llvm::StringRef FileURI; // The 0-based offsets of the symbol from the beginning of the source file, // using half-open range, [StartOffset, EndOffset). unsigned StartOffset = 0; unsigned EndOffset = 0; operator bool() const { return !FileURI.empty(); } }; 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 160-bits 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; } private: friend llvm::hash_code hash_value(const SymbolID &ID) { // We already have a good hash, just return the first bytes. static_assert(sizeof(size_t) <= 20, "size_t longer than SHA1!"); return *reinterpret_cast(ID.HashValue.data()); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); friend void operator>>(llvm::StringRef Str, SymbolID &ID); std::array HashValue; }; // Write SymbolID into the given stream. SymbolID is encoded as a 40-bytes // hex string. llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); // Construct SymbolID from a hex string. // The HexStr is required to be a 40-bytes hex string, which is encoded from the // "<<" operator. void operator>>(llvm::StringRef HexStr, SymbolID &ID); } // namespace clangd } // namespace clang namespace llvm { // Support SymbolIDs as DenseMap keys. template <> struct DenseMapInfo { static inline clang::clangd::SymbolID getEmptyKey() { static clang::clangd::SymbolID EmptyKey("EMPTYKEY"); return EmptyKey; } static inline clang::clangd::SymbolID getTombstoneKey() { static clang::clangd::SymbolID TombstoneKey("TOMBSTONEKEY"); return TombstoneKey; } static unsigned getHashValue(const clang::clangd::SymbolID &Sym) { return hash_value(Sym); } static bool isEqual(const clang::clangd::SymbolID &LHS, const clang::clangd::SymbolID &RHS) { return LHS == RHS; } }; } // namespace llvm namespace clang { namespace clangd { // The class presents a C++ symbol, e.g. class, function. // // WARNING: Symbols do not own much of their underlying data - typically strings // are owned by a SymbolSlab. They should be treated as non-owning references. // Copies are shallow. // When adding new unowned data fields to Symbol, remember to update: // - SymbolSlab::Builder in Index.cpp, to copy them to the slab's storage. // - mergeSymbol in Merge.cpp, to properly combine two Symbols. struct Symbol { // The ID of the symbol. SymbolID ID; // The symbol information, like symbol kind. index::SymbolInfo SymInfo; // The unqualified name of the symbol, e.g. "bar" (for ns::bar). llvm::StringRef Name; // The containing namespace. e.g. "" (global), "ns::" (top-level namespace). llvm::StringRef Scope; // The location of the symbol's definition, if one was found. // This just covers the symbol name (e.g. without class/function body). SymbolLocation Definition; // The location of the preferred declaration of the symbol. // This just covers the symbol name. // This may be the same as Definition. // // A C++ symbol may have multiple declarations, and we pick one to prefer. // * For classes, the canonical declaration should be the definition. // * For non-inline functions, the canonical declaration typically appears // in the ".h" file corresponding to the definition. SymbolLocation CanonicalDeclaration; + // The number of translation units that reference this symbol from their main + // file. This number is only meaningful if aggregated in an index. + unsigned References = 0; /// A brief description of the symbol that can be displayed in the completion /// candidate list. For example, "Foo(X x, Y y) const" is a labal for a /// function. llvm::StringRef CompletionLabel; /// The piece of text that the user is expected to type to match the /// code-completion string, typically a keyword or the name of a declarator or /// macro. llvm::StringRef CompletionFilterText; /// What to insert when completing this symbol (plain text version). llvm::StringRef CompletionPlainInsertText; /// What to insert when completing this symbol (snippet version). This is /// empty if it is the same as the plain insert text above. llvm::StringRef CompletionSnippetInsertText; /// Optional symbol details that are not required to be set. For example, an /// index fuzzy match can return a large number of symbol candidates, and it /// is preferable to send only core symbol information in the batched results /// and have clients resolve full symbol information for a specific candidate /// if needed. struct Details { /// Documentation including comment for the symbol declaration. llvm::StringRef Documentation; /// This is what goes into the LSP detail field in a completion item. For /// example, the result type of a function. llvm::StringRef CompletionDetail; /// This can be either a URI of the header to be #include'd for this symbol, /// or a literal header quoted with <> or "" that is suitable to be included /// directly. When this is a URI, the exact #include path needs to be /// calculated according to the URI scheme. /// /// This is a canonical include for the symbol and can be different from /// FileURI in the CanonicalDeclaration. llvm::StringRef IncludeHeader; }; // Optional details of the symbol. const Details *Detail = nullptr; // FIXME: add all occurrences support. // FIXME: add extra fields for index scoring signals. }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S); // An immutable symbol container that stores a set of symbols. // The container will maintain the lifetime of the symbols. class SymbolSlab { public: using const_iterator = std::vector::const_iterator; using iterator = const_iterator; SymbolSlab() = default; const_iterator begin() const { return Symbols.begin(); } const_iterator end() const { return Symbols.end(); } const_iterator find(const SymbolID &SymID) const; size_t size() const { return Symbols.size(); } // Estimates the total memory usage. size_t bytes() const { return sizeof(*this) + Arena.getTotalMemory() + Symbols.capacity() * sizeof(Symbol); } // SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab. // The frozen SymbolSlab will use less memory. class Builder { public: // Adds a symbol, overwriting any existing one with the same ID. // This is a deep copy: underlying strings will be owned by the slab. void insert(const Symbol& S); // Returns the symbol with an ID, if it exists. Valid until next insert(). const Symbol* find(const SymbolID &ID) { auto I = SymbolIndex.find(ID); return I == SymbolIndex.end() ? nullptr : &Symbols[I->second]; } // Consumes the builder to finalize the slab. SymbolSlab build() &&; private: llvm::BumpPtrAllocator Arena; // Intern table for strings. Contents are on the arena. llvm::DenseSet Strings; std::vector Symbols; // Values are indices into Symbols vector. llvm::DenseMap SymbolIndex; }; private: SymbolSlab(llvm::BumpPtrAllocator Arena, std::vector Symbols) : Arena(std::move(Arena)), Symbols(std::move(Symbols)) {} llvm::BumpPtrAllocator Arena; // Owns Symbol data that the Symbols do not. std::vector Symbols; // Sorted by SymbolID to allow lookup. }; struct FuzzyFindRequest { /// \brief A query string for the fuzzy find. This is matched against symbols' /// un-qualified identifiers and should not contain qualifiers like "::". std::string Query; /// \brief If this is non-empty, symbols must be in at least one of the scopes /// (e.g. namespaces) excluding nested scopes. For example, if a scope "xyz::" /// is provided, the matched symbols must be defined in namespace xyz but not /// namespace xyz::abc. /// /// The global scope is "", a top level scope is "foo::", etc. std::vector Scopes; /// \brief The number of top candidates to return. The index may choose to /// return more than this, e.g. if it doesn't know which candidates are best. size_t MaxCandidateCount = UINT_MAX; }; /// \brief Interface for symbol indexes that can be used for searching or /// matching symbols among a set of symbols based on names or unique IDs. class SymbolIndex { public: virtual ~SymbolIndex() = default; /// \brief Matches symbols in the index fuzzily and applies \p Callback on /// each matched symbol before returning. /// If returned Symbols are used outside Callback, they must be deep-copied! /// /// Returns true if there may be more results (limited by MaxCandidateCount). virtual bool fuzzyFind(const FuzzyFindRequest &Req, llvm::function_ref Callback) const = 0; // FIXME: add interfaces for more index use cases: // - Symbol getSymbolInfo(SymbolID); // - getAllOccurrences(SymbolID); }; } // namespace clangd } // namespace clang #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H diff --git a/clang-tools-extra/clangd/index/Merge.cpp b/clang-tools-extra/clangd/index/Merge.cpp index 57c62c5bc5cf..9df5376bbfd8 100644 --- a/clang-tools-extra/clangd/index/Merge.cpp +++ b/clang-tools-extra/clangd/index/Merge.cpp @@ -1,107 +1,108 @@ //===--- Merge.h ------------------------------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===---------------------------------------------------------------------===// #include "Merge.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/raw_ostream.h" namespace clang { namespace clangd { namespace { using namespace llvm; class MergedIndex : public SymbolIndex { public: MergedIndex(const SymbolIndex *Dynamic, const SymbolIndex *Static) : Dynamic(Dynamic), Static(Static) {} // FIXME: Deleted symbols in dirty files are still returned (from Static). // To identify these eliminate these, we should: // - find the generating file from each Symbol which is Static-only // - ask Dynamic if it has that file (needs new SymbolIndex method) // - if so, drop the Symbol. bool fuzzyFind(const FuzzyFindRequest &Req, function_ref Callback) const override { // We can't step through both sources in parallel. So: // 1) query all dynamic symbols, slurping results into a slab // 2) query the static symbols, for each one: // a) if it's not in the dynamic slab, yield it directly // b) if it's in the dynamic slab, merge it and yield the result // 3) now yield all the dynamic symbols we haven't processed. bool More = false; // We'll be incomplete if either source was. SymbolSlab::Builder DynB; More |= Dynamic->fuzzyFind(Req, [&](const Symbol &S) { DynB.insert(S); }); SymbolSlab Dyn = std::move(DynB).build(); DenseSet SeenDynamicSymbols; Symbol::Details Scratch; More |= Static->fuzzyFind(Req, [&](const Symbol &S) { auto DynS = Dyn.find(S.ID); if (DynS == Dyn.end()) return Callback(S); SeenDynamicSymbols.insert(S.ID); Callback(mergeSymbol(*DynS, S, &Scratch)); }); for (const Symbol &S : Dyn) if (!SeenDynamicSymbols.count(S.ID)) Callback(S); return More; } private: const SymbolIndex *Dynamic, *Static; }; } Symbol mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) { assert(L.ID == R.ID); // We prefer information from TUs that saw the definition. // Classes: this is the def itself. Functions: hopefully the header decl. // If both did (or both didn't), continue to prefer L over R. bool PreferR = R.Definition && !L.Definition; Symbol S = PreferR ? R : L; // The target symbol we're merging into. const Symbol &O = PreferR ? L : R; // The "other" less-preferred symbol. // For each optional field, fill it from O if missing in S. // (It might be missing in O too, but that's a no-op). if (!S.Definition) S.Definition = O.Definition; if (!S.CanonicalDeclaration) S.CanonicalDeclaration = O.CanonicalDeclaration; + S.References += O.References; if (S.CompletionLabel == "") S.CompletionLabel = O.CompletionLabel; if (S.CompletionFilterText == "") S.CompletionFilterText = O.CompletionFilterText; if (S.CompletionPlainInsertText == "") S.CompletionPlainInsertText = O.CompletionPlainInsertText; if (S.CompletionSnippetInsertText == "") S.CompletionSnippetInsertText = O.CompletionSnippetInsertText; if (O.Detail) { if (S.Detail) { // Copy into scratch space so we can merge. *Scratch = *S.Detail; if (Scratch->Documentation == "") Scratch->Documentation = O.Detail->Documentation; if (Scratch->CompletionDetail == "") Scratch->CompletionDetail = O.Detail->CompletionDetail; if (Scratch->IncludeHeader == "") Scratch->IncludeHeader = O.Detail->IncludeHeader; S.Detail = Scratch; } else S.Detail = O.Detail; } return S; } std::unique_ptr mergeIndex(const SymbolIndex *Dynamic, const SymbolIndex *Static) { return llvm::make_unique(Dynamic, Static); } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index e237e136d256..b27ace5f3ffa 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -1,346 +1,367 @@ //===--- SymbolCollector.cpp -------------------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "SymbolCollector.h" #include "../AST.h" #include "../CodeCompletionStrings.h" #include "../Logger.h" #include "../URI.h" #include "CanonicalIncludes.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/USRGeneration.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" namespace clang { namespace clangd { namespace { // 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. llvm::Optional toURI(const SourceManager &SM, StringRef Path, const SymbolCollector::Options &Opts) { llvm::SmallString<128> AbsolutePath(Path); if (std::error_code EC = SM.getFileManager().getVirtualFileSystem()->makeAbsolute( AbsolutePath)) llvm::errs() << "Warning: could not make absolute file: '" << EC.message() << '\n'; if (llvm::sys::path::is_absolute(AbsolutePath)) { // Handle the symbolic link path case where the current working directory // (getCurrentWorkingDirectory) is a symlink./ We always want to the real // file path (instead of the symlink path) for the C++ symbols. // // Consider the following example: // // src dir: /project/src/foo.h // current working directory (symlink): /tmp/build -> /project/src/ // // The file path of Symbol is "/project/src/foo.h" instead of // "/tmp/build/foo.h" if (const DirectoryEntry *Dir = SM.getFileManager().getDirectory( llvm::sys::path::parent_path(AbsolutePath.str()))) { StringRef DirName = SM.getFileManager().getCanonicalName(Dir); SmallString<128> AbsoluteFilename; llvm::sys::path::append(AbsoluteFilename, DirName, llvm::sys::path::filename(AbsolutePath.str())); AbsolutePath = AbsoluteFilename; } } else if (!Opts.FallbackDir.empty()) { llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath); llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); } std::string ErrMsg; for (const auto &Scheme : Opts.URISchemes) { auto U = URI::create(AbsolutePath, Scheme); if (U) return U->toString(); ErrMsg += llvm::toString(U.takeError()) + "\n"; } log(llvm::Twine("Failed to create an URI for file ") + AbsolutePath + ": " + ErrMsg); return llvm::None; } // "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no qualifier. std::pair splitQualifiedName(llvm::StringRef QName) { assert(!QName.startswith("::") && "Qualified names should not start with ::"); size_t Pos = QName.rfind("::"); if (Pos == llvm::StringRef::npos) return {StringRef(), QName}; return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; } bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx, const SymbolCollector::Options &Opts) { using namespace clang::ast_matchers; if (ND->isImplicit()) return true; // Skip anonymous declarations, e.g (anonymous enum/class/struct). if (ND->getDeclName().isEmpty()) return true; // FIXME: figure out a way to handle internal linkage symbols (e.g. static // variables, function) defined in the .cc files. Also we skip the symbols // in anonymous namespace as the qualifier names of these symbols are like // `foo::::bar`, which need a special handling. // In real world projects, we have a relatively large set of header files // that define static variables (like "static const int A = 1;"), we still // want to collect these symbols, although they cause potential ODR // violations. if (ND->isInAnonymousNamespace()) return true; // We only want: // * symbols in namespaces or translation unit scopes (e.g. no class // members) // * enum constants in unscoped enum decl (e.g. "red" in "enum {red};") auto InTopLevelScope = hasDeclContext( anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl())); // Don't index template specializations. auto IsSpecialization = anyOf(functionDecl(isExplicitTemplateSpecialization()), cxxRecordDecl(isExplicitTemplateSpecialization()), varDecl(isExplicitTemplateSpecialization())); if (match(decl(allOf(unless(isExpansionInMainFile()), anyOf(InTopLevelScope, hasDeclContext(enumDecl(InTopLevelScope, unless(isScoped())))), unless(IsSpecialization))), *ND, *ASTCtx) .empty()) return true; return false; } // We only collect #include paths for symbols that are suitable for global code // completion, except for namespaces since #include path for a namespace is hard // to define. bool shouldCollectIncludePath(index::SymbolKind Kind) { using SK = index::SymbolKind; switch (Kind) { case SK::Macro: case SK::Enum: case SK::Struct: case SK::Class: case SK::Union: case SK::TypeAlias: case SK::Using: case SK::Function: case SK::Variable: case SK::EnumConstant: return true; default: return false; } } /// Gets a canonical include (URI of the header or
or "header") for /// header of \p Loc. /// Returns None if fails to get include header for \p Loc. /// FIXME: we should handle .inc files whose symbols are expected be exported by /// their containing headers. llvm::Optional getIncludeHeader(llvm::StringRef QName, const SourceManager &SM, SourceLocation Loc, const SymbolCollector::Options &Opts) { llvm::StringRef FilePath = SM.getFilename(Loc); if (FilePath.empty()) return llvm::None; if (Opts.Includes) { llvm::StringRef Mapped = Opts.Includes->mapHeader(FilePath, QName); if (Mapped != FilePath) return (Mapped.startswith("<") || Mapped.startswith("\"")) ? Mapped.str() : ("\"" + Mapped + "\"").str(); } return toURI(SM, SM.getFilename(Loc), Opts); } // Return the symbol location of the given declaration `D`. // // For symbols defined inside macros: // * use expansion location, if the symbol is formed via macro concatenation. // * use spelling location, otherwise. llvm::Optional getSymbolLocation( const NamedDecl &D, SourceManager &SM, const SymbolCollector::Options &Opts, const clang::LangOptions &LangOpts, std::string &FileURIStorage) { SourceLocation NameLoc = findNameLoc(&D); auto U = toURI(SM, SM.getFilename(NameLoc), Opts); if (!U) return llvm::None; FileURIStorage = std::move(*U); SymbolLocation Result; Result.FileURI = FileURIStorage; Result.StartOffset = SM.getFileOffset(NameLoc); Result.EndOffset = Result.StartOffset + clang::Lexer::MeasureTokenLength( NameLoc, SM, LangOpts); return std::move(Result); } // Checks whether \p ND is a definition of a TagDecl (class/struct/enum/union) // in a header file, in which case clangd would prefer to use ND as a canonical // declaration. // FIXME: handle symbol types that are not TagDecl (e.g. functions), if using // the the first seen declaration as canonical declaration is not a good enough // heuristic. bool isPreferredDeclaration(const NamedDecl &ND, index::SymbolRoleSet Roles) { using namespace clang::ast_matchers; return (Roles & static_cast(index::SymbolRole::Definition)) && llvm::isa(&ND) && match(decl(isExpansionInMainFile()), ND, ND.getASTContext()).empty(); } } // namespace SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} void SymbolCollector::initialize(ASTContext &Ctx) { ASTCtx = &Ctx; CompletionAllocator = std::make_shared(); CompletionTUInfo = llvm::make_unique(CompletionAllocator); } // Always return true to continue indexing. bool SymbolCollector::handleDeclOccurence( const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) { assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); + assert(CompletionAllocator && CompletionTUInfo); + const NamedDecl *ND = llvm::dyn_cast(D); + if (!ND) + return true; + + // Mark D as referenced if this is a reference coming from the main file. + // D may not be an interesting symbol, but it's cheaper to check at the end. + if (Opts.CountReferences && + (Roles & static_cast(index::SymbolRole::Reference)) && + ASTCtx->getSourceManager().getMainFileID() == FID) + ReferencedDecls.insert(ND); - // FIXME: collect all symbol references. + // Don't continue indexing if this is a mere reference. if (!(Roles & static_cast(index::SymbolRole::Declaration) || Roles & static_cast(index::SymbolRole::Definition))) return true; + if (shouldFilterDecl(ND, ASTCtx, Opts)) + return true; - assert(CompletionAllocator && CompletionTUInfo); + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(ND, USR)) + return true; + SymbolID ID(USR); - if (const NamedDecl *ND = llvm::dyn_cast(D)) { - if (shouldFilterDecl(ND, ASTCtx, Opts)) - return true; + const NamedDecl &OriginalDecl = *cast(ASTNode.OrigD); + const Symbol *BasicSymbol = Symbols.find(ID); + if (!BasicSymbol) // Regardless of role, ND is the canonical declaration. + BasicSymbol = addDeclaration(*ND, std::move(ID)); + else if (isPreferredDeclaration(OriginalDecl, Roles)) + // If OriginalDecl is preferred, replace the existing canonical + // declaration (e.g. a class forward declaration). There should be at most + // one duplicate as we expect to see only one preferred declaration per + // TU, because in practice they are definitions. + BasicSymbol = addDeclaration(OriginalDecl, std::move(ID)); + + if (Roles & static_cast(index::SymbolRole::Definition)) + addDefinition(OriginalDecl, *BasicSymbol); + return true; +} + +void SymbolCollector::finish() { + // At the end of the TU, add 1 to the refcount of the ReferencedDecls. + for (const auto *ND : ReferencedDecls) { llvm::SmallString<128> USR; - if (index::generateUSRForDecl(ND, USR)) - return true; - - const NamedDecl &OriginalDecl = *cast(ASTNode.OrigD); - auto ID = SymbolID(USR); - const Symbol *BasicSymbol = Symbols.find(ID); - if (!BasicSymbol) // Regardless of role, ND is the canonical declaration. - BasicSymbol = addDeclaration(*ND, std::move(ID)); - else if (isPreferredDeclaration(OriginalDecl, Roles)) - // If OriginalDecl is preferred, replace the existing canonical - // declaration (e.g. a class forward declaration). There should be at most - // one duplicate as we expect to see only one preferred declaration per - // TU, because in practice they are definitions. - BasicSymbol = addDeclaration(OriginalDecl, std::move(ID)); - - if (Roles & static_cast(index::SymbolRole::Definition)) - addDefinition(OriginalDecl, *BasicSymbol); + if (!index::generateUSRForDecl(ND, USR)) + if (const auto *S = Symbols.find(SymbolID(USR))) { + Symbol Inc = *S; + ++Inc.References; + Symbols.insert(Inc); + } } - return true; + ReferencedDecls.clear(); } const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID) { auto &SM = ND.getASTContext().getSourceManager(); std::string QName; llvm::raw_string_ostream OS(QName); PrintingPolicy Policy(ASTCtx->getLangOpts()); // Note that inline namespaces are treated as transparent scopes. This // reflects the way they're most commonly used for lookup. Ideally we'd // include them, but at query time it's hard to find all the inline // namespaces to query: the preamble doesn't have a dedicated list. Policy.SuppressUnwrittenScope = true; ND.printQualifiedName(OS, Policy); OS.flush(); Symbol S; S.ID = std::move(ID); std::tie(S.Scope, S.Name) = splitQualifiedName(QName); S.SymInfo = index::getSymbolInfo(&ND); std::string FileURI; if (auto DeclLoc = getSymbolLocation(ND, SM, Opts, ASTCtx->getLangOpts(), FileURI)) S.CanonicalDeclaration = *DeclLoc; // Add completion info. // FIXME: we may want to choose a different redecl, or combine from several. assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); CodeCompletionResult SymbolCompletion(&ND, 0); const auto *CCS = SymbolCompletion.CreateCodeCompletionString( *ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator, *CompletionTUInfo, /*IncludeBriefComments*/ true); std::string Label; std::string SnippetInsertText; std::string IgnoredLabel; std::string PlainInsertText; getLabelAndInsertText(*CCS, &Label, &SnippetInsertText, /*EnableSnippets=*/true); getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText, /*EnableSnippets=*/false); std::string FilterText = getFilterText(*CCS); std::string Documentation = getDocumentation(*CCS); std::string CompletionDetail = getDetail(*CCS); std::string Include; if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { // Use the expansion location to get the #include header since this is // where the symbol is exposed. if (auto Header = getIncludeHeader( QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts)) Include = std::move(*Header); } S.CompletionFilterText = FilterText; S.CompletionLabel = Label; S.CompletionPlainInsertText = PlainInsertText; S.CompletionSnippetInsertText = SnippetInsertText; Symbol::Details Detail; Detail.Documentation = Documentation; Detail.CompletionDetail = CompletionDetail; Detail.IncludeHeader = Include; S.Detail = &Detail; Symbols.insert(S); return Symbols.find(S.ID); } void SymbolCollector::addDefinition(const NamedDecl &ND, const Symbol &DeclSym) { if (DeclSym.Definition) return; // If we saw some forward declaration, we end up copying the symbol. // This is not ideal, but avoids duplicating the "is this a definition" check // in clang::index. We should only see one definition. Symbol S = DeclSym; std::string FileURI; if (auto DefLoc = getSymbolLocation(ND, ND.getASTContext().getSourceManager(), Opts, ASTCtx->getLangOpts(), FileURI)) S.Definition = *DefLoc; Symbols.insert(S); } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h index c18f74a8362a..7237e391ba03 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -1,80 +1,86 @@ //===--- SymbolCollector.h ---------------------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "CanonicalIncludes.h" #include "Index.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Sema/CodeCompleteConsumer.h" namespace clang { namespace clangd { /// \brief Collect top-level symbols from an AST. These are symbols defined /// immediately inside a namespace or a translation unit scope. For example, /// symbols in classes or functions are not collected. Note that this only /// collects symbols that declared in at least one file that is not a main /// file (i.e. the source file corresponding to a TU). These are symbols that /// can be imported by other files by including the file where symbols are /// declared. /// /// 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: struct Options { /// When symbol paths cannot be resolved to absolute paths (e.g. files in /// VFS that does not have absolute path), combine the fallback directory /// with symbols' paths to get absolute paths. This must be an absolute /// path. std::string FallbackDir; /// Specifies URI schemes that can be used to generate URIs for file paths /// in symbols. The list of schemes will be tried in order until a working /// scheme is found. If no scheme works, symbol location will be dropped. std::vector URISchemes = {"file"}; bool CollectIncludePath = false; /// If set, this is used to map symbol #include path to a potentially /// different #include path. const CanonicalIncludes *Includes = nullptr; + // Populate the Symbol.References field. + bool CountReferences = false; }; SymbolCollector(Options Opts); void initialize(ASTContext &Ctx) override; void setPreprocessor(std::shared_ptr PP) override { this->PP = std::move(PP); } bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override; SymbolSlab takeSymbols() { return std::move(Symbols).build(); } + void finish() override; + private: const Symbol *addDeclaration(const NamedDecl &, SymbolID); void addDefinition(const NamedDecl &, const Symbol &DeclSymbol); // All Symbols collected from the AST. SymbolSlab::Builder Symbols; ASTContext *ASTCtx; std::shared_ptr PP; std::shared_ptr CompletionAllocator; std::unique_ptr CompletionTUInfo; Options Opts; + // Decls referenced from the current TU, flushed on finish(). + llvm::DenseSet ReferencedDecls; }; } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/SymbolYAML.cpp b/clang-tools-extra/clangd/index/SymbolYAML.cpp index 379cb334d6b0..307b0af5b5cf 100644 --- a/clang-tools-extra/clangd/index/SymbolYAML.cpp +++ b/clang-tools-extra/clangd/index/SymbolYAML.cpp @@ -1,201 +1,202 @@ //===--- SymbolYAML.cpp ------------------------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "SymbolYAML.h" #include "Index.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/Errc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(clang::clangd::Symbol) namespace llvm { namespace yaml { using clang::clangd::Symbol; using clang::clangd::SymbolID; using clang::clangd::SymbolLocation; using clang::index::SymbolInfo; using clang::index::SymbolLanguage; using clang::index::SymbolKind; // Helper to (de)serialize the SymbolID. We serialize it as a hex string. struct NormalizedSymbolID { NormalizedSymbolID(IO &) {} NormalizedSymbolID(IO &, const SymbolID& ID) { llvm::raw_string_ostream OS(HexString); OS << ID; } SymbolID denormalize(IO&) { SymbolID ID; HexString >> ID; return ID; } std::string HexString; }; template <> struct MappingTraits { static void mapping(IO &IO, SymbolLocation &Value) { IO.mapRequired("StartOffset", Value.StartOffset); IO.mapRequired("EndOffset", Value.EndOffset); IO.mapRequired("FileURI", Value.FileURI); } }; template <> struct MappingTraits { static void mapping(IO &io, SymbolInfo &SymInfo) { // FIXME: expose other fields? io.mapRequired("Kind", SymInfo.Kind); io.mapRequired("Lang", SymInfo.Lang); } }; template <> struct MappingTraits { static void mapping(IO &io, Symbol::Details &Detail) { io.mapOptional("Documentation", Detail.Documentation); io.mapOptional("CompletionDetail", Detail.CompletionDetail); io.mapOptional("IncludeHeader", Detail.IncludeHeader); } }; // A YamlIO normalizer for fields of type "const T*" allocated on an arena. // Normalizes to Optional, so traits should be provided for T. template struct ArenaPtr { ArenaPtr(IO &) {} ArenaPtr(IO &, const T *D) { if (D) Opt = *D; } const T *denormalize(IO &IO) { assert(IO.getContext() && "Expecting an arena (as context) to allocate " "data for read symbols."); if (!Opt) return nullptr; return new (*static_cast(IO.getContext())) T(std::move(*Opt)); // Allocate a copy of Opt on the arena. } llvm::Optional Opt; }; template <> struct MappingTraits { static void mapping(IO &IO, Symbol &Sym) { MappingNormalization NSymbolID(IO, Sym.ID); MappingNormalization, const Symbol::Details *> NDetail(IO, Sym.Detail); IO.mapRequired("ID", NSymbolID->HexString); IO.mapRequired("Name", Sym.Name); IO.mapRequired("Scope", Sym.Scope); IO.mapRequired("SymInfo", Sym.SymInfo); IO.mapOptional("CanonicalDeclaration", Sym.CanonicalDeclaration, SymbolLocation()); IO.mapOptional("Definition", Sym.Definition, SymbolLocation()); + IO.mapOptional("References", Sym.References, 0u); IO.mapRequired("CompletionLabel", Sym.CompletionLabel); IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText); IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText); IO.mapOptional("CompletionSnippetInsertText", Sym.CompletionSnippetInsertText); IO.mapOptional("Detail", NDetail->Opt); } }; template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, SymbolLanguage &Value) { IO.enumCase(Value, "C", SymbolLanguage::C); IO.enumCase(Value, "Cpp", SymbolLanguage::CXX); IO.enumCase(Value, "ObjC", SymbolLanguage::ObjC); IO.enumCase(Value, "Swift", SymbolLanguage::Swift); } }; template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, SymbolKind &Value) { #define DEFINE_ENUM(name) IO.enumCase(Value, #name, SymbolKind::name) DEFINE_ENUM(Unknown); DEFINE_ENUM(Function); DEFINE_ENUM(Module); DEFINE_ENUM(Namespace); DEFINE_ENUM(NamespaceAlias); DEFINE_ENUM(Macro); DEFINE_ENUM(Enum); DEFINE_ENUM(Struct); DEFINE_ENUM(Class); DEFINE_ENUM(Protocol); DEFINE_ENUM(Extension); DEFINE_ENUM(Union); DEFINE_ENUM(TypeAlias); DEFINE_ENUM(Function); DEFINE_ENUM(Variable); DEFINE_ENUM(Field); DEFINE_ENUM(EnumConstant); DEFINE_ENUM(InstanceMethod); DEFINE_ENUM(ClassMethod); DEFINE_ENUM(StaticMethod); DEFINE_ENUM(InstanceProperty); DEFINE_ENUM(ClassProperty); DEFINE_ENUM(StaticProperty); DEFINE_ENUM(Constructor); DEFINE_ENUM(Destructor); DEFINE_ENUM(ConversionFunction); DEFINE_ENUM(Parameter); DEFINE_ENUM(Using); #undef DEFINE_ENUM } }; } // namespace yaml } // namespace llvm namespace clang { namespace clangd { SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent) { // Store data of pointer fields (excl. `StringRef`) like `Detail`. llvm::BumpPtrAllocator Arena; llvm::yaml::Input Yin(YAMLContent, &Arena); std::vector S; Yin >> S; SymbolSlab::Builder Syms; for (auto &Sym : S) Syms.insert(Sym); return std::move(Syms).build(); } Symbol SymbolFromYAML(llvm::yaml::Input &Input, llvm::BumpPtrAllocator &Arena) { // We could grab Arena out of Input, but it'd be a huge hazard for callers. assert(Input.getContext() == &Arena); Symbol S; Input >> S; return S; } void SymbolsToYAML(const SymbolSlab& Symbols, llvm::raw_ostream &OS) { llvm::yaml::Output Yout(OS); for (Symbol S : Symbols) // copy: Yout<< requires mutability. Yout << S; } std::string SymbolToYAML(Symbol Sym) { std::string Str; llvm::raw_string_ostream OS(Str); llvm::yaml::Output Yout(OS); Yout << Sym; return OS.str(); } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/unittests/clangd/IndexTests.cpp b/clang-tools-extra/unittests/clangd/IndexTests.cpp index 20eb4527d9f8..5e5ddbfb86e9 100644 --- a/clang-tools-extra/unittests/clangd/IndexTests.cpp +++ b/clang-tools-extra/unittests/clangd/IndexTests.cpp @@ -1,272 +1,275 @@ //===-- IndexTests.cpp -------------------------------*- C++ -*-----------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "index/Index.h" #include "index/MemIndex.h" #include "index/Merge.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::UnorderedElementsAre; using testing::Pointee; namespace clang { namespace clangd { namespace { Symbol symbol(llvm::StringRef QName) { Symbol Sym; Sym.ID = SymbolID(QName.str()); size_t Pos = QName.rfind("::"); if (Pos == llvm::StringRef::npos) { Sym.Name = QName; Sym.Scope = ""; } else { Sym.Name = QName.substr(Pos + 2); Sym.Scope = QName.substr(0, Pos); } return Sym; } MATCHER_P(Named, N, "") { return arg.Name == N; } TEST(SymbolSlab, FindAndIterate) { SymbolSlab::Builder B; B.insert(symbol("Z")); B.insert(symbol("Y")); B.insert(symbol("X")); EXPECT_EQ(nullptr, B.find(SymbolID("W"))); for (const char *Sym : {"X", "Y", "Z"}) EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(Named(Sym))); SymbolSlab S = std::move(B).build(); EXPECT_THAT(S, UnorderedElementsAre(Named("X"), Named("Y"), Named("Z"))); EXPECT_EQ(S.end(), S.find(SymbolID("W"))); for (const char *Sym : {"X", "Y", "Z"}) EXPECT_THAT(*S.find(SymbolID(Sym)), Named(Sym)); } struct SlabAndPointers { SymbolSlab Slab; std::vector Pointers; }; // Create a slab of symbols with the given qualified names as both IDs and // names. The life time of the slab is managed by the returned shared pointer. // If \p WeakSymbols is provided, it will be pointed to the managed object in // the returned shared pointer. std::shared_ptr> generateSymbols(std::vector QualifiedNames, std::weak_ptr *WeakSymbols = nullptr) { SymbolSlab::Builder Slab; for (llvm::StringRef QName : QualifiedNames) Slab.insert(symbol(QName)); auto Storage = std::make_shared(); Storage->Slab = std::move(Slab).build(); for (const auto &Sym : Storage->Slab) Storage->Pointers.push_back(&Sym); if (WeakSymbols) *WeakSymbols = Storage; auto *Pointers = &Storage->Pointers; return {std::move(Storage), Pointers}; } // Create a slab of symbols with IDs and names [Begin, End], otherwise identical // to the `generateSymbols` above. std::shared_ptr> generateNumSymbols(int Begin, int End, std::weak_ptr *WeakSymbols = nullptr) { std::vector Names; for (int i = Begin; i <= End; i++) Names.push_back(std::to_string(i)); return generateSymbols(Names, WeakSymbols); } std::vector match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete = nullptr) { std::vector Matches; bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) { Matches.push_back( (Sym.Scope + (Sym.Scope.empty() ? "" : "::") + Sym.Name).str()); }); if (Incomplete) *Incomplete = IsIncomplete; return Matches; } TEST(MemIndexTest, MemIndexSymbolsRecycled) { MemIndex I; std::weak_ptr Symbols; I.build(generateNumSymbols(0, 10, &Symbols)); FuzzyFindRequest Req; Req.Query = "7"; EXPECT_THAT(match(I, Req), UnorderedElementsAre("7")); EXPECT_FALSE(Symbols.expired()); // Release old symbols. I.build(generateNumSymbols(0, 0)); EXPECT_TRUE(Symbols.expired()); } TEST(MemIndexTest, MemIndexDeduplicate) { auto Symbols = generateNumSymbols(0, 10); // Inject some duplicates and make sure we only match the same symbol once. auto Sym = symbol("7"); Symbols->push_back(&Sym); Symbols->push_back(&Sym); Symbols->push_back(&Sym); FuzzyFindRequest Req; Req.Query = "7"; MemIndex I; I.build(std::move(Symbols)); auto Matches = match(I, Req); EXPECT_EQ(Matches.size(), 1u); } TEST(MemIndexTest, MemIndexLimitedNumMatches) { MemIndex I; I.build(generateNumSymbols(0, 100)); FuzzyFindRequest Req; Req.Query = "5"; Req.MaxCandidateCount = 3; bool Incomplete; auto Matches = match(I, Req, &Incomplete); EXPECT_EQ(Matches.size(), Req.MaxCandidateCount); EXPECT_TRUE(Incomplete); } TEST(MemIndexTest, FuzzyMatch) { MemIndex I; I.build( generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"})); FuzzyFindRequest Req; Req.Query = "lol"; Req.MaxCandidateCount = 2; EXPECT_THAT(match(I, Req), UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); } TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { MemIndex I; I.build(generateSymbols({"a::y1", "b::y2", "y3"})); FuzzyFindRequest Req; Req.Query = "y"; EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); } TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { MemIndex I; I.build(generateSymbols({"a::y1", "b::y2", "y3"})); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {""}; EXPECT_THAT(match(I, Req), UnorderedElementsAre("y3")); } TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { MemIndex I; I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"})); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a"}; EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2")); } TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { MemIndex I; I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"})); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a", "b"}; EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); } TEST(MemIndexTest, NoMatchNestedScopes) { MemIndex I; I.build(generateSymbols({"a::y1", "a::b::y2"})); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a"}; EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1")); } TEST(MemIndexTest, IgnoreCases) { MemIndex I; I.build(generateSymbols({"ns::ABC", "ns::abc"})); FuzzyFindRequest Req; Req.Query = "AB"; Req.Scopes = {"ns"}; EXPECT_THAT(match(I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); } TEST(MergeTest, MergeIndex) { MemIndex I, J; I.build(generateSymbols({"ns::A", "ns::B"})); J.build(generateSymbols({"ns::B", "ns::C"})); FuzzyFindRequest Req; Req.Scopes = {"ns"}; EXPECT_THAT(match(*mergeIndex(&I, &J), Req), UnorderedElementsAre("ns::A", "ns::B", "ns::C")); } TEST(MergeTest, Merge) { Symbol L, R; L.ID = R.ID = SymbolID("hello"); L.Name = R.Name = "Foo"; // same in both L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs R.CanonicalDeclaration.FileURI = "file:///right.h"; + L.References = 1; + R.References = 2; L.CompletionPlainInsertText = "f00"; // present in left only R.CompletionSnippetInsertText = "f0{$1:0}"; // present in right only Symbol::Details DetL, DetR; DetL.CompletionDetail = "DetL"; DetR.CompletionDetail = "DetR"; DetR.Documentation = "--doc--"; L.Detail = &DetL; R.Detail = &DetR; Symbol::Details Scratch; Symbol M = mergeSymbol(L, R, &Scratch); EXPECT_EQ(M.Name, "Foo"); EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:///left.h"); + EXPECT_EQ(M.References, 3u); EXPECT_EQ(M.CompletionPlainInsertText, "f00"); EXPECT_EQ(M.CompletionSnippetInsertText, "f0{$1:0}"); ASSERT_TRUE(M.Detail); EXPECT_EQ(M.Detail->CompletionDetail, "DetL"); EXPECT_EQ(M.Detail->Documentation, "--doc--"); } TEST(MergeTest, PreferSymbolWithDefn) { Symbol L, R; Symbol::Details Scratch; L.ID = R.ID = SymbolID("hello"); L.CanonicalDeclaration.FileURI = "file:/left.h"; R.CanonicalDeclaration.FileURI = "file:/right.h"; L.CompletionPlainInsertText = "left-insert"; R.CompletionPlainInsertText = "right-insert"; Symbol M = mergeSymbol(L, R, &Scratch); EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/left.h"); EXPECT_EQ(M.Definition.FileURI, ""); EXPECT_EQ(M.CompletionPlainInsertText, "left-insert"); R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored. M = mergeSymbol(L, R, &Scratch); EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/right.h"); EXPECT_EQ(M.Definition.FileURI, "file:/right.cpp"); EXPECT_EQ(M.CompletionPlainInsertText, "right-insert"); } } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp b/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp index c6a155a4a369..76eace528f43 100644 --- a/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp +++ b/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp @@ -1,644 +1,670 @@ //===-- 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 "Annotations.h" #include "TestFS.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/FileSystemOptions.h" #include "clang/Basic/VirtualFileSystem.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Index/IndexingAction.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 "gmock/gmock.h" #include "gtest/gtest.h" #include #include using testing::AllOf; using testing::Eq; using testing::Field; using testing::Not; using testing::UnorderedElementsAre; using testing::UnorderedElementsAreArray; // GMock helpers for matching Symbol. MATCHER_P(Labeled, Label, "") { return arg.CompletionLabel == Label; } MATCHER(HasDetail, "") { return arg.Detail; } MATCHER_P(Detail, D, "") { return arg.Detail && arg.Detail->CompletionDetail == D; } MATCHER_P(Doc, D, "") { return arg.Detail && arg.Detail->Documentation == D; } MATCHER_P(Plain, Text, "") { return arg.CompletionPlainInsertText == Text; } MATCHER_P(Snippet, S, "") { return arg.CompletionSnippetInsertText == S; } MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; } MATCHER_P(DefURI, P, "") { return arg.Definition.FileURI == P; } MATCHER_P(IncludeHeader, P, "") { return arg.Detail && arg.Detail->IncludeHeader == P; } MATCHER_P(DeclRange, Offsets, "") { return arg.CanonicalDeclaration.StartOffset == Offsets.first && arg.CanonicalDeclaration.EndOffset == Offsets.second; } MATCHER_P(DefRange, Offsets, "") { return arg.Definition.StartOffset == Offsets.first && arg.Definition.EndOffset == Offsets.second; } +MATCHER_P(Refs, R, "") { return int(arg.References) == R; } namespace clang { namespace clangd { namespace { class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: SymbolIndexActionFactory(SymbolCollector::Options COpts, CommentHandler *PragmaHandler) : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} clang::FrontendAction *create() override { class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, const index::IndexingOptions &Opts, CommentHandler *PragmaHandler) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), PragmaHandler(PragmaHandler) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { if (PragmaHandler) CI.getPreprocessor().addCommentHandler(PragmaHandler); return WrapperFrontendAction::CreateASTConsumer(CI, InFile); } private: index::IndexingOptions IndexOpts; CommentHandler *PragmaHandler; }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); return new WrappedIndexAction(Collector, std::move(IndexOpts), PragmaHandler); } std::shared_ptr Collector; SymbolCollector::Options COpts; CommentHandler *PragmaHandler; }; class SymbolCollectorTest : public ::testing::Test { public: SymbolCollectorTest() : InMemoryFileSystem(new vfs::InMemoryFileSystem), TestHeaderName(testPath("symbol.h")), TestFileName(testPath("symbol.cc")) { TestHeaderURI = URI::createFile(TestHeaderName).toString(); TestFileURI = URI::createFile(TestFileName).toString(); } bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode, const std::vector &ExtraArgs = {}) { llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); auto Factory = llvm::make_unique( CollectorOpts, PragmaHandler.get()); std::vector Args = {"symbol_collector", "-fsyntax-only", "-std=c++11", "-include", TestHeaderName, TestFileName}; Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); tooling::ToolInvocation Invocation( Args, Factory->create(), Files.get(), std::make_shared()); InMemoryFileSystem->addFile(TestHeaderName, 0, llvm::MemoryBuffer::getMemBuffer(HeaderCode)); InMemoryFileSystem->addFile(TestFileName, 0, llvm::MemoryBuffer::getMemBuffer(MainCode)); Invocation.run(); Symbols = Factory->Collector->takeSymbols(); return true; } protected: llvm::IntrusiveRefCntPtr InMemoryFileSystem; std::string TestHeaderName; std::string TestHeaderURI; std::string TestFileName; std::string TestFileURI; SymbolSlab Symbols; SymbolCollector::Options CollectorOpts; std::unique_ptr PragmaHandler; }; TEST_F(SymbolCollectorTest, CollectSymbols) { const std::string Header = R"( class Foo { void f(); }; void f1(); inline void f2() {} static const int KInt = 2; const char* kStr = "123"; namespace { void ff() {} // ignore } void f1() {} namespace foo { // Type alias typedef int int32; using int32_t = int32; // Variable int v1; // Namespace namespace bar { int v2; } // Namespace alias namespace baz = bar; // FIXME: using declaration is not supported as the IndexAction will ignore // implicit declarations (the implicit using shadow declaration) by default, // and there is no way to customize this behavior at the moment. using bar::v2; } // namespace foo )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAreArray( {QName("Foo"), QName("f1"), QName("f2"), QName("KInt"), QName("kStr"), QName("foo"), QName("foo::bar"), QName("foo::int32"), QName("foo::int32_t"), QName("foo::v1"), QName("foo::bar::v2"), QName("foo::baz")})); } TEST_F(SymbolCollectorTest, Template) { Annotations Header(R"( // Template is indexed, specialization and instantiation is not. - template struct [[Tmpl]] {T x = 0}; + template struct [[Tmpl]] {T x = 0;}; template <> struct Tmpl {}; extern template struct Tmpl; template struct Tmpl; )"); runSymbolCollector(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAreArray({AllOf( QName("Tmpl"), DeclRange(Header.offsetRange()))})); } TEST_F(SymbolCollectorTest, Locations) { Annotations Header(R"cpp( // Declared in header, defined in main. extern int $xdecl[[X]]; class $clsdecl[[Cls]]; void $printdecl[[print]](); // Declared in header, defined nowhere. extern int $zdecl[[Z]]; )cpp"); Annotations Main(R"cpp( int $xdef[[X]] = 42; class $clsdef[[Cls]] {}; void $printdef[[print]]() {} // Declared/defined in main only. int Y; )cpp"); runSymbolCollector(Header.code(), Main.code()); EXPECT_THAT( Symbols, UnorderedElementsAre( AllOf(QName("X"), DeclRange(Header.offsetRange("xdecl")), DefRange(Main.offsetRange("xdef"))), AllOf(QName("Cls"), DeclRange(Header.offsetRange("clsdecl")), DefRange(Main.offsetRange("clsdef"))), AllOf(QName("print"), DeclRange(Header.offsetRange("printdecl")), DefRange(Main.offsetRange("printdef"))), AllOf(QName("Z"), DeclRange(Header.offsetRange("zdecl"))))); } +TEST_F(SymbolCollectorTest, References) { + const std::string Header = R"( + class W; + class X {}; + class Y; + class Z {}; // not used anywhere + Y* y = nullptr; // used in header doesn't count + )"; + const std::string Main = R"( + W* w = nullptr; + W* w2 = nullptr; // only one usage counts + X x(); + class V; + V* v = nullptr; // Used, but not eligible for indexing. + class Y{}; // definition doesn't count as a reference + )"; + CollectorOpts.CountReferences = true; + runSymbolCollector(Header, Main); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("W"), Refs(1)), + AllOf(QName("X"), Refs(1)), + AllOf(QName("Y"), Refs(0)), + AllOf(QName("Z"), Refs(0)), QName("y"))); +} + TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { TestHeaderName = "x.h"; TestFileName = "x.cpp"; TestHeaderURI = URI::createFile(testPath(TestHeaderName)).toString(); CollectorOpts.FallbackDir = testRoot(); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } #ifndef LLVM_ON_WIN32 TEST_F(SymbolCollectorTest, CustomURIScheme) { // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest"); TestHeaderName = testPath("test-root/x.h"); TestFileName = testPath("test-root/x.cpp"); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("unittest:x.h")))); } #endif TEST_F(SymbolCollectorTest, InvalidURIScheme) { // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes = {"invalid"}; runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("")))); } TEST_F(SymbolCollectorTest, FallbackToFileURI) { // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes = {"invalid", "file"}; runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, IncludeEnums) { const std::string Header = R"( enum { Red }; enum Color { Green }; enum class Color2 { Yellow // ignore }; namespace ns { enum { Black }; } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Red"), QName("Color"), QName("Green"), QName("Color2"), QName("ns"), QName("ns::Black"))); } TEST_F(SymbolCollectorTest, IgnoreNamelessSymbols) { const std::string Header = R"( struct { int a; } Foo; )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"))); } TEST_F(SymbolCollectorTest, SymbolFormedFromMacro) { Annotations Header(R"( #define FF(name) \ class name##_Test {}; $expansion[[FF]](abc); #define FF2() \ class $spelling[[Test]] {}; FF2(); )"); runSymbolCollector(Header.code(), /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( AllOf(QName("abc_Test"), DeclRange(Header.offsetRange("expansion")), DeclURI(TestHeaderURI)), AllOf(QName("Test"), DeclRange(Header.offsetRange("spelling")), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { Annotations Header(R"( #ifdef NAME class $expansion[[NAME]] {}; #endif )"); runSymbolCollector(Header.code(), /*Main=*/"", /*ExtraArgs=*/{"-DNAME=name"}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( QName("name"), DeclRange(Header.offsetRange("expansion")), DeclURI(TestHeaderURI)))); } TEST_F(SymbolCollectorTest, IgnoreSymbolsInMainFile) { const std::string Header = R"( class Foo {}; void f1(); inline void f2() {} )"; const std::string Main = R"( namespace { void ff() {} // ignore } void main_f() {} // ignore void f1() {} )"; runSymbolCollector(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), QName("f1"), QName("f2"))); } TEST_F(SymbolCollectorTest, IgnoreClassMembers) { const std::string Header = R"( class Foo { void f() {} void g(); static void sf() {} static void ssf(); static int x; }; )"; const std::string Main = R"( void Foo::g() {} void Foo::ssf() {} )"; runSymbolCollector(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"))); } TEST_F(SymbolCollectorTest, Scopes) { const std::string Header = R"( namespace na { class Foo {}; namespace nb { class Bar {}; } } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("na::nb"), QName("na::Foo"), QName("na::nb::Bar"))); } TEST_F(SymbolCollectorTest, ExternC) { const std::string Header = R"( extern "C" { class Foo {}; } namespace na { extern "C" { class Bar {}; } } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("Foo"), QName("na::Bar"))); } TEST_F(SymbolCollectorTest, SkipInlineNamespace) { const std::string Header = R"( namespace na { inline namespace nb { class Foo {}; } } namespace na { // This is still inlined. namespace nb { class Bar {}; } } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("na::nb"), QName("na::Foo"), QName("na::Bar"))); } TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { const std::string Header = R"( namespace nx { /// Foo comment. int ff(int x, double y) { return 0; } } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("nx"), AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Detail("int"), Doc("Foo comment.")))); } TEST_F(SymbolCollectorTest, PlainAndSnippet) { const std::string Header = R"( namespace nx { void f() {} int ff(int x, double y) { return 0; } } )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( QName("nx"), AllOf(QName("nx::f"), Labeled("f()"), Plain("f"), Snippet("f()")), AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Plain("ff"), Snippet("ff(${1:int x}, ${2:double y})")))); } TEST_F(SymbolCollectorTest, YAMLConversions) { const std::string YAML1 = R"( --- ID: 057557CEBF6E6B2DD437FBF60CC58F352D1DF856 Name: 'Foo1' Scope: 'clang::' SymInfo: Kind: Function Lang: Cpp CanonicalDeclaration: StartOffset: 0 EndOffset: 1 FileURI: file:///path/foo.h CompletionLabel: 'Foo1-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' Detail: Documentation: 'Foo doc' CompletionDetail: 'int' ... )"; const std::string YAML2 = R"( --- ID: 057557CEBF6E6B2DD437FBF60CC58F352D1DF858 Name: 'Foo2' Scope: 'clang::' SymInfo: Kind: Function Lang: Cpp CanonicalDeclaration: StartOffset: 10 EndOffset: 12 FileURI: file:///path/bar.h CompletionLabel: 'Foo2-label' CompletionFilterText: 'filter' CompletionPlainInsertText: 'plain' CompletionSnippetInsertText: 'snippet' ... )"; auto Symbols1 = SymbolsFromYAML(YAML1); EXPECT_THAT(Symbols1, UnorderedElementsAre(AllOf( QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"), Detail("int"), DeclURI("file:///path/foo.h")))); auto Symbols2 = SymbolsFromYAML(YAML2); EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf( QName("clang::Foo2"), Labeled("Foo2-label"), Not(HasDetail()), DeclURI("file:///path/bar.h")))); std::string ConcatenatedYAML; { llvm::raw_string_ostream OS(ConcatenatedYAML); SymbolsToYAML(Symbols1, OS); SymbolsToYAML(Symbols2, OS); } auto ConcatenatedSymbols = SymbolsFromYAML(ConcatenatedYAML); EXPECT_THAT(ConcatenatedSymbols, UnorderedElementsAre(QName("clang::Foo1"), QName("clang::Foo2"))); } TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { CollectorOpts.CollectIncludePath = true; runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), IncludeHeader(TestHeaderURI)))); } #ifndef LLVM_ON_WIN32 TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); CollectorOpts.Includes = &Includes; // bits/basic_string.h$ should be mapped to TestHeaderName = "/nasty/bits/basic_string.h"; TestFileName = "/nasty/bits/basic_string.cpp"; TestHeaderURI = URI::createFile(TestHeaderName).toString(); runSymbolCollector("class string {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), DeclURI(TestHeaderURI), IncludeHeader("")))); } #endif TEST_F(SymbolCollectorTest, STLiosfwd) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); CollectorOpts.Includes = &Includes; // Symbols from should be mapped individually. TestHeaderName = testPath("iosfwd"); TestFileName = testPath("iosfwd.cpp"); std::string Header = R"( namespace std { class no_map {}; class ios {}; class ostream {}; class filebuf {}; } // namespace std )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( QName("std"), AllOf(QName("std::no_map"), IncludeHeader("")), AllOf(QName("std::ios"), IncludeHeader("")), AllOf(QName("std::ostream"), IncludeHeader("")), AllOf(QName("std::filebuf"), IncludeHeader("")))); } TEST_F(SymbolCollectorTest, IWYUPragma) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; PragmaHandler = collectIWYUHeaderMaps(&Includes); CollectorOpts.Includes = &Includes; const std::string Header = R"( // IWYU pragma: private, include the/good/header.h class Foo {}; )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI), IncludeHeader("\"the/good/header.h\"")))); } TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { CollectorOpts.CollectIncludePath = true; Annotations Header(R"( // Forward declarations of TagDecls. class C; struct S; union U; // Canonical declarations. class $cdecl[[C]] {}; struct $sdecl[[S]] {}; union $udecl[[U]] {int x; bool y;}; )"); runSymbolCollector(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("C"), DeclURI(TestHeaderURI), DeclRange(Header.offsetRange("cdecl")), IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), DefRange(Header.offsetRange("cdecl"))), AllOf(QName("S"), DeclURI(TestHeaderURI), DeclRange(Header.offsetRange("sdecl")), IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), DefRange(Header.offsetRange("sdecl"))), AllOf(QName("U"), DeclURI(TestHeaderURI), DeclRange(Header.offsetRange("udecl")), IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI), DefRange(Header.offsetRange("udecl"))))); } TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { CollectorOpts.CollectIncludePath = true; runSymbolCollector(/*Header=*/"class X;", /*Main=*/"class X {};"); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( QName("X"), DeclURI(TestHeaderURI), IncludeHeader(TestHeaderURI), DefURI(TestFileURI)))); } } // namespace } // namespace clangd } // namespace clang