nixd
Loading...
Searching...
No Matches
VariableLookup.cpp
Go to the documentation of this file.
5
6using namespace nixf;
7
8namespace {
9
10/// Builder a map of definitions. If there are something overlapped, maybe issue
11/// a diagnostic.
12class DefBuilder {
14
15public:
16 void addBuiltin(std::string Name) {
17 // Don't need to record def map for builtins.
18 auto _ = add(std::move(Name), nullptr, Definition::DS_Builtin);
19 }
20
21 [[nodiscard("Record ToDef Map!")]] std::shared_ptr<Definition>
22 add(std::string Name, const Node *Entry,
24 assert(!Def.contains(Name));
25 auto NewDef = std::make_shared<Definition>(Entry, Source);
26 Def.insert({std::move(Name), NewDef});
27 return NewDef;
28 }
29
30 EnvNode::DefMap finish() { return std::move(Def); }
31};
32
33} // namespace
34
35bool EnvNode::isLive() const {
36 for (const auto &[_, D] : Defs) {
37 if (!D->uses().empty())
38 return true;
39 }
40 return false;
41}
42
43void VariableLookupAnalysis::emitEnvLivenessWarning(
44 const std::shared_ptr<EnvNode> &NewEnv) {
45 for (const auto &[Name, Def] : NewEnv->defs()) {
46 // If the definition comes from lambda arg, omit the diagnostic
47 // because there is no elegant way to "fix" this trivially & keep
48 // the lambda signature.
49 if (Def->source() == Definition::DS_LambdaArg)
50 continue;
51 // Ignore builtins usage.
52 if (!Def->syntax())
53 continue;
54 if (Def->uses().empty()) {
55 Diagnostic::DiagnosticKind Kind = [&]() {
56 switch (Def->source()) {
58 return Diagnostic::DK_UnusedDefLet;
60 return Diagnostic::DK_UnusedDefLambdaNoArg_Formal;
62 return Diagnostic::DK_UnusedDefLambdaWithArg_Formal;
64 return Diagnostic::DK_UnusedDefLambdaWithArg_Arg;
65 default:
66 assert(false && "liveness diagnostic encountered an unknown source!");
67 __builtin_unreachable();
68 }
69 }();
70 Diagnostic &D = Diags.emplace_back(Kind, Def->syntax()->range());
71 D << Name;
73 }
74 }
75}
76
77void VariableLookupAnalysis::lookupVar(const ExprVar &Var,
78 const std::shared_ptr<EnvNode> &Env) {
79 const auto &Name = Var.id().name();
80 const auto *CurEnv = Env.get();
81 std::shared_ptr<Definition> Def;
82 std::vector<const EnvNode *> WithEnvs;
83 for (; CurEnv; CurEnv = CurEnv->parent()) {
84 if (CurEnv->defs().contains(Name)) {
85 Def = CurEnv->defs().at(Name);
86 break;
87 }
88 // Find all nested "with" expression, variables potentially come from those.
89 // For example
90 // with lib;
91 // with builtins;
92 // generators <--- this variable may come from "lib" | "builtins"
93 //
94 // We cannot determine where it precisely come from, thus mark all Envs
95 // alive.
96 if (CurEnv->isWith()) {
97 WithEnvs.emplace_back(CurEnv);
98 }
99 }
100
101 if (Def) {
102 Def->usedBy(Var);
103 Results.insert({&Var, LookupResult{LookupResultKind::Defined, Def}});
104 } else if (!WithEnvs.empty()) { // comes from enclosed "with" expressions.
105 for (const auto *WithEnv : WithEnvs) {
106 Def = WithDefs.at(WithEnv->syntax());
107 Def->usedBy(Var);
108 }
109 Results.insert({&Var, LookupResult{LookupResultKind::FromWith, Def}});
110 } else {
111 // Otherwise, this variable is undefined.
112 Results.insert({&Var, LookupResult{LookupResultKind::Undefined, nullptr}});
113 Diagnostic &Diag =
114 Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.range());
115 Diag << Var.id().name();
116 }
117}
118
119void VariableLookupAnalysis::dfs(const ExprLambda &Lambda,
120 const std::shared_ptr<EnvNode> &Env) {
121 // Early exit for in-complete lambda.
122 if (!Lambda.body())
123 return;
124
125 // Create a new EnvNode, as lambdas may have formal & arg.
126 DefBuilder DBuilder;
127 assert(Lambda.arg());
128 const LambdaArg &Arg = *Lambda.arg();
129
130 // foo: body
131 // ^~~<------- add function argument.
132 if (Arg.id()) {
133 if (!Arg.formals()) {
134 ToDef.insert_or_assign(Arg.id(), DBuilder.add(Arg.id()->name(), Arg.id(),
136 // Function arg cannot duplicate to it's formal.
137 // If it this unluckily happens, we would like to skip this definition.
138 } else if (!Arg.formals()->dedup().contains(Arg.id()->name())) {
139 ToDef.insert_or_assign(Arg.id(),
140 DBuilder.add(Arg.id()->name(), Arg.id(),
142 }
143 }
144
145 // { foo, bar, ... } : body
146 // ^~~~~~~~~<-------------- add function formals.
147
148 // This section differentiates between formal parameters with an argument and
149 // without. Example:
150 //
151 // { foo }@arg : use arg
152 //
153 // In this case, the definition of `foo` is not used directly; however, it
154 // might be accessed via arg.foo. Therefore, the severity of an unused formal
155 // parameter is reduced in this scenario.
156 if (Arg.formals()) {
157 for (const auto &[Name, Formal] : Arg.formals()->dedup()) {
161 ToDef.insert_or_assign(Formal->id(),
162 DBuilder.add(Name, Formal->id(), Source));
163 }
164 }
165
166 auto NewEnv = std::make_shared<EnvNode>(Env, DBuilder.finish(), &Lambda);
167
168 if (Arg.formals()) {
169 for (const auto &Formal : Arg.formals()->members()) {
170 if (const Expr *Def = Formal->defaultExpr()) {
171 dfs(*Def, NewEnv);
172 }
173 }
174 }
175
176 dfs(*Lambda.body(), NewEnv);
177
178 emitEnvLivenessWarning(NewEnv);
179}
180
181void VariableLookupAnalysis::dfsDynamicAttrs(
182 const std::vector<Attribute> &DynamicAttrs,
183 const std::shared_ptr<EnvNode> &Env) {
184 for (const auto &Attr : DynamicAttrs) {
185 if (!Attr.value())
186 continue;
187 dfs(Attr.key(), Env);
188 dfs(*Attr.value(), Env);
189 }
190}
191
192std::shared_ptr<EnvNode> VariableLookupAnalysis::dfsAttrs(
193 const SemaAttrs &SA, const std::shared_ptr<EnvNode> &Env,
194 const Node *Syntax, Definition::DefinitionSource Source) {
195 if (SA.isRecursive()) {
196 // rec { }, or let ... in ...
197 DefBuilder DB;
198 // For each static names, create a name binding.
199 for (const auto &[Name, Attr] : SA.staticAttrs())
200 ToDef.insert_or_assign(&Attr.key(), DB.add(Name, &Attr.key(), Source));
201
202 auto NewEnv = std::make_shared<EnvNode>(Env, DB.finish(), Syntax);
203
204 for (const auto &[_, Attr] : SA.staticAttrs()) {
205 if (!Attr.value())
206 continue;
207 if (Attr.kind() == Attribute::AttributeKind::Plain ||
209 dfs(*Attr.value(), NewEnv);
210 } else {
211 assert(Attr.kind() == Attribute::AttributeKind::Inherit);
212 dfs(*Attr.value(), Env);
213 }
214 }
215
216 dfsDynamicAttrs(SA.dynamicAttrs(), NewEnv);
217 return NewEnv;
218 }
219
220 // Non-recursive. Dispatch nested node with old Env
221 for (const auto &[_, Attr] : SA.staticAttrs()) {
222 if (!Attr.value())
223 continue;
224 dfs(*Attr.value(), Env);
225 }
226
227 dfsDynamicAttrs(SA.dynamicAttrs(), Env);
228 return Env;
229};
230
231void VariableLookupAnalysis::dfs(const ExprAttrs &Attrs,
232 const std::shared_ptr<EnvNode> &Env) {
233 const SemaAttrs &SA = Attrs.sema();
234 std::shared_ptr<EnvNode> NewEnv =
235 dfsAttrs(SA, Env, &Attrs, Definition::DS_Rec);
236 if (NewEnv != Env) {
237 assert(Attrs.isRecursive() &&
238 "NewEnv must be created for recursive attrset");
239 if (!NewEnv->isLive()) {
240 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_ExtraRecursive,
241 Attrs.rec()->range());
242 D.fix("remove `rec` keyword")
243 .edit(TextEdit::mkRemoval(Attrs.rec()->range()));
245 }
246 }
247}
248
249void VariableLookupAnalysis::dfs(const ExprLet &Let,
250 const std::shared_ptr<EnvNode> &Env) {
251
252 // Obtain the env object suitable for "in" expression.
253 auto GetLetEnv = [&Env, &Let, this]() -> std::shared_ptr<EnvNode> {
254 // This is an empty let ... in ... expr, definitely anti-pattern in
255 // nix language. We want to passthrough the env then.
256 if (!Let.attrs()) {
257 return Env;
258 }
259
260 // If there are some attributes actually, create a new env.
261 const SemaAttrs &SA = Let.attrs()->sema();
262 assert(SA.isRecursive() && "let ... in ... attrset must be recursive");
263 return dfsAttrs(SA, Env, &Let, Definition::DS_Let);
264 };
265
266 auto LetEnv = GetLetEnv();
267
268 if (Let.expr())
269 dfs(*Let.expr(), LetEnv);
270 emitEnvLivenessWarning(LetEnv);
271}
272
273void VariableLookupAnalysis::trivialDispatch(
274 const Node &Root, const std::shared_ptr<EnvNode> &Env) {
275 for (const Node *Ch : Root.children()) {
276 if (!Ch)
277 continue;
278 dfs(*Ch, Env);
279 }
280}
281
282void VariableLookupAnalysis::dfs(const ExprWith &With,
283 const std::shared_ptr<EnvNode> &Env) {
284 auto NewEnv = std::make_shared<EnvNode>(Env, EnvNode::DefMap{}, &With);
285 if (!WithDefs.contains(&With)) {
286 auto NewDef =
287 std::make_shared<Definition>(&With.kwWith(), Definition::DS_With);
288 ToDef.insert_or_assign(&With.kwWith(), NewDef);
289 WithDefs.insert_or_assign(&With, NewDef);
290 }
291
292 if (With.with())
293 dfs(*With.with(), Env);
294
295 if (With.expr())
296 dfs(*With.expr(), NewEnv);
297
298 if (WithDefs.at(&With)->uses().empty()) {
299 Diagnostic &D =
300 Diags.emplace_back(Diagnostic::DK_ExtraWith, With.kwWith().range());
301 Fix &F = D.fix("remove `with` expression")
303 if (With.tokSemi())
305 if (With.with())
306 F.edit(TextEdit::mkRemoval(With.with()->range()));
307 }
308}
309
310void VariableLookupAnalysis::dfs(const Node &Root,
311 const std::shared_ptr<EnvNode> &Env) {
312 Envs.insert({&Root, Env});
313 switch (Root.kind()) {
314 case Node::NK_ExprVar: {
315 const auto &Var = static_cast<const ExprVar &>(Root);
316 lookupVar(Var, Env);
317 break;
318 }
319 case Node::NK_ExprLambda: {
320 const auto &Lambda = static_cast<const ExprLambda &>(Root);
321 dfs(Lambda, Env);
322 break;
323 }
324 case Node::NK_ExprAttrs: {
325 const auto &Attrs = static_cast<const ExprAttrs &>(Root);
326 dfs(Attrs, Env);
327 break;
328 }
329 case Node::NK_ExprLet: {
330 const auto &Let = static_cast<const ExprLet &>(Root);
331 dfs(Let, Env);
332 break;
333 }
334 case Node::NK_ExprWith: {
335 const auto &With = static_cast<const ExprWith &>(Root);
336 dfs(With, Env);
337 break;
338 }
339 default:
340 trivialDispatch(Root, Env);
341 }
342}
343
345 // Create a basic env
346 DefBuilder DB;
347 std::vector<std::string> Builtins{
348 "__add",
349 "__fetchurl",
350 "__isFloat",
351 "__seq",
352 "break",
353 "__addDrvOutputDependencies",
354 "__filter",
355 "__isFunction",
356 "__sort",
357 "builtins",
358 "__addErrorContext",
359 "__filterSource",
360 "__isInt",
361 "__split",
362 "derivation",
363 "__all",
364 "__findFile",
365 "__isList",
366 "__splitVersion",
367 "derivationStrict",
368 "__any",
369 "__flakeRefToString",
370 "__isPath",
371 "__storeDir",
372 "dirOf",
373 "__appendContext",
374 "__floor",
375 "__isString",
376 "__storePath",
377 "false",
378 "__attrNames",
379 "__foldl'",
380 "__langVersion",
381 "__stringLength",
382 "fetchGit",
383 "__attrValues",
384 "__fromJSON",
385 "__length",
386 "__sub",
387 "fetchMercurial",
388 "__bitAnd",
389 "__functionArgs",
390 "__lessThan",
391 "__substring",
392 "fetchTarball",
393 "__bitOr",
394 "__genList",
395 "__listToAttrs",
396 "__tail",
397 "fetchTree",
398 "__bitXor",
399 "__genericClosure",
400 "__mapAttrs",
401 "__toFile",
402 "fromTOML",
403 "__catAttrs",
404 "__getAttr",
405 "__match",
406 "__toJSON",
407 "import",
408 "__ceil",
409 "__getContext",
410 "__mul",
411 "__toPath",
412 "isNull",
413 "__compareVersions",
414 "__getEnv",
415 "__nixPath",
416 "__toXML",
417 "map",
418 "__concatLists",
419 "__getFlake",
420 "__nixVersion",
421 "__trace",
422 "null",
423 "__concatMap",
424 "__groupBy",
425 "__parseDrvName",
426 "__traceVerbose",
427 "placeholder",
428 "__concatStringsSep",
429 "__hasAttr",
430 "__parseFlakeRef",
431 "__tryEval",
432 "removeAttrs",
433 "__convertHash",
434 "__hasContext",
435 "__partition",
436 "__typeOf",
437 "scopedImport",
438 "__currentSystem",
439 "__hashFile",
440 "__path",
441 "__unsafeDiscardOutputDependency",
442 "throw",
443 "__currentTime",
444 "__hashString",
445 "__pathExists",
446 "__unsafeDiscardStringContext",
447 "toString",
448 "__deepSeq",
449 "__head",
450 "__readDir",
451 "__unsafeGetAttrPos",
452 "true",
453 "__div",
454 "__intersectAttrs",
455 "__readFile",
456 "__zipAttrsWith",
457 "__elem",
458 "__isAttrs",
459 "__readFileType",
460 "abort",
461 "__elemAt",
462 "__isBool",
463 "__replaceStrings",
464 "baseNameOf",
465 };
466
467 for (const auto &Builtin : Builtins)
468 DB.addBuiltin(Builtin);
469
470 auto Env = std::make_shared<EnvNode>(nullptr, DB.finish(), nullptr);
471
472 dfs(Root, Env);
473}
474
476 : Diags(Diags) {}
477
479 if (!Envs.contains(N))
480 return nullptr;
481 return Envs.at(N).get();
482}
Lookup variable names, from it's parent scope.
@ InheritFrom
inherit (expr) a b c
DefinitionSource
"Source" information so we can know where the def comes from.
@ DS_Rec
From recursive attribute set. e.g. rec { }.
@ DS_LambdaArg
From ambda arg e.g. a: a + 1.
@ DS_LambdaNoArg_Formal
From lambda (noarg) formal, e.g. { a }: a + 1.
@ DS_Builtin
Builtin names.
@ DS_LambdaWithArg_Arg
From lambda (with @arg) arg, e.g. a in { foo }@a: foo + 1
@ DS_With
From with <expr>;.
@ DS_LambdaWithArg_Formal
From lambda (with @arg) formal, e.g. foo in { foo }@a: foo + 1
@ DS_Let
From let ... in ...
Fix & fix(std::string Message)
Definition Diagnostic.h:203
A set of variable definitions, which may inherit parent environment.
bool isLive() const
std::map< std::string, std::shared_ptr< Definition > > DefMap
bool isRecursive() const
Definition Attrs.h:282
const SemaAttrs & sema() const
Definition Attrs.h:284
const Misc * rec() const
Definition Attrs.h:280
Expr * body() const
Definition Lambda.h:119
LambdaArg * arg() const
Definition Lambda.h:118
const ExprAttrs * attrs() const
Definition Expr.h:147
const Expr * expr() const
Definition Expr.h:148
const Identifier & id() const
Definition Simple.h:200
const Misc & kwWith() const
Definition Expr.h:170
Expr * with() const
Definition Expr.h:172
const Misc * tokSemi() const
Definition Expr.h:171
Expr * expr() const
Definition Expr.h:173
Fix & edit(TextEdit Edit)
Definition Diagnostic.h:65
Identifier * id() const
Definition Lambda.h:37
Expr * defaultExpr() const
Definition Lambda.h:41
const FormalVector & members() const
Definition Lambda.h:71
const std::map< std::string, const Formal * > & dedup()
Deduplicated formals.
Definition Lambda.h:74
const std::string & name() const
Definition Basic.h:120
Formals * formals() const
Definition Lambda.h:101
Identifier * id() const
Definition Lambda.h:99
NodeKind kind() const
Definition Basic.h:34
LexerCursorRange range() const
Definition Basic.h:35
virtual ChildVector children() const =0
void tag(DiagnosticTag Tag)
Definition Diagnostic.h:96
Attribute set after deduplication.
Definition Attrs.h:231
bool isRecursive() const
If the attribute set is rec.
Definition Attrs.h:264
const std::vector< Attribute > & dynamicAttrs() const
Dynamic attributes, require evaluation to get the key.
Definition Attrs.h:259
const std::map< std::string, Attribute > & staticAttrs() const
Static attributes, do not require evaluation to get the key.
Definition Attrs.h:252
static TextEdit mkRemoval(LexerCursorRange RemovingRange)
Definition Diagnostic.h:39
const EnvNode * env(const Node *N) const
void runOnAST(const Node &Root)
Perform variable lookup analysis (def-use) on AST.
VariableLookupAnalysis(std::vector< Diagnostic > &Diags)