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 std::shared_ptr<EnvNode> dfsAttrs(const SemaAttrs &SA,
146 const std::shared_ptr<EnvNode> &Env,
147 const Node *Syntax,
149
150 void emitEnvLivenessWarning(const std::shared_ptr<EnvNode> &NewEnv);
151
152 void dfsDynamicAttrs(const std::vector<Attribute> &DynamicAttrs,
153 const std::shared_ptr<EnvNode> &Env);
154
155 // "dfs" is an abbreviation of "Deep-First-Search".
156 void dfs(const ExprLambda &Lambda, const std::shared_ptr<EnvNode> &Env);
157 void dfs(const ExprAttrs &Attrs, const std::shared_ptr<EnvNode> &Env);
158 void dfs(const ExprLet &Let, const std::shared_ptr<EnvNode> &Env);
159 void dfs(const ExprWith &With, const std::shared_ptr<EnvNode> &Env);
160
161 void dfs(const Node &Root, const std::shared_ptr<EnvNode> &Env);
162
163 void trivialDispatch(const Node &Root, const std::shared_ptr<EnvNode> &Env);
164
165 std::map<const ExprVar *, LookupResult> Results;
166
167 /// \brief For variables resolved from `with` scopes, track all enclosing
168 /// `with` expressions that could potentially provide the binding.
169 /// This is needed to detect indirect nested `with` scenarios where
170 /// converting an outer `with` to `let/inherit` could change semantics.
171 std::map<const ExprVar *, std::vector<const ExprWith *>> VarWithScopes;
172
173public:
174 VariableLookupAnalysis(std::vector<Diagnostic> &Diags);
175
176 /// \brief Perform variable lookup analysis (def-use) on AST.
177 /// \note This method should be invoked after any other method called.
178 /// \note The result remains immutable thus it can be shared among threads.
179 void runOnAST(const Node &Root);
180
181 /// \brief Query the which name/with binds to specific varaible.
182 [[nodiscard]] LookupResult query(const ExprVar &Var) const {
183 if (!Results.contains(&Var))
184 return {.Kind = LookupResultKind::NoSuchVar};
185 return Results.at(&Var);
186 }
187
188 /// \brief Get definition record for some name.
189 ///
190 /// For some cases, we need to get "definition" record to find all references
191 /// to this definition, on AST.
192 ///
193 /// Thus we need to store AST -> Definition
194 /// There are many pointers on AST, the convention is:
195 ///
196 /// 1. attrname "key" syntax is recorded.
197 // For static attrs, they are Node::NK_AttrName.
198 /// 2. "with" keyword is recorded.
199 /// 3. Lambda arguments, record its identifier.
200 [[nodiscard]] const Definition *toDef(const Node &N) const {
201 if (ToDef.contains(&N))
202 return ToDef.at(&N).get();
203 return nullptr;
204 }
205
206 const EnvNode *env(const Node *N) const;
207
208 /// \brief Get all `with` expressions that could provide the binding for a
209 /// variable.
210 ///
211 /// For variables resolved from `with` scopes, multiple enclosing `with`
212 /// expressions may potentially provide the binding. This method returns
213 /// all such `with` expressions, ordered from innermost to outermost.
214 ///
215 /// This is useful for detecting indirect nested `with` scenarios where
216 /// converting an outer `with` to `let/inherit` could change semantics.
217 ///
218 /// \param Var The variable to query.
219 /// \return A vector of `ExprWith` pointers representing all `with` scopes
220 /// that could provide the variable's binding. Returns an empty
221 /// vector if the variable is not resolved from a `with` scope.
222 [[nodiscard]] std::vector<const ExprWith *>
223 getWithScopes(const ExprVar &Var) const {
224 auto It = VarWithScopes.find(&Var);
225 if (It != VarWithScopes.end())
226 return It->second;
227 return {};
228 }
229};
230
231} // 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