diff --git a/nixd/lib/Controller/AST.h b/nixd/lib/Controller/AST.h index 5cac4c0d3..fd04e0366 100644 --- a/nixd/lib/Controller/AST.h +++ b/nixd/lib/Controller/AST.h @@ -62,13 +62,21 @@ struct NotAnIdiomException : IdiomSelectorException { } }; +struct VLAException : std::exception {}; + /// \brief No such variable. -struct NoSuchVarException : IdiomSelectorException { +struct NoSuchVarException : VLAException { [[nodiscard]] const char *what() const noexcept override { return "no such variable"; } }; +struct UndefinedVarException : VLAException { + [[nodiscard]] const char *what() const noexcept override { + return "undefined variable"; + } +}; + /// \brief Construct a nixd::Selector from \p Var. /// /// Try to heuristically find a selector of a variable, based on some known diff --git a/nixd/lib/Controller/Definition.cpp b/nixd/lib/Controller/Definition.cpp index 829ca59d3..5a35503d6 100644 --- a/nixd/lib/Controller/Definition.cpp +++ b/nixd/lib/Controller/Definition.cpp @@ -10,6 +10,8 @@ #include "nixd/Controller/Controller.h" #include "nixd/Protocol/AttrSet.h" +#include "lspserver/Protocol.h" + #include #include @@ -94,20 +96,26 @@ const ExprVar *findVar(const Node &N, const ParentMapAnalysis &PMA, return static_cast(PMA.upTo(N, Node::NK_ExprVar)); } -Expected staticDef(URIForFile URI, const Node &N, - const ParentMapAnalysis &PMA, - const VariableLookupAnalysis &VLA) { - Expected ExpDef = findDefinition(N, PMA, VLA); - if (!ExpDef) - return ExpDef.takeError(); +const Definition &findVarDefinition(const ExprVar &Var, + const VariableLookupAnalysis &VLA) { + LookupResult Result = VLA.query(static_cast(Var)); + + if (Result.Kind == ResultKind::Undefined) + throw UndefinedVarException(); + + if (Result.Kind == ResultKind::NoSuchVar) + throw NoSuchVarException(); + + assert(Result.Def); - if (ExpDef->isBuiltin()) - return error("this is a builtin variable defined by nix interpreter"); - assert(ExpDef->syntax()); + return *Result.Def; +} +/// \brief Convert nixf::Definition to lspserver::Location +Location convertToLocation(const Definition &Def, URIForFile URI) { return Location{ .uri = std::move(URI), - .range = toLSPRange(ExpDef->syntax()->range()), + .range = toLSPRange(Def.syntax()->range()), }; } @@ -270,6 +278,39 @@ Locations defineSelect(const ExprSelect &Sel, const VariableLookupAnalysis &VLA, return {}; } +Locations defineVarStatic(const ExprVar &Var, const VariableLookupAnalysis &VLA, + const URIForFile &URI) { + const Definition &Def = findVarDefinition(Var, VLA); + return {convertToLocation(Def, URI)}; +} + +template +std::vector mergeVec(std::vector A, const std::vector &B) { + A.insert(A.end(), B.begin(), B.end()); + return A; +} + +Locations defineVar(const ExprVar &Var, const VariableLookupAnalysis &VLA, + const ParentMapAnalysis &PM, AttrSetClient &NixpkgsClient, + const URIForFile &URI) { + try { + Locations StaticLocs = defineVarStatic(Var, VLA, URI); + + // Nixpkgs locations. + try { + Selector Sel = mkIdiomSelector(Var, VLA, PM); + Locations NixpkgsLocs = defineNixpkgsSelector(Sel, NixpkgsClient); + return mergeVec(std::move(StaticLocs), NixpkgsLocs); + } catch (std::exception &E) { + elog("definition/idiom/selector: {0}", E.what()); + return StaticLocs; + } + } catch (std::exception &E) { + elog("definition/static: {0}", E.what()); + } + return {}; +} + /// \brief Squash a vector into smaller json variant. template llvm::json::Value squash(std::vector List) { std::size_t Size = List.size(); @@ -293,27 +334,17 @@ llvm::Expected squash(llvm::Expected> List) { } // namespace -Expected -nixd::findDefinition(const Node &N, const ParentMapAnalysis &PMA, - const VariableLookupAnalysis &VLA) { +const Definition &nixd::findDefinition(const Node &N, + const ParentMapAnalysis &PMA, + const VariableLookupAnalysis &VLA) { const ExprVar *Var = findVar(N, PMA, VLA); if (!Var) [[unlikely]] { if (const Definition *Def = findSelfDefinition(N, PMA, VLA)) return *Def; - return error("cannot find variable on given position"); + throw CannotFindVarException(); } assert(Var->kind() == Node::NK_ExprVar); - LookupResult Result = VLA.query(static_cast(*Var)); - - if (Result.Kind == ResultKind::Undefined) - return error("this varaible is undefined"); - - if (Result.Kind == ResultKind::NoSuchVar) - return error("this varaible is not used in var lookup (duplicated attr?)"); - - assert(Result.Def); - - return *Result.Def; + return findVarDefinition(*Var, VLA); } void Controller::onDefinition(const TextDocumentPositionParams &Params, @@ -340,7 +371,15 @@ void Controller::onDefinition(const TextDocumentPositionParams &Params, const Node &UpExpr = *MaybeUpExpr; return Reply(squash([&]() -> llvm::Expected { + // Special case for inherited names. + if (const ExprVar *Var = findInheritVar(N, PM, VLA)) + return defineVar(*Var, VLA, PM, *nixpkgsClient(), URI); + switch (UpExpr.kind()) { + case Node::NK_ExprVar: { + const auto &Var = static_cast(UpExpr); + return defineVar(Var, VLA, PM, *nixpkgsClient(), URI); + } case Node::NK_ExprSelect: { const auto &Sel = static_cast(UpExpr); return defineSelect(Sel, VLA, PM, *nixpkgsClient()); @@ -350,16 +389,7 @@ void Controller::onDefinition(const TextDocumentPositionParams &Params, default: break; } - - // Get static locations. - // Likely this will fail, thus just logging errors. - return [&]() -> Locations { - Expected StaticLoc = staticDef(URI, *MaybeN, PM, VLA); - if (StaticLoc) - return {*StaticLoc}; - elog("definition/static: {0}", StaticLoc.takeError()); - return {}; - }(); + return error("unknown node type for definition"); }())); } } diff --git a/nixd/lib/Controller/Definition.h b/nixd/lib/Controller/Definition.h index 0b8e09c61..270660227 100644 --- a/nixd/lib/Controller/Definition.h +++ b/nixd/lib/Controller/Definition.h @@ -7,9 +7,15 @@ namespace nixd { +struct CannotFindVarException : std::exception { + [[nodiscard]] const char *what() const noexcept override { + return "cannot find variable on given node"; + } +}; + /// \brief Heuristically find definition on some node -llvm::Expected -findDefinition(const nixf::Node &N, const nixf::ParentMapAnalysis &PMA, - const nixf::VariableLookupAnalysis &VLA); +const nixf::Definition &findDefinition(const nixf::Node &N, + const nixf::ParentMapAnalysis &PMA, + const nixf::VariableLookupAnalysis &VLA); } // namespace nixd diff --git a/nixd/lib/Controller/DocumentHighlight.cpp b/nixd/lib/Controller/DocumentHighlight.cpp index 03371641c..0a9c1f3d0 100644 --- a/nixd/lib/Controller/DocumentHighlight.cpp +++ b/nixd/lib/Controller/DocumentHighlight.cpp @@ -9,6 +9,7 @@ #include "nixd/Controller/Controller.h" #include +#include #include #include #include @@ -19,30 +20,34 @@ using namespace llvm; using namespace nixf; namespace { -Error highlight(const nixf::Node &Desc, const ParentMapAnalysis &PMA, - const VariableLookupAnalysis &VLA, const URIForFile &URI, - std::vector &Highlights) { + +std::vector highlight(const nixf::Node &Desc, + const ParentMapAnalysis &PMA, + const VariableLookupAnalysis &VLA, + const URIForFile &URI) { // Find "definition" - if (auto Def = findDefinition(Desc, PMA, VLA)) { - // OK, iterate all uses. - for (const auto *Use : Def->uses()) { - assert(Use); - Highlights.emplace_back(DocumentHighlight{ - .range = toLSPRange(Use->range()), - .kind = DocumentHighlightKind::Read, - }); - } - if (Def->syntax()) { - const Node &Syntax = *Def->syntax(); - Highlights.emplace_back(DocumentHighlight{ - .range = toLSPRange(Syntax.range()), - .kind = DocumentHighlightKind::Write, - }); - } - return Error::success(); + auto Def = findDefinition(Desc, PMA, VLA); + + std::vector Highlights; + // OK, iterate all uses. + for (const auto *Use : Def.uses()) { + assert(Use); + Highlights.emplace_back(DocumentHighlight{ + .range = toLSPRange(Use->range()), + .kind = DocumentHighlightKind::Read, + }); + } + if (Def.syntax()) { + const Node &Syntax = *Def.syntax(); + Highlights.emplace_back(DocumentHighlight{ + .range = toLSPRange(Syntax.range()), + .kind = DocumentHighlightKind::Write, + }); } - return error("Cannot find definition of this node"); + + return Highlights; } + } // namespace void Controller::onDocumentHighlight( @@ -58,20 +63,14 @@ void Controller::onDocumentHighlight( Reply(error("cannot find corresponding node on given position")); return; } - std::vector Highlights; - if (auto Err = highlight(*Desc, *TU->parentMap(), *TU->variableLookup(), - URI, Highlights)) { - // FIXME: Empty response if there are no def-use chain found. - // For document highlights, the specification explicitly specified LSP - // should do "fuzzy" things. - - // Empty response on error, don't reply all errors because this method - // is very frequently called. - Reply(std::vector{}); - lspserver::elog("textDocument/documentHighlight failed: {0}", Err); - return; + try { + const auto &PM = *TU->parentMap(); + const auto &VLA = *TU->variableLookup(); + return Reply(highlight(*Desc, PM, VLA, URI)); + } catch (std::exception &E) { + elog("textDocument/documentHighlight failed: {0}", E.what()); + return Reply(std::vector{}); } - Reply(std::move(Highlights)); } } }; diff --git a/nixd/lib/Controller/FindReferences.cpp b/nixd/lib/Controller/FindReferences.cpp index 391e4ebd8..e3369c5eb 100644 --- a/nixd/lib/Controller/FindReferences.cpp +++ b/nixd/lib/Controller/FindReferences.cpp @@ -20,27 +20,27 @@ using namespace nixf; namespace { -Error findReferences(const nixf::Node &Desc, const ParentMapAnalysis &PMA, - const VariableLookupAnalysis &VLA, const URIForFile &URI, - std::vector &Locations) { +std::vector findReferences(const nixf::Node &Desc, + const ParentMapAnalysis &PMA, + const VariableLookupAnalysis &VLA, + const URIForFile &URI) { // Two steps. // 1. Find some "definition" for this node. // 2. Find all "uses", and construct the vector. // Find "definition" - if (auto Def = findDefinition(Desc, PMA, VLA)) { - // OK, iterate all uses. - for (const auto *Use : Def->uses()) { - assert(Use); - Locations.emplace_back(Location{ - .uri = URI, - .range = toLSPRange(Use->range()), - }); - } - return Error::success(); + auto Def = findDefinition(Desc, PMA, VLA); + std::vector Locations; + // OK, iterate all uses. + for (const auto *Use : Def.uses()) { + assert(Use); + Locations.emplace_back(Location{ + .uri = URI, + .range = toLSPRange(Use->range()), + }); } - return error("Cannot find definition of this node"); + return Locations; } } // namespace @@ -54,16 +54,16 @@ void Controller::onReferences(const TextDocumentPositionParams &Params, if (std::shared_ptr AST = getAST(*TU, Reply)) [[likely]] { const nixf::Node *Desc = AST->descend({Pos, Pos}); if (!Desc) { - Reply(error("cannot find corresponding node on given position")); - return; + return Reply( + error("cannot find corresponding node on given position")); } - std::vector Locations; - if (auto Err = findReferences(*Desc, *TU->parentMap(), - *TU->variableLookup(), URI, Locations)) { - Reply(std::move(Err)); - return; + const auto &PM = *TU->parentMap(); + const auto &VLA = *TU->variableLookup(); + try { + return Reply(findReferences(*Desc, PM, VLA, URI)); + } catch (std::exception &E) { + return Reply(error("references: {0}", E.what())); } - Reply(std::move(Locations)); } } }; diff --git a/nixd/lib/Controller/Rename.cpp b/nixd/lib/Controller/Rename.cpp index 1064c7282..5e675c1fe 100644 --- a/nixd/lib/Controller/Rename.cpp +++ b/nixd/lib/Controller/Rename.cpp @@ -11,32 +11,45 @@ #include +#include + using namespace lspserver; using namespace nixd; using namespace llvm; using namespace nixf; namespace { -llvm::Expected rename(const nixf::Node &Desc, - const std::string &NewText, - const ParentMapAnalysis &PMA, - const VariableLookupAnalysis &VLA, - const URIForFile &URI) { + +struct RenameException : std::exception {}; + +struct RenameWithException : RenameException { + [[nodiscard]] const char *what() const noexcept override { + return "cannot rename `with` defined variable"; + } +}; + +struct RenameBuiltinException : RenameException { + [[nodiscard]] const char *what() const noexcept override { + return "cannot rename builtin variable"; + } +}; + +WorkspaceEdit rename(const nixf::Node &Desc, const std::string &NewText, + const ParentMapAnalysis &PMA, + const VariableLookupAnalysis &VLA, const URIForFile &URI) { using lspserver::TextEdit; // Find "definition" auto Def = findDefinition(Desc, PMA, VLA); - if (!Def) - return Def.takeError(); - if (Def->source() == Definition::DS_With) - return error("cannot rename `with` defined variables"); + if (Def.source() == Definition::DS_With) + throw RenameWithException(); - if (Def->isBuiltin()) - return error("cannot rename builtin variable"); + if (Def.isBuiltin()) + throw RenameBuiltinException(); std::vector Edits; - for (const auto *Use : Def->uses()) { + for (const auto *Use : Def.uses()) { Edits.emplace_back(TextEdit{ .range = toLSPRange(Use->range()), .newText = NewText, @@ -44,7 +57,7 @@ llvm::Expected rename(const nixf::Node &Desc, } Edits.emplace_back(TextEdit{ - .range = toLSPRange(Def->syntax()->range()), + .range = toLSPRange(Def.syntax()->range()), .newText = NewText, }); WorkspaceEdit WE; @@ -67,9 +80,13 @@ void Controller::onRename(const RenameParams &Params, Reply(error("cannot find corresponding node on given position")); return; } - Reply(rename(*Desc, NewText, *TU->parentMap(), *TU->variableLookup(), - URI)); - return; + const auto &PM = *TU->parentMap(); + const auto &VLA = *TU->variableLookup(); + try { + return Reply(rename(*Desc, NewText, PM, VLA, URI)); + } catch (std::exception &E) { + return Reply(error(E.what())); + } } } }; @@ -86,17 +103,17 @@ void Controller::onPrepareRename( if (std::shared_ptr AST = getAST(*TU, Reply)) [[likely]] { const nixf::Node *Desc = AST->descend({Pos, Pos}); if (!Desc) { - Reply(error("cannot find corresponding node on given position")); - return; + return Reply( + error("cannot find corresponding node on given position")); } - llvm::Expected Exp = - rename(*Desc, "", *TU->parentMap(), *TU->variableLookup(), URI); - if (Exp) { - Reply(toLSPRange(Desc->range())); - return; + const auto &PM = *TU->parentMap(); + const auto &VLA = *TU->variableLookup(); + try { + WorkspaceEdit WE = rename(*Desc, "", PM, VLA, URI); + return Reply(toLSPRange(Desc->range())); + } catch (std::exception &E) { + return Reply(error(E.what())); } - Reply(Exp.takeError()); - return; } } }; diff --git a/nixd/tools/nixd/test/definition/package.md b/nixd/tools/nixd/test/definition/package.md index c7ec87d94..a4882a424 100644 --- a/nixd/tools/nixd/test/definition/package.md +++ b/nixd/tools/nixd/test/definition/package.md @@ -62,28 +62,28 @@ CHECK-NEXT: "result": [ CHECK-NEXT: { CHECK-NEXT: "range": { CHECK-NEXT: "end": { -CHECK-NEXT: "character": 0, -CHECK-NEXT: "line": 33 +CHECK-NEXT: "character": 4, +CHECK-NEXT: "line": 0 CHECK-NEXT: }, CHECK-NEXT: "start": { CHECK-NEXT: "character": 0, -CHECK-NEXT: "line": 33 +CHECK-NEXT: "line": 0 CHECK-NEXT: } CHECK-NEXT: }, -CHECK-NEXT: "uri": "file:///foo" +CHECK-NEXT: "uri": "file:///basic.nix" CHECK-NEXT: }, CHECK-NEXT: { CHECK-NEXT: "range": { CHECK-NEXT: "end": { -CHECK-NEXT: "character": 4, -CHECK-NEXT: "line": 0 +CHECK-NEXT: "character": 0, +CHECK-NEXT: "line": 33 CHECK-NEXT: }, CHECK-NEXT: "start": { CHECK-NEXT: "character": 0, -CHECK-NEXT: "line": 0 +CHECK-NEXT: "line": 33 CHECK-NEXT: } CHECK-NEXT: }, -CHECK-NEXT: "uri": "file:///basic.nix" +CHECK-NEXT: "uri": "file:///foo" CHECK-NEXT: } ``` diff --git a/nixd/tools/nixd/test/rename/duplicated.md b/nixd/tools/nixd/test/rename/duplicated.md index 116ff091a..9ff5c6922 100644 --- a/nixd/tools/nixd/test/rename/duplicated.md +++ b/nixd/tools/nixd/test/rename/duplicated.md @@ -61,7 +61,7 @@ ``` CHECK: "error": { CHECK-NEXT: "code": -32001, -CHECK-NEXT: "message": "this varaible is not used in var lookup (duplicated attr?)" +CHECK-NEXT: "message": "no such variable" CHECK-NEXT: }, CHECK-NEXT: "id": 2, CHECK-NEXT: "jsonrpc": "2.0" diff --git a/nixd/tools/nixd/test/rename/with.md b/nixd/tools/nixd/test/rename/with.md index 476fb24e8..56f6fea70 100644 --- a/nixd/tools/nixd/test/rename/with.md +++ b/nixd/tools/nixd/test/rename/with.md @@ -58,7 +58,7 @@ ``` ``` - CHECK: "message": "cannot rename `with` defined variables" + CHECK: "message": "cannot rename `with` defined variable" CHECK-NEXT: }, CHECK-NEXT: "id": 2, CHECK-NEXT: "jsonrpc": "2.0"