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 std::string &Name = Var.id().name();
80
81 bool EnclosedWith = false; // If there is a "With" enclosed this var name.
82 EnvNode *WithEnv = nullptr;
83 EnvNode *CurEnv = Env.get();
84 std::shared_ptr<Definition> Def;
85 for (; CurEnv; CurEnv = CurEnv->parent()) {
86 if (CurEnv->defs().contains(Name)) {
87 Def = CurEnv->defs().at(Name);
88 break;
89 }
90 // Find the most nested `with` expression, and set uses.
91 if (CurEnv->isWith() && !EnclosedWith) {
92 EnclosedWith = true;
93 WithEnv = CurEnv;
94 }
95 }
96
97 if (Def) {
98 Def->usedBy(Var);
99 Results.insert({&Var, LookupResult{LookupResultKind::Defined, Def}});
100 return;
101 }
102 if (EnclosedWith) {
103 Def = WithDefs.at(WithEnv->syntax());
104 Def->usedBy(Var);
105 Results.insert({&Var, LookupResult{LookupResultKind::FromWith, Def}});
106 return;
107 }
108
109 // Otherwise, this variable is undefined.
110 Results.insert({&Var, LookupResult{LookupResultKind::Undefined, nullptr}});
111 Diagnostic &Diag =
112 Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.range());
113 Diag << Var.id().name();
114}
115
116void VariableLookupAnalysis::dfs(const ExprLambda &Lambda,
117 const std::shared_ptr<EnvNode> &Env) {
118 // Early exit for in-complete lambda.
119 if (!Lambda.body())
120 return;
121
122 // Create a new EnvNode, as lambdas may have formal & arg.
123 DefBuilder DBuilder;
124 assert(Lambda.arg());
125 const LambdaArg &Arg = *Lambda.arg();
126
127 // foo: body
128 // ^~~<------- add function argument.
129 if (Arg.id()) {
130 if (!Arg.formals()) {
131 ToDef.insert_or_assign(Arg.id(), DBuilder.add(Arg.id()->name(), Arg.id(),
133 // Function arg cannot duplicate to it's formal.
134 // If it this unluckily happens, we would like to skip this definition.
135 } else if (!Arg.formals()->dedup().contains(Arg.id()->name())) {
136 ToDef.insert_or_assign(Arg.id(),
137 DBuilder.add(Arg.id()->name(), Arg.id(),
139 }
140 }
141
142 // { foo, bar, ... } : body
143 // ^~~~~~~~~<-------------- add function formals.
144
145 // This section differentiates between formal parameters with an argument and
146 // without. Example:
147 //
148 // { foo }@arg : use arg
149 //
150 // In this case, the definition of `foo` is not used directly; however, it
151 // might be accessed via arg.foo. Therefore, the severity of an unused formal
152 // parameter is reduced in this scenario.
153 if (Arg.formals()) {
154 for (const auto &[Name, Formal] : Arg.formals()->dedup()) {
158 ToDef.insert_or_assign(Formal->id(),
159 DBuilder.add(Name, Formal->id(), Source));
160 }
161 }
162
163 auto NewEnv = std::make_shared<EnvNode>(Env, DBuilder.finish(), &Lambda);
164
165 if (Arg.formals()) {
166 for (const auto &Formal : Arg.formals()->members()) {
167 if (const Expr *Def = Formal->defaultExpr()) {
168 dfs(*Def, NewEnv);
169 }
170 }
171 }
172
173 dfs(*Lambda.body(), NewEnv);
174
175 emitEnvLivenessWarning(NewEnv);
176}
177
178void VariableLookupAnalysis::dfsDynamicAttrs(
179 const std::vector<Attribute> &DynamicAttrs,
180 const std::shared_ptr<EnvNode> &Env) {
181 for (const auto &Attr : DynamicAttrs) {
182 if (!Attr.value())
183 continue;
184 dfs(Attr.key(), Env);
185 dfs(*Attr.value(), Env);
186 }
187}
188
189std::shared_ptr<EnvNode> VariableLookupAnalysis::dfsAttrs(
190 const SemaAttrs &SA, const std::shared_ptr<EnvNode> &Env,
191 const Node *Syntax, Definition::DefinitionSource Source) {
192 if (SA.isRecursive()) {
193 // rec { }, or let ... in ...
194 DefBuilder DB;
195 // For each static names, create a name binding.
196 for (const auto &[Name, Attr] : SA.staticAttrs())
197 ToDef.insert_or_assign(&Attr.key(), DB.add(Name, &Attr.key(), Source));
198
199 auto NewEnv = std::make_shared<EnvNode>(Env, DB.finish(), Syntax);
200
201 for (const auto &[_, Attr] : SA.staticAttrs()) {
202 if (!Attr.value())
203 continue;
204 if (Attr.kind() == Attribute::AttributeKind::Plain ||
206 dfs(*Attr.value(), NewEnv);
207 } else {
208 assert(Attr.kind() == Attribute::AttributeKind::Inherit);
209 dfs(*Attr.value(), Env);
210 }
211 }
212
213 dfsDynamicAttrs(SA.dynamicAttrs(), NewEnv);
214 return NewEnv;
215 }
216
217 // Non-recursive. Dispatch nested node with old Env
218 for (const auto &[_, Attr] : SA.staticAttrs()) {
219 if (!Attr.value())
220 continue;
221 dfs(*Attr.value(), Env);
222 }
223
224 dfsDynamicAttrs(SA.dynamicAttrs(), Env);
225 return Env;
226};
227
228void VariableLookupAnalysis::dfs(const ExprAttrs &Attrs,
229 const std::shared_ptr<EnvNode> &Env) {
230 const SemaAttrs &SA = Attrs.sema();
231 std::shared_ptr<EnvNode> NewEnv =
232 dfsAttrs(SA, Env, &Attrs, Definition::DS_Rec);
233 if (NewEnv != Env) {
234 assert(Attrs.isRecursive() &&
235 "NewEnv must be created for recursive attrset");
236 if (!NewEnv->isLive()) {
237 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_ExtraRecursive,
238 Attrs.rec()->range());
239 D.fix("remove `rec` keyword")
240 .edit(TextEdit::mkRemoval(Attrs.rec()->range()));
242 }
243 }
244}
245
246void VariableLookupAnalysis::dfs(const ExprLet &Let,
247 const std::shared_ptr<EnvNode> &Env) {
248
249 // Obtain the env object suitable for "in" expression.
250 auto GetLetEnv = [&Env, &Let, this]() -> std::shared_ptr<EnvNode> {
251 // This is an empty let ... in ... expr, definitely anti-pattern in
252 // nix language. We want to passthrough the env then.
253 if (!Let.attrs()) {
254 return Env;
255 }
256
257 // If there are some attributes actually, create a new env.
258 const SemaAttrs &SA = Let.attrs()->sema();
259 assert(SA.isRecursive() && "let ... in ... attrset must be recursive");
260 return dfsAttrs(SA, Env, &Let, Definition::DS_Let);
261 };
262
263 auto LetEnv = GetLetEnv();
264
265 if (Let.expr())
266 dfs(*Let.expr(), LetEnv);
267 emitEnvLivenessWarning(LetEnv);
268}
269
270void VariableLookupAnalysis::trivialDispatch(
271 const Node &Root, const std::shared_ptr<EnvNode> &Env) {
272 for (const Node *Ch : Root.children()) {
273 if (!Ch)
274 continue;
275 dfs(*Ch, Env);
276 }
277}
278
279void VariableLookupAnalysis::dfs(const ExprWith &With,
280 const std::shared_ptr<EnvNode> &Env) {
281 auto NewEnv = std::make_shared<EnvNode>(Env, EnvNode::DefMap{}, &With);
282 if (!WithDefs.contains(&With)) {
283 auto NewDef =
284 std::make_shared<Definition>(&With.kwWith(), Definition::DS_With);
285 ToDef.insert_or_assign(&With.kwWith(), NewDef);
286 WithDefs.insert_or_assign(&With, NewDef);
287 }
288
289 if (With.with())
290 dfs(*With.with(), Env);
291
292 if (With.expr())
293 dfs(*With.expr(), NewEnv);
294
295 if (WithDefs.at(&With)->uses().empty()) {
296 Diagnostic &D =
297 Diags.emplace_back(Diagnostic::DK_ExtraWith, With.kwWith().range());
298 Fix &F = D.fix("remove `with` expression")
300 if (With.tokSemi())
302 if (With.with())
303 F.edit(TextEdit::mkRemoval(With.with()->range()));
304 }
305}
306
307void VariableLookupAnalysis::dfs(const Node &Root,
308 const std::shared_ptr<EnvNode> &Env) {
309 Envs.insert({&Root, Env});
310 switch (Root.kind()) {
311 case Node::NK_ExprVar: {
312 const auto &Var = static_cast<const ExprVar &>(Root);
313 lookupVar(Var, Env);
314 break;
315 }
316 case Node::NK_ExprLambda: {
317 const auto &Lambda = static_cast<const ExprLambda &>(Root);
318 dfs(Lambda, Env);
319 break;
320 }
321 case Node::NK_ExprAttrs: {
322 const auto &Attrs = static_cast<const ExprAttrs &>(Root);
323 dfs(Attrs, Env);
324 break;
325 }
326 case Node::NK_ExprLet: {
327 const auto &Let = static_cast<const ExprLet &>(Root);
328 dfs(Let, Env);
329 break;
330 }
331 case Node::NK_ExprWith: {
332 const auto &With = static_cast<const ExprWith &>(Root);
333 dfs(With, Env);
334 break;
335 }
336 default:
337 trivialDispatch(Root, Env);
338 }
339}
340
342 // Create a basic env
343 DefBuilder DB;
344 std::vector<std::string> Builtins{
345 "__add",
346 "__fetchurl",
347 "__isFloat",
348 "__seq",
349 "break",
350 "__addDrvOutputDependencies",
351 "__filter",
352 "__isFunction",
353 "__sort",
354 "builtins",
355 "__addErrorContext",
356 "__filterSource",
357 "__isInt",
358 "__split",
359 "derivation",
360 "__all",
361 "__findFile",
362 "__isList",
363 "__splitVersion",
364 "derivationStrict",
365 "__any",
366 "__flakeRefToString",
367 "__isPath",
368 "__storeDir",
369 "dirOf",
370 "__appendContext",
371 "__floor",
372 "__isString",
373 "__storePath",
374 "false",
375 "__attrNames",
376 "__foldl'",
377 "__langVersion",
378 "__stringLength",
379 "fetchGit",
380 "__attrValues",
381 "__fromJSON",
382 "__length",
383 "__sub",
384 "fetchMercurial",
385 "__bitAnd",
386 "__functionArgs",
387 "__lessThan",
388 "__substring",
389 "fetchTarball",
390 "__bitOr",
391 "__genList",
392 "__listToAttrs",
393 "__tail",
394 "fetchTree",
395 "__bitXor",
396 "__genericClosure",
397 "__mapAttrs",
398 "__toFile",
399 "fromTOML",
400 "__catAttrs",
401 "__getAttr",
402 "__match",
403 "__toJSON",
404 "import",
405 "__ceil",
406 "__getContext",
407 "__mul",
408 "__toPath",
409 "isNull",
410 "__compareVersions",
411 "__getEnv",
412 "__nixPath",
413 "__toXML",
414 "map",
415 "__concatLists",
416 "__getFlake",
417 "__nixVersion",
418 "__trace",
419 "null",
420 "__concatMap",
421 "__groupBy",
422 "__parseDrvName",
423 "__traceVerbose",
424 "placeholder",
425 "__concatStringsSep",
426 "__hasAttr",
427 "__parseFlakeRef",
428 "__tryEval",
429 "removeAttrs",
430 "__convertHash",
431 "__hasContext",
432 "__partition",
433 "__typeOf",
434 "scopedImport",
435 "__currentSystem",
436 "__hashFile",
437 "__path",
438 "__unsafeDiscardOutputDependency",
439 "throw",
440 "__currentTime",
441 "__hashString",
442 "__pathExists",
443 "__unsafeDiscardStringContext",
444 "toString",
445 "__deepSeq",
446 "__head",
447 "__readDir",
448 "__unsafeGetAttrPos",
449 "true",
450 "__div",
451 "__intersectAttrs",
452 "__readFile",
453 "__zipAttrsWith",
454 "__elem",
455 "__isAttrs",
456 "__readFileType",
457 "abort",
458 "__elemAt",
459 "__isBool",
460 "__replaceStrings",
461 "baseNameOf",
462 };
463
464 for (const auto &Builtin : Builtins)
465 DB.addBuiltin(Builtin);
466
467 auto Env = std::make_shared<EnvNode>(nullptr, DB.finish(), nullptr);
468
469 dfs(Root, Env);
470}
471
473 : Diags(Diags) {}
474
476 if (!Envs.contains(N))
477 return nullptr;
478 return Envs.at(N).get();
479}
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 isWith() const
const Node * syntax() const
Where this node comes from.
bool isLive() const
std::map< std::string, std::shared_ptr< Definition > > DefMap
EnvNode * parent() const
const DefMap & defs() const
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 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
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
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)