15std::set<std::string> Constants{
16 "true",
"false",
"null",
17 "__currentTime",
"__currentSystem",
"__nixVersion",
18 "__storeDir",
"__langVersion",
"__importNative",
19 "__traceVerbose",
"__nixPath",
"derivation",
26 std::vector<Diagnostic> &Diags;
28 std::shared_ptr<Definition> addSimple(std::string Name,
const Node *Entry,
30 assert(!Def.contains(Name));
31 auto NewDef = std::make_shared<Definition>(Entry, Source);
32 Def.insert({std::move(Name), NewDef});
37 DefBuilder(std::vector<Diagnostic> &Diags) : Diags(Diags) {}
39 void addBuiltin(std::string Name) {
44 [[nodiscard(
"Record ToDef Map!")]] std::shared_ptr<Definition>
46 bool IsInheritFromBuiltin) {
48 if (PrimOpLookup == PrimopLookupResult::Found && !IsInheritFromBuiltin) {
51 Diags.emplace_back(Diagnostic::DK_PrimOpOverridden, Entry->
range());
56 if (Constants.contains(Name)) {
58 Diags.emplace_back(Diagnostic::DK_ConstantOverridden, Entry->
range());
62 return addSimple(std::move(Name), Entry, Source);
73bool checkInheritedFromBuiltin(
const Attribute &Attr) {
77 assert(Attr.
value() &&
78 "select expr desugared from inherit should not be null");
79 assert(Attr.
value()->
kind() == Node::NK_ExprSelect &&
80 "desugared inherited from should be a select expr");
82 if (Select.expr().kind() == Node::NK_ExprVar) {
83 const auto &Var =
static_cast<const ExprVar &
>(Select.expr());
84 return Var.
id().
name() ==
"builtins";
89bool isBuiltinConstant(
const std::string &Name) {
90 if (Name.starts_with(
"_"))
92 return Constants.contains(Name) || Constants.contains(
"__" + Name);
98 for (
const auto &[_, D] : Defs) {
99 if (!D->uses().empty())
105void VariableLookupAnalysis::emitEnvLivenessWarning(
106 const std::shared_ptr<EnvNode> &NewEnv) {
107 for (
const auto &[Name, Def] : NewEnv->defs()) {
119 if (Def->uses().empty()) {
120 Diagnostic::DiagnosticKind Kind = [&]() {
121 switch (Def->source()) {
123 return Diagnostic::DK_UnusedDefLet;
125 return Diagnostic::DK_UnusedDefLambdaNoArg_Formal;
127 return Diagnostic::DK_UnusedDefLambdaWithArg_Formal;
129 return Diagnostic::DK_UnusedDefLambdaWithArg_Arg;
131 assert(
false &&
"liveness diagnostic encountered an unknown source!");
132 __builtin_unreachable();
135 Diagnostic &D = Diags.emplace_back(Kind, Def->syntax()->range());
141 const Node *LetNode = NewEnv->syntax();
142 if (LetNode && LetNode->
kind() == Node::NK_ExprLet) {
143 const auto &Let =
static_cast<const ExprLet &
>(*LetNode);
145 for (
const auto &BindNode : Let.binds()->bindings()) {
147 if (BindNode->kind() != Node::NK_Binding)
150 const auto &Bind =
static_cast<const Binding &
>(*BindNode);
151 const auto &PathNames = Bind.path().names();
152 if (PathNames.empty())
155 const auto &FirstName = PathNames[0];
161 if (D.
range().
lCur() == FirstName->range().lCur() &&
162 D.
range().
rCur() == FirstName->range().rCur()) {
163 D.
fix(
"remove unused binding")
175void VariableLookupAnalysis::lookupVar(
const ExprVar &Var,
176 const std::shared_ptr<EnvNode> &Env) {
177 const auto &Name = Var.
id().
name();
178 const auto *CurEnv = Env.get();
179 std::shared_ptr<Definition> Def;
180 std::vector<const EnvNode *> WithEnvs;
181 for (; CurEnv; CurEnv = CurEnv->parent()) {
182 if (CurEnv->defs().contains(Name)) {
183 Def = CurEnv->defs().at(Name);
194 if (CurEnv->isWith()) {
195 WithEnvs.emplace_back(CurEnv);
202 }
else if (!WithEnvs.empty()) {
206 std::vector<const ExprWith *> WithScopes;
207 for (
const auto *WithEnv : WithEnvs) {
208 Def = WithDefs.at(WithEnv->syntax());
210 WithScopes.push_back(
static_cast<const ExprWith *
>(WithEnv->syntax()));
212 VarWithScopes.insert({&Var, std::move(WithScopes)});
218 assert(
false &&
"primop name should be defined");
222 Diags.emplace_back(Diagnostic::DK_PrimOpNeedsPrefix, Var.
range());
223 D.
fix(
"use `builtins.` prefix")
234 Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.
range());
241void VariableLookupAnalysis::dfs(
const ExprLambda &Lambda,
242 const std::shared_ptr<EnvNode> &Env) {
248 DefBuilder DBuilder(Diags);
249 assert(Lambda.
arg());
250 const LambdaArg &Arg = *Lambda.
arg();
256 ToDef.insert_or_assign(Arg.
id(),
257 DBuilder.add(Arg.
id()->
name(), Arg.
id(),
263 ToDef.insert_or_assign(Arg.
id(),
264 DBuilder.add(Arg.
id()->
name(), Arg.
id(),
282 for (
const auto &[Name, Formal] : Arg.
formals()->
dedup()) {
284 if (&Lambda == OutputsLambda && FlakeInjected.contains(Name))
289 ToDef.insert_or_assign(Formal->id(),
290 DBuilder.add(Name, Formal->id(), Source,
295 auto NewEnv = std::make_shared<EnvNode>(Env, DBuilder.finish(), &Lambda);
299 if (
const Expr *Def = Formal->defaultExpr()) {
305 dfs(*Lambda.
body(), NewEnv);
307 emitEnvLivenessWarning(NewEnv);
310void VariableLookupAnalysis::dfsDynamicAttrs(
311 const std::vector<Attribute> &DynamicAttrs,
312 const std::shared_ptr<EnvNode> &Env) {
313 for (
const auto &Attr : DynamicAttrs) {
316 dfs(Attr.
key(), Env);
317 dfs(*Attr.
value(), Env);
321std::shared_ptr<EnvNode> VariableLookupAnalysis::dfsAttrs(
322 const SemaAttrs &SA,
const std::shared_ptr<EnvNode> &Env,
326 DefBuilder DB(Diags);
328 for (
const auto &[Name, Attr] : SA.
staticAttrs()) {
329 ToDef.insert_or_assign(
331 DB.add(Name, &Attr.
key(), Source, checkInheritedFromBuiltin(Attr)));
334 auto NewEnv = std::make_shared<EnvNode>(Env, DB.finish(), Syntax);
341 dfs(*Attr.
value(), NewEnv);
344 dfs(*Attr.
value(), Env);
356 dfs(*Attr.
value(), Env);
363void VariableLookupAnalysis::dfs(
const ExprAttrs &Attrs,
364 const std::shared_ptr<EnvNode> &Env) {
365 const SemaAttrs &SA = Attrs.
sema();
366 std::shared_ptr<EnvNode> NewEnv =
370 "NewEnv must be created for recursive attrset");
371 if (!NewEnv->isLive()) {
372 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_ExtraRecursive,
374 D.
fix(
"remove `rec` keyword")
381void VariableLookupAnalysis::dfs(
const ExprLet &Let,
382 const std::shared_ptr<EnvNode> &Env) {
385 auto GetLetEnv = [&Env, &Let,
this]() -> std::shared_ptr<EnvNode> {
394 const SemaAttrs &SA = Let.
attrs()->
sema();
395 assert(SA.
isRecursive() &&
"let ... in ... attrset must be recursive");
396 checkLetInheritBuiltins(SA);
400 auto LetEnv = GetLetEnv();
403 dfs(*Let.
expr(), LetEnv);
404 emitEnvLivenessWarning(LetEnv);
407void VariableLookupAnalysis::dfs(
const ExprLegacyLet &LegacyLet,
408 const std::shared_ptr<EnvNode> &Env) {
409 if (LegacyLet.
attrs())
410 dfs(*LegacyLet.
attrs(), Env);
413void VariableLookupAnalysis::trivialDispatch(
414 const Node &Root,
const std::shared_ptr<EnvNode> &Env) {
415 for (
const Node *Ch : Root.
children()) {
422void VariableLookupAnalysis::dfs(
const ExprWith &With,
423 const std::shared_ptr<EnvNode> &Env) {
424 auto NewEnv = std::make_shared<EnvNode>(Env,
EnvNode::DefMap{}, &With);
425 if (!WithDefs.contains(&With)) {
428 ToDef.insert_or_assign(&With.
kwWith(), NewDef);
429 WithDefs.insert_or_assign(&With, NewDef);
433 dfs(*With.
with(), Env);
436 dfs(*With.
expr(), NewEnv);
438 if (WithDefs.at(&With)->uses().empty()) {
440 Diags.emplace_back(Diagnostic::DK_ExtraWith, With.
kwWith().
range());
441 Fix &F = D.
fix(
"remove `with` expression")
450void VariableLookupAnalysis::checkBuiltins(
const ExprSelect &Sel) {
458 if (Sel.
expr().
kind() != Node::NK_ExprVar)
461 const auto &Builtins =
static_cast<const ExprVar &
>(Sel.
expr());
462 if (Builtins.id().name() !=
"builtins")
465 const auto &AP = *Sel.
path();
467 if (AP.names().size() != 1)
470 AttrName &First = *AP.
names()[0];
478 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_PrimOpRemovablePrefix,
481 D.
fix(
"remove `builtins.` prefix")
493 if (!isBuiltinConstant(Name)) {
494 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_PrimOpUnknown,
495 AP.names()[0]->range());
502void VariableLookupAnalysis::checkLetInheritBuiltins(
const SemaAttrs &SA) {
503 for (
const auto &[Name, Attr] : SA.
staticAttrs()) {
504 if (!checkInheritedFromBuiltin(Attr))
509 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_PrimOpRemovablePrefix,
511 D.
fix(
"remove unnecessary inherit")
517void VariableLookupAnalysis::dfs(
const Node &Root,
518 const std::shared_ptr<EnvNode> &Env) {
519 Envs.insert({&Root, Env});
520 switch (Root.
kind()) {
521 case Node::NK_ExprVar: {
522 const auto &Var =
static_cast<const ExprVar &
>(Root);
526 case Node::NK_ExprLambda: {
527 const auto &Lambda =
static_cast<const ExprLambda &
>(Root);
531 case Node::NK_ExprAttrs: {
532 const auto &Attrs =
static_cast<const ExprAttrs &
>(Root);
536 case Node::NK_ExprLet: {
537 const auto &Let =
static_cast<const ExprLet &
>(Root);
541 case Node::NK_ExprLegacyLet: {
542 const auto &LegacyLet =
static_cast<const ExprLegacyLet &
>(Root);
546 case Node::NK_ExprWith: {
547 const auto &With =
static_cast<const ExprWith &
>(Root);
551 case Node::NK_ExprSelect: {
552 trivialDispatch(Root, Env);
553 const auto &Sel =
static_cast<const ExprSelect &
>(Root);
558 trivialDispatch(Root, Env);
564 DefBuilder DB(Diags);
567 if (!Info.Internal) {
573 for (
const auto &Builtin : Constants)
574 DB.addBuiltin(Builtin);
576 DB.addBuiltin(
"builtins");
578 DB.addBuiltin(std::string(
"__curPos"));
580 auto Env = std::make_shared<EnvNode>(
nullptr, DB.finish(),
nullptr);
583 collectFlakeInjected(Root);
592 if (!Envs.contains(N))
594 return Envs.at(N).get();
599void VariableLookupAnalysis::collectFlakeInjected(
const Node &Root) {
600 if (Root.
kind() != Node::NK_ExprAttrs)
602 const auto &S =
static_cast<const ExprAttrs &
>(Root).sema().staticAttrs();
604 FlakeInjected.insert(
"self");
605 if (
auto It = S.find(
"inputs"); It != S.end())
606 if (
const auto *V = It->second.value();
607 V && V->kind() == Node::NK_ExprAttrs) {
610 std::ranges::copy(std::views::keys(Keys),
611 std::inserter(FlakeInjected, FlakeInjected.end()));
614 auto It = S.find(
"outputs");
617 const auto *V = It->second.value();
618 if (!V || V->kind() != Node::NK_ExprLambda)
620 const auto &L =
static_cast<const ExprLambda &
>(*V);
621 if (L.arg() && L.arg()->formals())
Lookup variable names, from it's parent scope.
const std::string & staticName() const
const std::vector< std::shared_ptr< AttrName > > & names() const
AttributeKind kind() const
@ InheritFrom
inherit (expr) a b c
DefinitionSource
"Source" information so we can know where the def comes from.
@ DS_FlakeInjectedFormal
Formal in flake outputs injected by call-flake.nix.
@ 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)
A set of variable definitions, which may inherit parent environment.
std::map< std::string, std::shared_ptr< Definition > > DefMap
const SemaAttrs & sema() const
const ExprAttrs * attrs() const
const ExprAttrs * attrs() const
const Expr * expr() const
const Expr * desugaredFrom() const
const Identifier & id() const
const Misc & kwWith() const
const Misc * tokSemi() const
Fix & edit(TextEdit Edit)
const std::string & name() const
Formals * formals() const
LexerCursorRange range() const
virtual ChildVector children() const =0
void tag(DiagnosticTag Tag)
LexerCursorRange range() const
Attribute set after deduplication.
bool isRecursive() const
If the attribute set is rec.
const std::vector< Attribute > & dynamicAttrs() const
Dynamic attributes, require evaluation to get the key.
const std::map< std::string, Attribute > & staticAttrs() const
Static attributes, do not require evaluation to get the key.
static TextEdit mkRemoval(LexerCursorRange RemovingRange)
static TextEdit mkInsertion(LexerCursor P, std::string NewText)
const EnvNode * env(const Node *N) const
void runOnAST(const Node &Root, bool IsFlake=false)
Perform variable lookup analysis (def-use) on AST.
VariableLookupAnalysis(std::vector< Diagnostic > &Diags)
PrimopLookupResult lookupGlobalPrimOpInfo(const std::string &Name)
Look up information about a global primop by name.
std::map< std::string, nixf::PrimOpInfo > PrimOpsInfo
@ PrefixedFound
The primop was found, but needs "builtin." prefix.
@ NotFound
The primop was not found.
@ Found
The primop was found with an exact match.