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 <string>
21#include <vector>
22
23namespace nixf {
24
25/// \brief Represents a definition
27public:
28 /// \brief "Source" information so we can know where the def comes from.
30 /// \brief From with <expr>;
32
33 /// \brief From let ... in ...
35
36 /// \brief From ambda arg e.g. a: a + 1
38
39 /// \brief From lambda (noarg) formal, e.g. { a }: a + 1
41
42 /// \brief From lambda (with `@arg`) `arg`,
43 /// e.g. `a` in `{ foo }@a: foo + 1`
45
46 /// \brief From lambda (with `@arg`) formal,
47 /// e.g. `foo` in `{ foo }@a: foo + 1`
49
50 /// \brief From recursive attribute set. e.g. rec { }
52
53 /// \brief Builtin names.
55 };
56
57private:
58 std::vector<const ExprVar *> Uses;
59 const Node *Syntax;
60 DefinitionSource Source;
61
62public:
63 Definition(const Node *Syntax, DefinitionSource Source)
64 : Syntax(Syntax), Source(Source) {}
65 Definition(std::vector<const ExprVar *> Uses, const Node *Syntax,
66 DefinitionSource Source)
67 : Uses(std::move(Uses)), Syntax(Syntax), Source(Source) {}
68
69 [[nodiscard]] const Node *syntax() const { return Syntax; }
70
71 [[nodiscard]] const std::vector<const ExprVar *> &uses() const {
72 return Uses;
73 }
74
75 [[nodiscard]] DefinitionSource source() const { return Source; }
76
77 void usedBy(const ExprVar &User) { Uses.emplace_back(&User); }
78
79 [[nodiscard]] bool isBuiltin() const { return Source == DS_Builtin; }
80};
81
82/// \brief A set of variable definitions, which may inherit parent environment.
83class EnvNode {
84public:
85 using DefMap = std::map<std::string, std::shared_ptr<Definition>>;
86
87private:
88 const std::shared_ptr<EnvNode> Parent; // Points to the parent node.
89
90 DefMap Defs; // Definitions.
91
92 const Node *Syntax;
93
94public:
95 EnvNode(std::shared_ptr<EnvNode> Parent, DefMap Defs, const Node *Syntax)
96 : Parent(std::move(Parent)), Defs(std::move(Defs)), Syntax(Syntax) {}
97
98 [[nodiscard]] EnvNode *parent() const { return Parent.get(); }
99
100 /// \brief Where this node comes from.
101 [[nodiscard]] const Node *syntax() const { return Syntax; }
102
103 [[nodiscard]] bool isWith() const {
104 return Syntax && Syntax->kind() == Node::NK_ExprWith;
105 }
106
107 [[nodiscard]] const DefMap &defs() const { return Defs; }
108
109 [[nodiscard]] bool isLive() const;
110};
111
113public:
120
123 std::shared_ptr<const Definition> Def;
124 };
125
126 using ToDefMap = std::map<const Node *, std::shared_ptr<Definition>>;
127 using EnvMap = std::map<const Node *, std::shared_ptr<EnvNode>>;
128
129private:
130 std::vector<Diagnostic> &Diags;
131
132 std::map<const Node *, std::shared_ptr<Definition>>
133 WithDefs; // record with ... ; users.
134
135 ToDefMap ToDef;
136
137 // Record the environment so that we can know which names are available after
138 // name lookup, for later references like code completions.
139 EnvMap Envs;
140
141 void lookupVar(const ExprVar &Var, const std::shared_ptr<EnvNode> &Env);
142
143 void checkBuiltins(const ExprSelect &Sel);
144
145 void checkLetInheritBuiltins(const SemaAttrs &SA);
146
147 std::shared_ptr<EnvNode> dfsAttrs(const SemaAttrs &SA,
148 const std::shared_ptr<EnvNode> &Env,
149 const Node *Syntax,
151
152 void emitEnvLivenessWarning(const std::shared_ptr<EnvNode> &NewEnv);
153
154 void dfsDynamicAttrs(const std::vector<Attribute> &DynamicAttrs,
155 const std::shared_ptr<EnvNode> &Env);
156
157 // "dfs" is an abbreviation of "Deep-First-Search".
158 void dfs(const ExprLambda &Lambda, const std::shared_ptr<EnvNode> &Env);
159 void dfs(const ExprAttrs &Attrs, const std::shared_ptr<EnvNode> &Env);
160 void dfs(const ExprLet &Let, const std::shared_ptr<EnvNode> &Env);
161 void dfs(const ExprWith &With, const std::shared_ptr<EnvNode> &Env);
162
163 void dfs(const Node &Root, const std::shared_ptr<EnvNode> &Env);
164
165 void trivialDispatch(const Node &Root, const std::shared_ptr<EnvNode> &Env);
166
167 std::map<const ExprVar *, LookupResult> Results;
168
169 /// \brief For variables resolved from `with` scopes, track all enclosing
170 /// `with` expressions that could potentially provide the binding.
171 /// This is needed to detect indirect nested `with` scenarios where
172 /// converting an outer `with` to `let/inherit` could change semantics.
173 std::map<const ExprVar *, std::vector<const ExprWith *>> VarWithScopes;
174
175public:
176 VariableLookupAnalysis(std::vector<Diagnostic> &Diags);
177
178 /// \brief Perform variable lookup analysis (def-use) on AST.
179 /// \note This method should be invoked after any other method called.
180 /// \note The result remains immutable thus it can be shared among threads.
181 void runOnAST(const Node &Root);
182
183 /// \brief Query the which name/with binds to specific varaible.
184 [[nodiscard]] LookupResult query(const ExprVar &Var) const {
185 if (!Results.contains(&Var))
186 return {.Kind = LookupResultKind::NoSuchVar};
187 return Results.at(&Var);
188 }
189
190 /// \brief Get definition record for some name.
191 ///
192 /// For some cases, we need to get "definition" record to find all references
193 /// to this definition, on AST.
194 ///
195 /// Thus we need to store AST -> Definition
196 /// There are many pointers on AST, the convention is:
197 ///
198 /// 1. attrname "key" syntax is recorded.
199 // For static attrs, they are Node::NK_AttrName.
200 /// 2. "with" keyword is recorded.
201 /// 3. Lambda arguments, record its identifier.
202 [[nodiscard]] const Definition *toDef(const Node &N) const {
203 if (ToDef.contains(&N))
204 return ToDef.at(&N).get();
205 return nullptr;
206 }
207
208 const EnvNode *env(const Node *N) const;
209
210 /// \brief Get all `with` expressions that could provide the binding for a
211 /// variable.
212 ///
213 /// For variables resolved from `with` scopes, multiple enclosing `with`
214 /// expressions may potentially provide the binding. This method returns
215 /// all such `with` expressions, ordered from innermost to outermost.
216 ///
217 /// This is useful for detecting indirect nested `with` scenarios where
218 /// converting an outer `with` to `let/inherit` could change semantics.
219 ///
220 /// \param Var The variable to query.
221 /// \return A vector of `ExprWith` pointers representing all `with` scopes
222 /// that could provide the variable's binding. Returns an empty
223 /// vector if the variable is not resolved from a `with` scope.
224 [[nodiscard]] std::vector<const ExprWith *>
225 getWithScopes(const ExprVar &Var) const {
226 auto It = VarWithScopes.find(&Var);
227 if (It != VarWithScopes.end())
228 return It->second;
229 return {};
230 }
231};
232
233} // 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_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.
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.
void runOnAST(const Node &Root)
Perform variable lookup analysis (def-use) on AST.
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