#include "query_utils.h" #include "queue_manager.h" #include #include #include namespace { // Computes roughly how long |range| is. int ComputeRangeSize(const Range& range) { if (range.start.line != range.end.line) return INT_MAX; return range.end.column - range.start.column; } } // namespace Maybe GetDefinitionSpellingOfSymbol(QueryDatabase* db, QueryFuncId id) { QueryFunc& func = db->funcs[id.id]; if (func.def) return func.def->spell; return nullopt; } Maybe GetDefinitionSpellingOfSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); if (type.def) return type.def->spell; break; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (func.def) return func.def->spell; break; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); if (var.def) return var.def->spell; break; } case SymbolKind::File: case SymbolKind::Invalid: { assert(false && "unexpected"); break; } } return nullopt; } Maybe GetDefinitionExtentOfSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); if (type.def) return type.def->extent; break; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (func.def) return Use(*func.def->extent); break; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); if (var.def) return var.def->extent; break; } case SymbolKind::File: return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind, Role::None); case SymbolKind::Invalid: { assert(false && "unexpected"); break; } } return nullopt; } Maybe GetDeclarationFileForSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); if (type.def && type.def->spell) return db->GetFileId(*type.def->spell); break; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (!func.declarations.empty()) return db->GetFileId(func.declarations[0]); if (func.def && func.def->spell) return db->GetFileId(*func.def->spell); break; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); if (var.def && var.def->spell) return db->GetFileId(*var.def->spell); break; } case SymbolKind::File: return QueryFileId(sym.id); case SymbolKind::Invalid: { assert(false && "unexpected"); break; } } return nullopt; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryFunc& func = db->funcs[id.id]; if (func.def && func.def->spell) ret.push_back(*func.def->spell); else if (func.declarations.size()) ret.push_back(func.declarations[0]); } return ret; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryType& type = db->types[id.id]; if (type.def && type.def->spell) ret.push_back(*type.def->spell); } return ret; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryVar& var = db->vars[id.id]; if (var.def && var.def->spell) ret.push_back(*var.def->spell); else if (var.declarations.size()) ret.push_back(var.declarations[0]); } return ret; } std::vector GetUsesOfSymbol(QueryDatabase* db, SymbolIdx sym, bool include_decl) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); std::vector ret = type.uses; if (include_decl && type.def && type.def->spell) ret.push_back(*type.def->spell); return ret; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); std::vector ret = func.uses; if (include_decl) { if (func.def && func.def->spell) ret.push_back(*func.def->spell); AddRange(&ret, func.declarations); } return ret; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); std::vector ret = var.uses; if (include_decl) { if (var.def && var.def->spell) ret.push_back(*var.def->spell); ret.insert(ret.end(), var.declarations.begin(), var.declarations.end()); } return ret; } case SymbolKind::File: case SymbolKind::Invalid: { assert(false && "unexpected"); return {}; } } } std::vector GetDeclarationsOfSymbolForGotoDefinition( QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Type: { // Returning the definition spelling of a type is a hack (and is why the // function has the postfix `ForGotoDefintion`, but it lets the user // jump to the start of a type if clicking goto-definition on the same // type from within the type definition. QueryType& type = db->GetType(sym); if (type.def) { Maybe def = type.def->spell; if (def) return {*def}; } break; } case SymbolKind::Func: return db->GetFunc(sym).declarations; case SymbolKind::Var: return db->GetVar(sym).declarations; default: break; } return {}; } bool HasCallersOnSelfOrBaseOrDerived(QueryDatabase* db, QueryFunc& root) { // Check self. if (!root.uses.empty()) return true; // Check for base calls. std::queue queue; EachWithGen(db->funcs, root.def->base, [&](QueryFunc& func) { queue.push(&func); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); if (!func.uses.empty()) return true; if (func.def) EachWithGen(db->funcs, func.def->base, [&](QueryFunc& func1) { queue.push(&func1); }); } // Check for derived calls. EachWithGen(db->funcs, root.derived, [&](QueryFunc& func1) { queue.push(&func1); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); if (!func.uses.empty()) return true; EachWithGen(db->funcs, func.derived, [&](QueryFunc& func1) { queue.push(&func1); }); } return false; } std::vector GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) { std::vector callers; if (!root.def) return callers; std::queue queue; EachWithGen(db->funcs, root.def->base, [&](QueryFunc& func1) { queue.push(&func1); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); AddRange(&callers, func.uses); if (func.def) EachWithGen(db->funcs, func.def->base, [&](QueryFunc& func1) { queue.push(&func1); }); } return callers; } std::vector GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) { std::vector callers; std::queue queue; EachWithGen(db->funcs, root.derived, [&](QueryFunc& func) { queue.push(&func); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); EachWithGen(db->funcs, func.derived, [&](QueryFunc& func1) { queue.push(&func1); }); AddRange(&callers, func.uses); } return callers; } optional GetLsPosition(WorkingFile* working_file, const Position& position) { if (!working_file) return lsPosition(position.line, position.column); int column = position.column; optional start = working_file->GetBufferPosFromIndexPos(position.line, &column, false); if (!start) return nullopt; return lsPosition(*start, column); } optional GetLsRange(WorkingFile* working_file, const Range& location) { if (!working_file) { return lsRange(lsPosition(location.start.line, location.start.column), lsPosition(location.end.line, location.end.column)); } int start_column = location.start.column, end_column = location.end.column; optional start = working_file->GetBufferPosFromIndexPos( location.start.line, &start_column, false); optional end = working_file->GetBufferPosFromIndexPos(location.end.line, &end_column, true); if (!start || !end) return nullopt; // If remapping end fails (end can never be < start), just guess that the // final location didn't move. This only screws up the highlighted code // region if we guess wrong, so not a big deal. // // Remapping fails often in C++ since there are a lot of "};" at the end of // class/struct definitions. if (*end < *start) *end = *start + (location.end.line - location.start.line); if (*start == *end && start_column > end_column) end_column = start_column; return lsRange(lsPosition(*start, start_column), lsPosition(*end, end_column)); } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path) { QueryFile& file = db->files[file_id.id]; if (file.def) { *path = file.def->path; return lsDocumentUri::FromPath(*path); } else { *path = ""; return lsDocumentUri::FromPath(""); } } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) { QueryFile& file = db->files[file_id.id]; if (file.def) { return lsDocumentUri::FromPath(file.def->path); } else { return lsDocumentUri::FromPath(""); } } optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, Reference ref) { std::string path; Maybe file_id = db->GetFileId(ref); if (!file_id.has_value()) return nullopt; lsDocumentUri uri = GetLsDocumentUri(db, *file_id, &path); optional range = GetLsRange(working_files->GetFileByFilename(path), ref.range); if (!range) return nullopt; return lsLocation(uri, *range); } optional GetLsLocationEx(QueryDatabase* db, WorkingFiles* working_files, Use use, bool extension) { optional ls_loc = GetLsLocation(db, working_files, use); if (!ls_loc) return nullopt; lsLocationEx ret; ret.lsLocation::operator=(*ls_loc); if (extension) switch (use.kind) { default: break; case SymbolKind::Func: { QueryFunc& func = db->GetFunc(use); if (func.def) ret.containerName = std::string_view(func.def->detailed_name); break; } case SymbolKind::Type: { QueryType& type = db->GetType(use); if (type.def) ret.containerName = std::string_view(type.def->detailed_name); break; } case SymbolKind::Var: { QueryVar& var = db->GetVar(use); if (var.def) ret.containerName = std::string_view(var.def->detailed_name); break; } } return ret; } std::vector GetLsLocations( QueryDatabase* db, WorkingFiles* working_files, const std::vector& uses) { std::unordered_set unique_locations; for (Use use : uses) { optional location = GetLsLocation(db, working_files, use); if (!location) continue; unique_locations.insert(*location); } std::vector result; result.reserve(unique_locations.size()); result.assign(unique_locations.begin(), unique_locations.end()); return result; } // Returns a symbol. The symbol will have *NOT* have a location assigned. optional GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx sym, bool use_short_name) { switch (sym.kind) { case SymbolKind::File: { QueryFile& file = db->GetFile(sym); if (!file.def) break; lsSymbolInformation info; info.name = file.def->path; info.kind = lsSymbolKind::File; return info; } case SymbolKind::Type: { QueryType& type = db->GetType(sym); if (!type.def) break; lsSymbolInformation info; if (use_short_name) info.name = type.def->ShortName(); else info.name = type.def->detailed_name; if (type.def->detailed_name.c_str() != type.def->ShortName()) info.containerName = type.def->detailed_name; // TODO ClangSymbolKind -> lsSymbolKind switch (type.def->kind) { default: info.kind = lsSymbolKind::Class; break; case ClangSymbolKind::Namespace: info.kind = lsSymbolKind::Namespace; break; } return info; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (!func.def) break; lsSymbolInformation info; if (use_short_name) info.name = func.def->ShortName(); else info.name = func.def->detailed_name; info.containerName = func.def->detailed_name; info.kind = lsSymbolKind::Function; if (func.def->declaring_type.has_value()) { QueryType& container = db->types[func.def->declaring_type->id]; if (container.def) info.kind = lsSymbolKind::Method; } return info; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); if (!var.def) break; lsSymbolInformation info; if (use_short_name) info.name = var.def->ShortName(); else info.name = var.def->detailed_name; info.containerName = var.def->detailed_name; info.kind = lsSymbolKind::Variable; return info; } case SymbolKind::Invalid: break; } return nullopt; } // TODO Sort only by range length, not |kind| or |idx| std::vector FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) { std::vector symbols; symbols.reserve(1); int target_line = position.line; int target_column = position.character; if (working_file) { optional index_line = working_file->GetIndexPosFromBufferPos( target_line, &target_column, false); if (index_line) target_line = *index_line; } for (const SymbolRef& sym : file->def->all_symbols) { if (sym.range.Contains(target_line, target_column)) symbols.push_back(sym); } // Order shorter ranges first, since they are more detailed/precise. This is // important for macros which generate code so that we can resolving the // macro argument takes priority over the entire macro body. // // Order SymbolKind::Var before SymbolKind::Type. Macro calls are treated as // Var currently. If a macro expands to tokens led by a SymbolKind::Type, the // macro and the Type have the same range. We want to find the macro // definition instead of the Type definition. // // Then order functions before other types, which makes goto definition work // better on constructors. std::sort(symbols.begin(), symbols.end(), [](const SymbolRef& a, const SymbolRef& b) { int a_size = ComputeRangeSize(a.range); int b_size = ComputeRangeSize(b.range); if (a_size != b_size) return a_size < b_size; // operator> orders Var/Func before Type. int t = static_cast(a.kind) - static_cast(b.kind); if (t) return t > 0; return a.id < b.id; }); return symbols; } void EmitDiagnostics(WorkingFiles* working_files, std::string path, std::vector diagnostics) { // Emit diagnostics. Out_TextDocumentPublishDiagnostics out; out.params.uri = lsDocumentUri::FromPath(path); out.params.diagnostics = diagnostics; QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); // Cache diagnostics so we can show fixits. working_files->DoActionOnFile(path, [&](WorkingFile* working_file) { if (working_file) working_file->diagnostics_ = diagnostics; }); }