nixd
Loading...
Searching...
No Matches
Definition.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of [Go to Definition]
3/// [Go to Definition]:
4/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
5
6#include "Definition.h"
7#include "AST.h"
8#include "Convert.h"
9
12
13#include "lspserver/Protocol.h"
14
15#include <boost/asio/post.hpp>
16
17#include <llvm/Support/Error.h>
18#include <llvm/Support/JSON.h>
19
23#include <nixf/Sema/ParentMap.h>
25
26#include <exception>
27#include <semaphore>
28
29using namespace nixd;
30using namespace nixd::idioms;
31using namespace nixf;
32using namespace lspserver;
33using namespace llvm;
34
37using Locations = std::vector<Location>;
38
39namespace {
40
42 const ParentMapAnalysis &PMA,
43 const VariableLookupAnalysis &VLA) {
44 // If "N" is a definition itself, just return it.
45 if (const Definition *Def = VLA.toDef(N))
46 return Def;
47
48 // If N is inside an attrset, it maybe an "AttrName", let's look for it.
49 const Node *Parent = PMA.query(N);
50 if (Parent && Parent->kind() == Node::NK_AttrName)
51 return VLA.toDef(*Parent);
52
53 return nullptr;
54}
55
56// Special case, variable in "inherit"
57// inherit name
58// ^~~~<--- this is an "AttrName", not variable.
59const ExprVar *findInheritVar(const Node &N, const ParentMapAnalysis &PMA,
60 const VariableLookupAnalysis &VLA) {
61 if (const Node *Up = PMA.upTo(N, Node::NK_Inherit)) {
62 const Node *UpAn = PMA.upTo(N, Node::NK_AttrName);
63 if (!UpAn)
64 return nullptr;
65 const auto &Inh = static_cast<const Inherit &>(*Up);
66 const auto &AN = static_cast<const AttrName &>(*UpAn);
67
68 // Skip:
69 //
70 // inherit (expr) name1 name2;
71 //
72 if (Inh.expr())
73 return nullptr;
74
75 // Skip dynamic.
76 if (!AN.isStatic())
77 return nullptr;
78
79 // This attrname will be desugared into an "ExprVar".
80 Up = PMA.upTo(Inh, Node::NK_ExprAttrs);
81 if (!Up)
82 return nullptr;
83
84 const SemaAttrs &SA = static_cast<const ExprAttrs &>(*Up).sema();
85 const Node *Var = SA.staticAttrs().at(AN.staticName()).value();
86 assert(Var->kind() == Node::NK_ExprVar);
87 return static_cast<const ExprVar *>(Var);
88 }
89 return nullptr;
90}
91
92const ExprVar *findVar(const Node &N, const ParentMapAnalysis &PMA,
93 const VariableLookupAnalysis &VLA) {
94 if (const ExprVar *InVar = findInheritVar(N, PMA, VLA))
95 return InVar;
96
97 return static_cast<const ExprVar *>(PMA.upTo(N, Node::NK_ExprVar));
98}
99
101 const VariableLookupAnalysis &VLA) {
102 LookupResult Result = VLA.query(static_cast<const ExprVar &>(Var));
103
104 if (Result.Kind == ResultKind::Undefined)
105 throw UndefinedVarException();
106
107 if (Result.Kind == ResultKind::NoSuchVar)
108 throw NoSuchVarException();
109
110 assert(Result.Def);
111
112 return *Result.Def;
113}
114
115/// \brief Convert nixf::Definition to lspserver::Location
116Location convertToLocation(llvm::StringRef Src, const Definition &Def,
117 URIForFile URI) {
118 if (!Def.syntax())
120 assert(Def.syntax());
121 return Location{
122 .uri = std::move(URI),
123 .range = toLSPRange(Src, Def.syntax()->range()),
124 };
125}
126
127struct NoLocationsFoundInNixpkgsException : std::exception {
128 [[nodiscard]] const char *what() const noexcept override {
129 return "no locations found in nixpkgs";
130 }
131};
132
133class WorkerReportedException : std::exception {
134 llvm::Error E;
135
136public:
137 WorkerReportedException(llvm::Error E) : E(std::move(E)){};
138
139 llvm::Error takeError() { return std::move(E); }
140 [[nodiscard]] const char *what() const noexcept override {
141 return "worker reported some error";
142 }
143};
144
145/// \brief Resolve definition by invoking nixpkgs provider.
146///
147/// Useful for users inspecting nixpkgs packages. For example, someone clicks
148/// "with pkgs; [ hello ]", it's better to goto nixpkgs position, instead of
149/// "with pkgs;"
150class NixpkgsDefinitionProvider {
151 AttrSetClient &NixpkgsClient;
152
153 /// \brief Parse nix-rolled location: file:line -> lsp Location
154 static Location parseLocation(std::string_view Position) {
155 // Firstly, find ":"
156 auto Pos = Position.find_first_of(':');
157 if (Pos == std::string_view::npos) {
158 return Location{
160 .range = {{0, 0}, {0, 0}},
161 };
162 }
163 int PosL = std::stoi(std::string(Position.substr(Pos + 1)));
165 std::string_view File = Position.substr(0, Pos);
166 return Location{
168 .range = {P, P},
169 };
170 }
171
172public:
173 NixpkgsDefinitionProvider(AttrSetClient &NixpkgsClient)
174 : NixpkgsClient(NixpkgsClient) {}
175
177 std::binary_semaphore Ready(0);
179 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
180 if (Resp)
181 Desc = *Resp;
182 else
183 Desc = Resp.takeError();
184 Ready.release();
185 };
186 NixpkgsClient.attrpathInfo(Sel, std::move(OnReply));
187 Ready.acquire();
188
189 if (!Desc)
190 throw WorkerReportedException(Desc.takeError());
191
192 // Prioritize package location if it exists.
193 if (const std::optional<std::string> &Position = Desc->PackageDesc.Position)
195
196 // Use the location in "ValueMeta".
197 if (const auto &Loc = Desc->Meta.Location)
198 return Locations{*Loc};
199
200 throw NoLocationsFoundInNixpkgsException();
201 }
202};
203
204/// \brief Try to get "location" by invoking options worker
205class OptionsDefinitionProvider {
206 AttrSetClient &Client;
207
208public:
209 OptionsDefinitionProvider(AttrSetClient &Client) : Client(Client) {}
210 void resolveLocations(const std::vector<std::string> &Params,
211 Locations &Locs) {
212 std::binary_semaphore Ready(0);
213 Expected<OptionInfoResponse> Info = error("not replied");
215 auto OnReply = [&Ready, &Info](llvm::Expected<OptionInfoResponse> Resp) {
216 Info = std::move(Resp);
217 Ready.release();
218 };
219 // Send request.
220
221 Client.optionInfo(Params, std::move(OnReply));
222 Ready.acquire();
223
224 if (!Info) {
225 elog("getting locations: {0}", Info.takeError());
226 return;
227 }
228
229 for (const auto &Decl : Info->Declarations)
230 Locs.emplace_back(Decl);
231 }
232};
233
234/// \brief Get the locations of some attribute path.
235///
236/// Usually this function will return a list of option declarations via RPC
238 std::mutex &OptionsLock,
239 Controller::OptionMapTy &Options) {
241 std::vector<std::string> Scope;
242 auto R = findAttrPath(N, PM, Scope);
244 if (R == PathResult::OK) {
245 std::lock_guard _(OptionsLock);
246 // For each option worker, try to get it's decl position.
247 for (const auto &[_, Client] : Options) {
248 if (AttrSetClient *C = Client->client()) {
249 OptionsDefinitionProvider ODP(*C);
250 ODP.resolveLocations(Scope, Locs);
251 }
252 }
253 }
254 return Locs;
255}
256
257/// \brief Get nixpkgs definition from a selector.
259 AttrSetClient &NixpkgsClient) {
260 try {
261 // Ask nixpkgs provider information about this selector.
262 NixpkgsDefinitionProvider NDP(NixpkgsClient);
263 return NDP.resolveSelector(Sel);
264 } catch (NoLocationsFoundInNixpkgsException &E) {
265 elog("definition/idiom: {0}", E.what());
266 } catch (WorkerReportedException &E) {
267 elog("definition/idiom/worker: {0}", E.takeError());
268 }
269 return {};
270}
271
272/// \brief Get definiton of select expressions.
274 const ParentMapAnalysis &PM,
275 AttrSetClient &NixpkgsClient) {
276 // Currently we can only deal with idioms.
277 // Maybe more data-flow analysis will be added though.
278 try {
279 return defineNixpkgsSelector(mkSelector(Sel, VLA, PM), NixpkgsClient);
280 } catch (IdiomSelectorException &E) {
281 elog("defintion/idiom/selector: {0}", E.what());
282 }
283 return {};
284}
285
287 const URIForFile &URI, llvm::StringRef Src) {
288 const Definition &Def = findVarDefinition(Var, VLA);
289 return {convertToLocation(Src, Def, URI)};
290}
291
292template <class T>
293std::vector<T> mergeVec(std::vector<T> A, const std::vector<T> &B) {
294 A.insert(A.end(), B.begin(), B.end());
295 return A;
296}
297
298llvm::Expected<Locations>
300 const ParentMapAnalysis &PM, AttrSetClient &NixpkgsClient,
301 const URIForFile &URI, llvm::StringRef Src) {
302 try {
304
305 // Nixpkgs locations.
306 try {
309 return mergeVec(std::move(StaticLocs), NixpkgsLocs);
310 } catch (std::exception &E) {
311 elog("definition/idiom/selector: {0}", E.what());
312 return StaticLocs;
313 }
314 } catch (std::exception &E) {
315 elog("definition/static: {0}", E.what());
316 return Locations{};
317 }
318 return error("unreachable code! Please submit an issue");
319}
320
321/// \brief Squash a vector into smaller json variant.
322template <class T> llvm::json::Value squash(std::vector<T> List) {
323 std::size_t Size = List.size();
324 switch (Size) {
325 case 0:
326 return nullptr;
327 case 1:
328 return std::move(List.back());
329 default:
330 break;
331 }
332 return std::move(List);
333}
334
335template <class T>
336llvm::Expected<llvm::json::Value> squash(llvm::Expected<std::vector<T>> List) {
337 if (!List)
338 return List.takeError();
339 return squash(std::move(*List));
340}
341
342} // namespace
343
345 const ParentMapAnalysis &PMA,
346 const VariableLookupAnalysis &VLA) {
347 const ExprVar *Var = findVar(N, PMA, VLA);
348 if (!Var) [[unlikely]] {
349 if (const Definition *Def = findSelfDefinition(N, PMA, VLA))
350 return *Def;
352 }
353 assert(Var->kind() == Node::NK_ExprVar);
354 return findVarDefinition(*Var, VLA);
355}
356
357void Controller::onDefinition(const TextDocumentPositionParams &Params,
359 auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
360 Pos = toNixfPosition(Params.position), this]() mutable {
361 std::string File(URI.file());
362 if (std::shared_ptr<NixTU> TU = getTU(File, Reply)) [[likely]] {
363 if (std::shared_ptr<Node> AST = getAST(*TU, Reply)) [[likely]] {
364 const VariableLookupAnalysis &VLA = *TU->variableLookup();
365 const ParentMapAnalysis &PM = *TU->parentMap();
366 const Node *MaybeN = AST->descend({Pos, Pos});
367 if (!MaybeN) [[unlikely]] {
368 Reply(error("cannot find AST node on given position"));
369 return;
370 }
371 const Node &N = *MaybeN;
372 const Node *MaybeUpExpr = PM.upExpr(N);
373 if (!MaybeUpExpr) {
374 Reply(nullptr);
375 return;
376 }
377
378 const Node &UpExpr = *MaybeUpExpr;
379
380 return Reply(squash([&]() -> llvm::Expected<Locations> {
381 // Special case for inherited names.
382 if (const ExprVar *Var = findInheritVar(N, PM, VLA))
383 return defineVar(*Var, VLA, PM, *nixpkgsClient(), URI, TU->src());
384
385 switch (UpExpr.kind()) {
386 case Node::NK_ExprVar: {
387 const auto &Var = static_cast<const ExprVar &>(UpExpr);
388 return defineVar(Var, VLA, PM, *nixpkgsClient(), URI, TU->src());
389 }
390 case Node::NK_ExprSelect: {
391 const auto &Sel = static_cast<const ExprSelect &>(UpExpr);
392 return defineSelect(Sel, VLA, PM, *nixpkgsClient());
393 }
394 case Node::NK_ExprAttrs:
395 return defineAttrPath(N, PM, OptionsLock, Options);
396 default:
397 break;
398 }
399 return error("unknown node type for definition");
400 }()));
401 }
402 }
403 };
404 boost::asio::post(Pool, std::move(Action));
405}
This file declares some common analysis (tree walk) on the AST.
Types used in nixpkgs provider.
Convert between LSP and nixf types.
std::vector< Location > Locations
Lookup variable names, from it's parent scope.
void optionInfo(const AttrPathInfoParams &Params, lspserver::Callback< OptionInfoResponse > Reply)
void attrpathInfo(const AttrPathInfoParams &Params, lspserver::Callback< AttrPathInfoResponse > Reply)
std::map< std::string, std::unique_ptr< AttrSetClientProc > > OptionMapTy
Definition Controller.h:21
Represents a definition.
const Node * syntax() const
const SemaAttrs & sema() const
Definition Attrs.h:284
const Node * descend(PositionRange Range) const
Descendant node that contains the given range.
Definition Basic.h:49
NodeKind kind() const
Definition Basic.h:34
LexerCursorRange range() const
Definition Basic.h:35
const Node * upTo(const Node &N, Node::NodeKind Kind) const
Search up until some kind of node is found.
const Node * query(const Node &N) const
Position()=default
Attribute set after deduplication.
Definition Attrs.h:231
const std::map< std::string, Attribute > & staticAttrs() const
Static attributes, do not require evaluation to get the key.
Definition Attrs.h:252
const Definition * toDef(const Node &N) const
Get definition record for some name.
LookupResult query(const ExprVar &Var) const
Query the which name/with binds to specific varaible.
ParentMap analysis.
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&...Vals)
Definition Logger.h:70
@ Info
An information message.
void elog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:52
Selector mkVarSelector(const nixf::ExprVar &Var, const nixf::VariableLookupAnalysis &VLA, const nixf::ParentMapAnalysis &PM)
Construct a nixd::Selector from Var.
Definition AST.cpp:199
Selector mkSelector(const nixf::AttrPath &AP, Selector BaseSelector)
Construct a nixd::Selector from AP.
Definition AST.cpp:249
bool fromJSON(const llvm::json::Value &Params, Configuration::Diagnostic &R, llvm::json::Path P)
FindAttrPathResult findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, std::vector< std::string > &Path)
Heuristically find attrpath suitable for "attrpath" completion.
Definition AST.cpp:283
std::vector< std::string > Selector
A list of strings that "select"s into a attribute set.
Definition AttrSet.h:42
FindAttrPathResult
Definition AST.h:119
nixf::Position toNixfPosition(const lspserver::Position &P)
Definition Convert.cpp:32
const nixf::Definition & findDefinition(const nixf::Node &N, const nixf::ParentMapAnalysis &PMA, const nixf::VariableLookupAnalysis &VLA)
Heuristically find definition on some node.
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
Definition Convert.cpp:40
std::vector< OptionField > OptionCompleteResponse
Definition AttrSet.h:137
URIForFile uri
The text document's URI.
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Exceptions scoped in nixd::mkIdiomSelector.
Definition AST.h:31
No such variable.
Definition AST.h:49