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:
114 enum class LookupResultKind {
115 Undefined,
116 FromWith,
117 Defined,
118 NoSuchVar,
119 };
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 std::shared_ptr<EnvNode> dfsAttrs(const SemaAttrs &SA,
144 const std::shared_ptr<EnvNode> &Env,
145 const Node *Syntax,
147
148 void emitEnvLivenessWarning(const std::shared_ptr<EnvNode> &NewEnv);
149
150 void dfsDynamicAttrs(const std::vector<Attribute> &DynamicAttrs,
151 const std::shared_ptr<EnvNode> &Env);
152
153 // "dfs" is an abbreviation of "Deep-First-Search".
154 void dfs(const ExprLambda &Lambda, const std::shared_ptr<EnvNode> &Env);
155 void dfs(const ExprAttrs &Attrs, const std::shared_ptr<EnvNode> &Env);
156 void dfs(const ExprLet &Let, const std::shared_ptr<EnvNode> &Env);
157 void dfs(const ExprWith &With, const std::shared_ptr<EnvNode> &Env);
158
159 void dfs(const Node &Root, const std::shared_ptr<EnvNode> &Env);
160
161 void trivialDispatch(const Node &Root, const std::shared_ptr<EnvNode> &Env);
162
163 std::map<const ExprVar *, LookupResult> Results;
164
165public:
166 VariableLookupAnalysis(std::vector<Diagnostic> &Diags);
167
168 /// \brief Perform variable lookup analysis (def-use) on AST.
169 /// \note This method should be invoked after any other method called.
170 /// \note The result remains immutable thus it can be shared among threads.
171 void runOnAST(const Node &Root);
172
173 /// \brief Query the which name/with binds to specific varaible.
174 [[nodiscard]] LookupResult query(const ExprVar &Var) const {
175 if (!Results.contains(&Var))
177 return Results.at(&Var);
178 }
179
180 /// \brief Get definition record for some name.
181 ///
182 /// For some cases, we need to get "definition" record to find all references
183 /// to this definition, on AST.
184 ///
185 /// Thus we need to store AST -> Definition
186 /// There are many pointers on AST, the convention is:
187 ///
188 /// 1. attrname "key" syntax is recorded.
189 // For static attrs, they are Node::NK_AttrName.
190 /// 2. "with" keyword is recorded.
191 /// 3. Lambda arguments, record its identifier.
192 [[nodiscard]] const Definition *toDef(const Node &N) const {
193 if (ToDef.contains(&N))
194 return ToDef.at(&N).get();
195 return nullptr;
196 }
197
198 const EnvNode *env(const Node *N) const;
199};
200
201} // 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
NodeKind kind() const
Definition Basic.h:34
Attribute set after deduplication.
Definition Attrs.h:231
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
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