nixd
Loading...
Searching...
No Matches
VariableLookup.h
Go to the documentation of this file.
1/// \file
2/// \brief Lookup variable names, from it's parent scope.
3///
4/// This file declares a variable-lookup analysis on AST.
5/// We do variable lookup for liveness checking, and emit diagnostics
6/// like "unused with", or "undefined variable".
7/// The implementation aims to be consistent with C++ nix (NixOS/nix).
8
9#pragma once
10
17
18#include <map>
19#include <memory>
20#include <set>
21#include <string>
22#include <vector>
23
24namespace nixf {
25
26/// \brief Represents a definition
28public:
29 /// \brief "Source" information so we can know where the def comes from.
31 /// \brief From with <expr>;
33
34 /// \brief From let ... in ...
36
37 /// \brief From ambda arg e.g. a: a + 1
39
40 /// \brief From lambda (noarg) formal, e.g. { a }: a + 1
42
43 /// \brief From lambda (with `@arg`) `arg`,
44 /// e.g. `a` in `{ foo }@a: foo + 1`
46
47 /// \brief From lambda (with `@arg`) formal,
48 /// e.g. `foo` in `{ foo }@a: foo + 1`
50
51 /// \brief From recursive attribute set. e.g. rec { }
53
54 /// \brief Builtin names.
56
57 /// \brief Formal in flake `outputs` injected by `call-flake.nix`
59 };
60
61private:
62 std::vector<const ExprVar *> Uses;
63 const Node *Syntax;
64 DefinitionSource Source;
65
66public:
67 Definition(const Node *Syntax, DefinitionSource Source)
68 : Syntax(Syntax), Source(Source) {}
69 Definition(std::vector<const ExprVar *> Uses, const Node *Syntax,
70 DefinitionSource Source)
71 : Uses(std::move(Uses)), Syntax(Syntax), Source(Source) {}
72
73 [[nodiscard]] const Node *syntax() const { return Syntax; }
74
75 [[nodiscard]] const std::vector<const ExprVar *> &uses() const {
76 return Uses;
77 }
78
79 [[nodiscard]] DefinitionSource source() const { return Source; }
80
81 void usedBy(const ExprVar &User) { Uses.emplace_back(&User); }
82
83 [[nodiscard]] bool isBuiltin() const { return Source == DS_Builtin; }
84};
85
86/// \brief A set of variable definitions, which may inherit parent environment.
87class EnvNode {
88public:
89 using DefMap = std::map<std::string, std::shared_ptr<Definition>>;
90
91private:
92 const std::shared_ptr<EnvNode> Parent; // Points to the parent node.
93
94 DefMap Defs; // Definitions.
95
96 const Node *Syntax;
97
98public:
99 EnvNode(std::shared_ptr<EnvNode> Parent, DefMap Defs, const Node *Syntax)
100 : Parent(std::move(Parent)), Defs(std::move(Defs)), Syntax(Syntax) {}
101
102 [[nodiscard]] EnvNode *parent() const { return Parent.get(); }
103
104 /// \brief Where this node comes from.
105 [[nodiscard]] const Node *syntax() const { return Syntax; }
106
107 [[nodiscard]] bool isWith() const {
108 return Syntax && Syntax->kind() == Node::NK_ExprWith;
109 }
110
111 [[nodiscard]] const DefMap &defs() const { return Defs; }
112
113 [[nodiscard]] bool isLive() const;
114};
115
117public:
124
127 std::shared_ptr<const Definition> Def;
128 };
129
130 using ToDefMap = std::map<const Node *, std::shared_ptr<Definition>>;
131 using EnvMap = std::map<const Node *, std::shared_ptr<EnvNode>>;
132
133private:
134 std::vector<Diagnostic> &Diags;
135
136 std::map<const Node *, std::shared_ptr<Definition>>
137 WithDefs; // record with ... ; users.
138
139 ToDefMap ToDef;
140
141 // Record the environment so that we can know which names are available after
142 // name lookup, for later references like code completions.
143 EnvMap Envs;
144
145 void lookupVar(const ExprVar &Var, const std::shared_ptr<EnvNode> &Env);
146
147 void checkBuiltins(const ExprSelect &Sel);
148
149 void checkLetInheritBuiltins(const SemaAttrs &SA);
150
151 std::shared_ptr<EnvNode> dfsAttrs(const SemaAttrs &SA,
152 const std::shared_ptr<EnvNode> &Env,
153 const Node *Syntax,
155
156 void emitEnvLivenessWarning(const std::shared_ptr<EnvNode> &NewEnv);
157
158 void dfsDynamicAttrs(const std::vector<Attribute> &DynamicAttrs,
159 const std::shared_ptr<EnvNode> &Env);
160
161 // "dfs" is an abbreviation of "Deep-First-Search".
162 void dfs(const ExprLambda &Lambda, const std::shared_ptr<EnvNode> &Env);
163 void dfs(const ExprAttrs &Attrs, const std::shared_ptr<EnvNode> &Env);
164 void dfs(const ExprLet &Let, const std::shared_ptr<EnvNode> &Env);
165 void dfs(const ExprLegacyLet &LegacyLet, const std::shared_ptr<EnvNode> &Env);
166 void dfs(const ExprWith &With, const std::shared_ptr<EnvNode> &Env);
167
168 void dfs(const Node &Root, const std::shared_ptr<EnvNode> &Env);
169
170 void trivialDispatch(const Node &Root, const std::shared_ptr<EnvNode> &Env);
171
172 std::map<const ExprVar *, LookupResult> Results;
173
174 /// \brief For variables resolved from `with` scopes, track all enclosing
175 /// `with` expressions that could potentially provide the binding.
176 /// This is needed to detect indirect nested `with` scenarios where
177 /// converting an outer `with` to `let/inherit` could change semantics.
178 std::map<const ExprVar *, std::vector<const ExprWith *>> VarWithScopes;
179
180 /// \brief The `outputs` lambda of the top-level flake attrset, null if not
181 /// a flake or if `outputs` is not a formals-lambda.
182 const ExprLambda *OutputsLambda = nullptr;
183
184 /// \brief Names unconditionally injected by call-flake: always `self`, plus
185 /// every key declared under `inputs`.
186 std::set<std::string> FlakeInjected;
187
188 void collectFlakeInjected(const Node &Root);
189
190public:
191 VariableLookupAnalysis(std::vector<Diagnostic> &Diags);
192
193 /// \brief Perform variable lookup analysis (def-use) on AST.
194 /// \note This method should be invoked after any other method called.
195 /// \note The result remains immutable thus it can be shared among threads.
196 void runOnAST(const Node &Root, bool IsFlake = false);
197
198 /// \brief Query the which name/with binds to specific varaible.
199 [[nodiscard]] LookupResult query(const ExprVar &Var) const {
200 if (!Results.contains(&Var))
201 return {.Kind = LookupResultKind::NoSuchVar};
202 return Results.at(&Var);
203 }
204
205 /// \brief Get definition record for some name.
206 ///
207 /// For some cases, we need to get "definition" record to find all references
208 /// to this definition, on AST.
209 ///
210 /// Thus we need to store AST -> Definition
211 /// There are many pointers on AST, the convention is:
212 ///
213 /// 1. attrname "key" syntax is recorded.
214 // For static attrs, they are Node::NK_AttrName.
215 /// 2. "with" keyword is recorded.
216 /// 3. Lambda arguments, record its identifier.
217 [[nodiscard]] const Definition *toDef(const Node &N) const {
218 if (ToDef.contains(&N))
219 return ToDef.at(&N).get();
220 return nullptr;
221 }
222
223 const EnvNode *env(const Node *N) const;
224
225 /// \brief Get all `with` expressions that could provide the binding for a
226 /// variable.
227 ///
228 /// For variables resolved from `with` scopes, multiple enclosing `with`
229 /// expressions may potentially provide the binding. This method returns
230 /// all such `with` expressions, ordered from innermost to outermost.
231 ///
232 /// This is useful for detecting indirect nested `with` scenarios where
233 /// converting an outer `with` to `let/inherit` could change semantics.
234 ///
235 /// \param Var The variable to query.
236 /// \return A vector of `ExprWith` pointers representing all `with` scopes
237 /// that could provide the variable's binding. Returns an empty
238 /// vector if the variable is not resolved from a `with` scope.
239 [[nodiscard]] std::vector<const ExprWith *>
240 getWithScopes(const ExprVar &Var) const {
241 auto It = VarWithScopes.find(&Var);
242 if (It != VarWithScopes.end())
243 return It->second;
244 return {};
245 }
246};
247
248} // namespace nixf
Represents a definition.
void usedBy(const ExprVar &User)
Definition(const Node *Syntax, DefinitionSource Source)
const Node * syntax() const
bool isBuiltin() const
Definition(std::vector< const ExprVar * > Uses, const Node *Syntax, DefinitionSource Source)
DefinitionSource source() const
const std::vector< const ExprVar * > & uses() const
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 ...
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(std::shared_ptr< EnvNode > Parent, DefMap Defs, const Node *Syntax)
EnvNode * parent() const
const DefMap & defs() const
Attribute set after deduplication.
Definition Attrs.h:236
const EnvNode * env(const Node *N) const
std::map< const Node *, std::shared_ptr< Definition > > ToDefMap
const Definition * toDef(const Node &N) const
Get definition record for some name.
void runOnAST(const Node &Root, bool IsFlake=false)
Perform variable lookup analysis (def-use) on AST.
std::map< const Node *, std::shared_ptr< EnvNode > > EnvMap
std::vector< const ExprWith * > getWithScopes(const ExprVar &Var) const
Get all with expressions that could provide the binding for a variable.
LookupResult query(const ExprVar &Var) const
Query the which name/with binds to specific varaible.
VariableLookupAnalysis(std::vector< Diagnostic > &Diags)
std::shared_ptr< const Definition > Def