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