#pragma once #include "indexer.h" #include "serializer.h" #include #include #include struct QueryFile; struct QueryType; struct QueryFunc; struct QueryVar; struct QueryDatabase; using QueryFileId = Id; using QueryTypeId = Id; using QueryFuncId = Id; using QueryVarId = Id; struct IdMap; // There are two sources of reindex updates: the (single) definition of a // symbol has changed, or one of many users of the symbol has changed. // // For simplicitly, if the single definition has changed, we update all of the // associated single-owner definition data. See |Update*DefId|. // // If one of the many symbol users submits an update, we store the update such // that it can be merged with other updates before actually being applied to // the main database. See |MergeableUpdate|. template struct MergeableUpdate { // The type/func/var which is getting new usages. TId id; // Entries to add and remove. std::vector to_add; std::vector to_remove; MergeableUpdate(TId id, std::vector&& to_add) : id(id), to_add(std::move(to_add)) {} MergeableUpdate(TId id, std::vector&& to_add, std::vector&& to_remove) : id(id), to_add(std::move(to_add)), to_remove(std::move(to_remove)) {} }; template void Reflect(TVisitor& visitor, MergeableUpdate& value) { REFLECT_MEMBER_START(); REFLECT_MEMBER(id); REFLECT_MEMBER(to_add); REFLECT_MEMBER(to_remove); REFLECT_MEMBER_END(); } template struct WithUsr { Usr usr; T value; WithUsr(Usr usr, const T& value) : usr(usr), value(value) {} WithUsr(Usr usr, T&& value) : usr(usr), value(std::move(value)) {} }; template void Reflect(TVisitor& visitor, WithUsr& value) { REFLECT_MEMBER_START(); REFLECT_MEMBER(usr); REFLECT_MEMBER(value); REFLECT_MEMBER_END(); } template struct WithFileContent { T value; std::string file_content; WithFileContent(const T& value, const std::string& file_content) : value(value), file_content(file_content) {} }; template void Reflect(TVisitor& visitor, WithFileContent& value) { REFLECT_MEMBER_START(); REFLECT_MEMBER(value); REFLECT_MEMBER(file_content); REFLECT_MEMBER_END(); } struct QueryFamily { using FileId = Id; using FuncId = Id; using TypeId = Id; using VarId = Id; using Range = Reference; }; struct QueryFile { struct Def { std::string path; std::vector args; // Language identifier std::string language; // Includes in the file. std::vector includes; // Outline of the file (ie, for code lens). std::vector outline; // Every symbol found in the file (ie, for goto definition) std::vector all_symbols; // Parts of the file which are disabled. std::vector inactive_regions; // Used by |$cquery/freshenIndex|. std::vector dependencies; }; using DefUpdate = WithFileContent; optional def; Maybe> symbol_idx; explicit QueryFile(const std::string& path) { def = Def(); def->path = path; } }; MAKE_REFLECT_STRUCT(QueryFile::Def, path, args, language, outline, all_symbols, inactive_regions, dependencies); struct QueryType { using Def = TypeDefDefinitionData; using DefUpdate = WithUsr; using DerivedUpdate = MergeableUpdate; using InstancesUpdate = MergeableUpdate; using UsesUpdate = MergeableUpdate; Usr usr; Maybe> symbol_idx; std::forward_list def; std::vector derived; std::vector instances; std::vector uses; explicit QueryType(const Usr& usr) : usr(usr) {} const Def* AnyDef() const { if (def.empty()) return nullptr; return &def.front(); } Def* AnyDef() { if (def.empty()) return nullptr; return &def.front(); } }; struct QueryFunc { using Def = FuncDefDefinitionData; using DefUpdate = WithUsr; using DeclarationsUpdate = MergeableUpdate; using DerivedUpdate = MergeableUpdate; using UsesUpdate = MergeableUpdate; Usr usr; Maybe> symbol_idx; std::forward_list def; std::vector declarations; std::vector derived; std::vector uses; explicit QueryFunc(const Usr& usr) : usr(usr) {} const Def* AnyDef() const { if (def.empty()) return nullptr; return &def.front(); } Def* AnyDef() { if (def.empty()) return nullptr; return &def.front(); } }; struct QueryVar { using Def = VarDefDefinitionData; using DefUpdate = WithUsr; using DeclarationsUpdate = MergeableUpdate; using UsesUpdate = MergeableUpdate; Usr usr; Maybe> symbol_idx; std::forward_list def; std::vector declarations; std::vector uses; explicit QueryVar(const Usr& usr) : usr(usr) {} const Def* AnyDef() const { if (def.empty()) return nullptr; return &def.front(); } Def* AnyDef() { if (def.empty()) return nullptr; return &def.front(); } }; struct IndexUpdate { // Creates a new IndexUpdate based on the delta from previous to current. If // no delta computation should be done just pass null for previous. static IndexUpdate CreateDelta(const IdMap* previous_id_map, const IdMap* current_id_map, IndexFile* previous, IndexFile* current); // Merge |update| into this update; this can reduce overhead / index update // work can be parallelized. void Merge(IndexUpdate&& update); // Dump the update to a string. std::string ToString(); // File updates. std::vector files_removed; std::vector files_def_update; // Type updates. std::vector types_removed; std::vector types_def_update; std::vector types_derived; std::vector types_instances; std::vector types_uses; // Function updates. std::vector funcs_removed; std::vector funcs_def_update; std::vector funcs_declarations; std::vector funcs_derived; std::vector funcs_uses; // Variable updates. std::vector vars_removed; std::vector vars_def_update; std::vector vars_declarations; std::vector vars_uses; private: // Creates an index update assuming that |previous| is already // in the index, so only the delta between |previous| and |current| // will be applied. IndexUpdate(const IdMap& previous_id_map, const IdMap& current_id_map, IndexFile& previous, IndexFile& current); }; // NOTICE: We're not reflecting on files_removed or files_def_update, it is too // much output when logging MAKE_REFLECT_STRUCT(IndexUpdate, types_removed, types_def_update, types_derived, types_instances, types_uses, funcs_removed, funcs_def_update, funcs_declarations, funcs_derived, funcs_uses, vars_removed, vars_def_update, vars_declarations, vars_uses); struct NormalizedPath { explicit NormalizedPath(const std::string& path); bool operator==(const NormalizedPath& rhs) const; bool operator!=(const NormalizedPath& rhs) const; std::string path; }; MAKE_HASHABLE(NormalizedPath, t.path); // The query database is heavily optimized for fast queries. It is stored // in-memory. struct QueryDatabase { // All File/Func/Type/Var symbols. std::vector symbols; // Raw data storage. Accessible via SymbolIdx instances. std::vector files; std::vector types; std::vector funcs; std::vector vars; // Lookup symbol based on a usr. spp::sparse_hash_map usr_to_file; spp::sparse_hash_map usr_to_type; spp::sparse_hash_map usr_to_func; spp::sparse_hash_map usr_to_var; // Marks the given Usrs as invalid. void RemoveUsrs(SymbolKind usr_kind, const std::vector& to_remove); // Insert the contents of |update| into |db|. void ApplyIndexUpdate(IndexUpdate* update); void ImportOrUpdate(const std::vector& updates); void ImportOrUpdate(std::vector&& updates); void ImportOrUpdate(std::vector&& updates); void ImportOrUpdate(std::vector&& updates); void UpdateSymbols(Maybe>* symbol_idx, SymbolKind kind, Id idx); std::string_view GetSymbolDetailedName(RawId symbol_idx) const; std::string_view GetSymbolShortName(RawId symbol_idx) const; // Query the indexing structure to look up symbol id for given Usr. Maybe GetQueryFileIdFromPath(const std::string& path); Maybe GetQueryTypeIdFromUsr(Usr usr); Maybe GetQueryFuncIdFromUsr(Usr usr); Maybe GetQueryVarIdFromUsr(Usr usr); Maybe GetFileId(SymbolIdx ref) { switch (ref.kind) { case SymbolKind::Invalid: break; case SymbolKind::File: return QueryFileId(ref.id); case SymbolKind::Func: { const QueryFunc::Def* def = funcs[ref.id.id].AnyDef(); if (def) return def->file; break; } case SymbolKind::Type: { const QueryType::Def* def = types[ref.id.id].AnyDef(); if (def) return def->file; break; } case SymbolKind::Var: { const QueryVar::Def* def = vars[ref.id.id].AnyDef(); if (def) return def->file; break; } } return QueryFileId(); } QueryFile& GetFile(SymbolIdx ref) { return files[ref.id.id]; } QueryFunc& GetFunc(SymbolIdx ref) { return funcs[ref.id.id]; } QueryType& GetType(SymbolIdx ref) { return types[ref.id.id]; } QueryVar& GetVar(SymbolIdx ref) { return vars[ref.id.id]; } }; template struct IndexToQuery; // clang-format off template <> struct IndexToQuery { using type = QueryFileId; }; template <> struct IndexToQuery { using type = QueryFuncId; }; template <> struct IndexToQuery { using type = QueryTypeId; }; template <> struct IndexToQuery { using type = QueryVarId; }; template <> struct IndexToQuery { using type = Use; }; template <> struct IndexToQuery { using type = SymbolRef; }; template <> struct IndexToQuery { using type = Use; }; template struct IndexToQuery> { using type = optional::type>; }; template struct IndexToQuery> { using type = std::vector::type>; }; // clang-format on struct IdMap { const IdCache& local_ids; QueryFileId primary_file; IdMap(QueryDatabase* query_db, const IdCache& local_ids); // FIXME Too verbose // clang-format off QueryTypeId ToQuery(IndexTypeId id) const; QueryFuncId ToQuery(IndexFuncId id) const; QueryVarId ToQuery(IndexVarId id) const; SymbolRef ToQuery(SymbolRef ref) const; Use ToQuery(Reference ref) const; Use ToQuery(Use ref) const; Use ToQuery(IndexFunc::Declaration decl) const; template Maybe::type> ToQuery(Maybe id) const { if (!id) return nullopt; return ToQuery(*id); } template std::vector::type> ToQuery(const std::vector& a) const { std::vector::type> ret; ret.reserve(a.size()); for (auto& x : a) ret.push_back(ToQuery(x)); return ret; } // clang-format on private: spp::sparse_hash_map cached_type_ids_; spp::sparse_hash_map cached_func_ids_; spp::sparse_hash_map cached_var_ids_; };