13std::set<std::string> Constants{
14 "true",
"false",
"null",
15 "__currentTime",
"__currentSystem",
"__nixVersion",
16 "__storeDir",
"__langVersion",
"__importNative",
17 "__traceVerbose",
"__nixPath",
"derivation",
24 std::vector<Diagnostic> &Diags;
26 std::shared_ptr<Definition> addSimple(std::string Name,
const Node *Entry,
28 assert(!Def.contains(Name));
29 auto NewDef = std::make_shared<Definition>(Entry, Source);
30 Def.insert({std::move(Name), NewDef});
35 DefBuilder(std::vector<Diagnostic> &Diags) : Diags(Diags) {}
37 void addBuiltin(std::string Name) {
42 [[nodiscard(
"Record ToDef Map!")]] std::shared_ptr<Definition>
43 add(std::string Name,
const Node *Entry,
46 if (PrimOpLookup != PrimopLookupResult::NotFound) {
49 Diags.emplace_back(Diagnostic::DK_PrimOpOverridden, Entry->
range());
54 if (Constants.contains(Name)) {
56 Diags.emplace_back(Diagnostic::DK_ConstantOverridden, Entry->
range());
60 return addSimple(std::move(Name), Entry, Source);
69 for (
const auto &[_, D] : Defs) {
70 if (!D->uses().empty())
76void VariableLookupAnalysis::emitEnvLivenessWarning(
77 const std::shared_ptr<EnvNode> &NewEnv) {
78 for (
const auto &[Name, Def] : NewEnv->defs()) {
87 if (Def->uses().empty()) {
88 Diagnostic::DiagnosticKind Kind = [&]() {
89 switch (Def->source()) {
91 return Diagnostic::DK_UnusedDefLet;
93 return Diagnostic::DK_UnusedDefLambdaNoArg_Formal;
95 return Diagnostic::DK_UnusedDefLambdaWithArg_Formal;
97 return Diagnostic::DK_UnusedDefLambdaWithArg_Arg;
99 assert(
false &&
"liveness diagnostic encountered an unknown source!");
100 __builtin_unreachable();
103 Diagnostic &D = Diags.emplace_back(Kind, Def->syntax()->range());
110void VariableLookupAnalysis::lookupVar(
const ExprVar &Var,
111 const std::shared_ptr<EnvNode> &Env) {
112 const auto &Name = Var.
id().
name();
113 const auto *CurEnv = Env.get();
114 std::shared_ptr<Definition> Def;
115 std::vector<const EnvNode *> WithEnvs;
116 for (; CurEnv; CurEnv = CurEnv->parent()) {
117 if (CurEnv->defs().contains(Name)) {
118 Def = CurEnv->defs().at(Name);
129 if (CurEnv->isWith()) {
130 WithEnvs.emplace_back(CurEnv);
137 }
else if (!WithEnvs.empty()) {
138 for (
const auto *WithEnv : WithEnvs) {
139 Def = WithDefs.at(WithEnv->syntax());
147 assert(
false &&
"primop name should be defined");
151 Diags.emplace_back(Diagnostic::DK_PrimOpNeedsPrefix, Var.
range());
152 D.
fix(
"use `builtins.` prefix")
163 Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.
range());
170void VariableLookupAnalysis::dfs(
const ExprLambda &Lambda,
171 const std::shared_ptr<EnvNode> &Env) {
177 DefBuilder DBuilder(Diags);
178 assert(Lambda.
arg());
179 const LambdaArg &Arg = *Lambda.
arg();
185 ToDef.insert_or_assign(Arg.
id(), DBuilder.add(Arg.
id()->
name(), Arg.
id(),
190 ToDef.insert_or_assign(Arg.
id(),
191 DBuilder.add(Arg.
id()->
name(), Arg.
id(),
208 for (
const auto &[Name, Formal] : Arg.
formals()->
dedup()) {
212 ToDef.insert_or_assign(Formal->id(),
213 DBuilder.add(Name, Formal->id(), Source));
217 auto NewEnv = std::make_shared<EnvNode>(Env, DBuilder.finish(), &Lambda);
221 if (
const Expr *Def = Formal->defaultExpr()) {
227 dfs(*Lambda.
body(), NewEnv);
229 emitEnvLivenessWarning(NewEnv);
232void VariableLookupAnalysis::dfsDynamicAttrs(
233 const std::vector<Attribute> &DynamicAttrs,
234 const std::shared_ptr<EnvNode> &Env) {
235 for (
const auto &Attr : DynamicAttrs) {
238 dfs(Attr.key(), Env);
239 dfs(*Attr.value(), Env);
243std::shared_ptr<EnvNode> VariableLookupAnalysis::dfsAttrs(
244 const SemaAttrs &SA,
const std::shared_ptr<EnvNode> &Env,
248 DefBuilder DB(Diags);
251 ToDef.insert_or_assign(&Attr.key(), DB.add(Name, &Attr.key(), Source));
253 auto NewEnv = std::make_shared<EnvNode>(Env, DB.finish(), Syntax);
260 dfs(*Attr.value(), NewEnv);
263 dfs(*Attr.value(), Env);
275 dfs(*Attr.value(), Env);
282void VariableLookupAnalysis::dfs(
const ExprAttrs &Attrs,
283 const std::shared_ptr<EnvNode> &Env) {
284 const SemaAttrs &SA = Attrs.
sema();
285 std::shared_ptr<EnvNode> NewEnv =
289 "NewEnv must be created for recursive attrset");
290 if (!NewEnv->isLive()) {
291 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_ExtraRecursive,
293 D.
fix(
"remove `rec` keyword")
300void VariableLookupAnalysis::dfs(
const ExprLet &Let,
301 const std::shared_ptr<EnvNode> &Env) {
304 auto GetLetEnv = [&Env, &Let,
this]() -> std::shared_ptr<EnvNode> {
312 const SemaAttrs &SA = Let.
attrs()->
sema();
313 assert(SA.
isRecursive() &&
"let ... in ... attrset must be recursive");
317 auto LetEnv = GetLetEnv();
320 dfs(*Let.
expr(), LetEnv);
321 emitEnvLivenessWarning(LetEnv);
324void VariableLookupAnalysis::trivialDispatch(
325 const Node &Root,
const std::shared_ptr<EnvNode> &Env) {
326 for (
const Node *Ch : Root.
children()) {
333void VariableLookupAnalysis::dfs(
const ExprWith &With,
334 const std::shared_ptr<EnvNode> &Env) {
335 auto NewEnv = std::make_shared<EnvNode>(Env,
EnvNode::DefMap{}, &With);
336 if (!WithDefs.contains(&With)) {
339 ToDef.insert_or_assign(&With.
kwWith(), NewDef);
340 WithDefs.insert_or_assign(&With, NewDef);
344 dfs(*With.
with(), Env);
347 dfs(*With.
expr(), NewEnv);
349 if (WithDefs.at(&With)->uses().empty()) {
351 Diags.emplace_back(Diagnostic::DK_ExtraWith, With.
kwWith().
range());
352 Fix &F = D.
fix(
"remove `with` expression")
362 if (Name.starts_with(
"_"))
364 return Constants.contains(Name) || Constants.contains(
"__" + Name);
367void VariableLookupAnalysis::checkBuiltins(
const ExprSelect &Sel) {
371 if (Sel.
expr().
kind() != Node::NK_ExprVar)
374 const auto &Builtins =
static_cast<const ExprVar &
>(Sel.
expr());
375 if (Builtins.id().name() !=
"builtins")
378 const auto &AP = *Sel.
path();
380 if (AP.names().size() != 1)
383 AttrName &First = *AP.
names()[0];
391 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_PrimOpRemovablePrefix,
394 D.
fix(
"remove `builtins.` prefix")
407 Diagnostic &D = Diags.emplace_back(Diagnostic::DK_PrimOpUnknown,
408 AP.names()[0]->range());
415void VariableLookupAnalysis::dfs(
const Node &Root,
416 const std::shared_ptr<EnvNode> &Env) {
417 Envs.insert({&Root, Env});
418 switch (Root.
kind()) {
419 case Node::NK_ExprVar: {
420 const auto &Var =
static_cast<const ExprVar &
>(Root);
424 case Node::NK_ExprLambda: {
425 const auto &Lambda =
static_cast<const ExprLambda &
>(Root);
429 case Node::NK_ExprAttrs: {
430 const auto &Attrs =
static_cast<const ExprAttrs &
>(Root);
434 case Node::NK_ExprLet: {
435 const auto &Let =
static_cast<const ExprLet &
>(Root);
439 case Node::NK_ExprWith: {
440 const auto &With =
static_cast<const ExprWith &
>(Root);
444 case Node::NK_ExprSelect: {
445 trivialDispatch(Root, Env);
446 const auto &Sel =
static_cast<const ExprSelect &
>(Root);
451 trivialDispatch(Root, Env);
457 DefBuilder DB(Diags);
460 if (!Info.Internal) {
466 for (
const auto &Builtin : Constants)
467 DB.addBuiltin(Builtin);
469 DB.addBuiltin(
"builtins");
471 DB.addBuiltin(std::string(
"__curPos"));
473 auto Env = std::make_shared<EnvNode>(
nullptr, DB.finish(),
nullptr);
482 if (!Envs.contains(N))
484 return Envs.at(N).get();
bool isBuiltinConstant(const std::string &Name)
Lookup variable names, from it's parent scope.
const std::string & staticName() const
const std::vector< std::shared_ptr< AttrName > > & names() const
@ 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)
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 Expr * expr() 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)
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)
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.