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