1use std::collections::HashSet;
2use std::fmt::Write;
3
4use crate::compiler::ast;
5
6struct AssignmentTracker<'a> {
7 out: HashSet<&'a str>,
8 nested_out: Option<HashSet<String>>,
9 assigned: Vec<HashSet<&'a str>>,
10}
11
12impl<'a> AssignmentTracker<'a> {
13 fn is_assigned(&self, name: &str) -> bool {
14 self.assigned.iter().any(|x| x.contains(name))
15 }
16
17 fn assign(&mut self, name: &'a str) {
18 self.assigned.last_mut().unwrap().insert(name);
19 }
20
21 fn assign_nested(&mut self, name: String) {
22 if let Some(ref mut nested_out) = self.nested_out {
23 if !nested_out.contains(&name) {
24 nested_out.insert(name);
25 }
26 }
27 }
28
29 fn push(&mut self) {
30 self.assigned.push(Default::default());
31 }
32
33 fn pop(&mut self) {
34 self.assigned.pop();
35 }
36}
37
38#[cfg(feature = "macros")]
40pub fn find_macro_closure<'a>(m: &ast::Macro<'a>) -> HashSet<&'a str> {
41 let mut state = AssignmentTracker {
42 out: HashSet::new(),
43 nested_out: None,
44 assigned: vec![Default::default()],
45 };
46 tracker_visit_macro(m, &mut state, false);
47 state.out
48}
49
50pub fn find_undeclared(t: &ast::Stmt<'_>, track_nested: bool) -> HashSet<String> {
52 let mut state = AssignmentTracker {
53 out: HashSet::new(),
54 nested_out: if track_nested {
55 Some(HashSet::new())
56 } else {
57 None
58 },
59 assigned: vec![Default::default()],
60 };
61 track_walk(t, &mut state);
62 if let Some(nested) = state.nested_out {
63 nested
64 } else {
65 state.out.into_iter().map(|x| x.to_string()).collect()
66 }
67}
68
69fn tracker_visit_expr_opt<'a>(expr: &Option<ast::Expr<'a>>, state: &mut AssignmentTracker<'a>) {
70 if let Some(expr) = expr {
71 tracker_visit_expr(expr, state);
72 }
73}
74
75#[cfg(feature = "macros")]
76fn tracker_visit_macro<'a>(
77 m: &ast::Macro<'a>,
78 state: &mut AssignmentTracker<'a>,
79 declare_caller: bool,
80) {
81 if declare_caller {
82 state.assign("caller");
87 }
88 m.args.iter().for_each(|arg| track_assign(arg, state));
89 m.defaults
90 .iter()
91 .for_each(|expr| tracker_visit_expr(expr, state));
92 m.body.iter().for_each(|node| track_walk(node, state));
93}
94
95fn tracker_visit_callarg<'a>(callarg: &ast::CallArg<'a>, state: &mut AssignmentTracker<'a>) {
96 match callarg {
97 ast::CallArg::Pos(expr)
98 | ast::CallArg::Kwarg(_, expr)
99 | ast::CallArg::PosSplat(expr)
100 | ast::CallArg::KwargSplat(expr) => tracker_visit_expr(expr, state),
101 }
102}
103
104fn tracker_visit_expr<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a>) {
105 match expr {
106 ast::Expr::Var(var) => {
107 if !state.is_assigned(var.id) {
108 state.out.insert(var.id);
109 if state.nested_out.is_none() {
112 state.assign(var.id);
113 } else {
114 state.assign_nested(var.id.to_string());
115 }
116 }
117 }
118 ast::Expr::Const(_) => {}
119 ast::Expr::UnaryOp(expr) => tracker_visit_expr(&expr.expr, state),
120 ast::Expr::BinOp(expr) => {
121 tracker_visit_expr(&expr.left, state);
122 tracker_visit_expr(&expr.right, state);
123 }
124 ast::Expr::IfExpr(expr) => {
125 tracker_visit_expr(&expr.test_expr, state);
126 tracker_visit_expr(&expr.true_expr, state);
127 tracker_visit_expr_opt(&expr.false_expr, state);
128 }
129 ast::Expr::Filter(expr) => {
130 tracker_visit_expr_opt(&expr.expr, state);
131 expr.args
132 .iter()
133 .for_each(|x| tracker_visit_callarg(x, state));
134 }
135 ast::Expr::Test(expr) => {
136 tracker_visit_expr(&expr.expr, state);
137 expr.args
138 .iter()
139 .for_each(|x| tracker_visit_callarg(x, state));
140 }
141 ast::Expr::GetAttr(expr) => {
142 if state.nested_out.is_some() {
146 let mut attrs = vec![expr.name];
147 let mut ptr = &expr.expr;
148 loop {
149 match ptr {
150 ast::Expr::Var(var) => {
151 if !state.is_assigned(var.id) {
152 let mut rv = var.id.to_string();
153 for attr in attrs.iter().rev() {
154 write!(rv, ".{}", attr).ok();
155 }
156 state.assign_nested(rv);
157 return;
158 } else {
159 break;
160 }
161 }
162 ast::Expr::GetAttr(expr) => {
163 attrs.push(expr.name);
164 ptr = &expr.expr;
165 continue;
166 }
167 _ => break,
168 }
169 }
170 }
171 tracker_visit_expr(&expr.expr, state)
172 }
173 ast::Expr::GetItem(expr) => {
174 tracker_visit_expr(&expr.expr, state);
175 tracker_visit_expr(&expr.subscript_expr, state);
176 }
177 ast::Expr::Slice(slice) => {
178 tracker_visit_expr_opt(&slice.start, state);
179 tracker_visit_expr_opt(&slice.stop, state);
180 tracker_visit_expr_opt(&slice.step, state);
181 }
182 ast::Expr::Call(expr) => {
183 tracker_visit_expr(&expr.expr, state);
184 expr.args
185 .iter()
186 .for_each(|x| tracker_visit_callarg(x, state));
187 }
188 ast::Expr::List(expr) => expr.items.iter().for_each(|x| tracker_visit_expr(x, state)),
189 ast::Expr::Map(expr) => expr.keys.iter().zip(expr.values.iter()).for_each(|(k, v)| {
190 tracker_visit_expr(k, state);
191 tracker_visit_expr(v, state);
192 }),
193 }
194}
195
196fn track_assign<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a>) {
197 match expr {
198 ast::Expr::Var(var) => state.assign(var.id),
199 ast::Expr::List(list) => list.items.iter().for_each(|x| track_assign(x, state)),
200 _ => {}
201 }
202}
203
204fn track_walk<'a>(node: &ast::Stmt<'a>, state: &mut AssignmentTracker<'a>) {
205 match node {
206 ast::Stmt::Template(stmt) => {
207 state.assign("self");
208 stmt.children.iter().for_each(|x| track_walk(x, state));
209 }
210 ast::Stmt::EmitExpr(expr) => tracker_visit_expr(&expr.expr, state),
211 ast::Stmt::EmitRaw(_) => {}
212 ast::Stmt::ForLoop(stmt) => {
213 state.push();
214 state.assign("loop");
215 tracker_visit_expr(&stmt.iter, state);
216 track_assign(&stmt.target, state);
217 tracker_visit_expr_opt(&stmt.filter_expr, state);
218 stmt.body.iter().for_each(|x| track_walk(x, state));
219 state.pop();
220 state.push();
221 stmt.else_body.iter().for_each(|x| track_walk(x, state));
222 state.pop();
223 }
224 ast::Stmt::IfCond(stmt) => {
225 tracker_visit_expr(&stmt.expr, state);
226 state.push();
227 stmt.true_body.iter().for_each(|x| track_walk(x, state));
228 state.pop();
229 state.push();
230 stmt.false_body.iter().for_each(|x| track_walk(x, state));
231 state.pop();
232 }
233 ast::Stmt::WithBlock(stmt) => {
234 state.push();
235 for (target, expr) in &stmt.assignments {
236 track_assign(target, state);
237 tracker_visit_expr(expr, state);
238 }
239 stmt.body.iter().for_each(|x| track_walk(x, state));
240 state.pop();
241 }
242 ast::Stmt::Set(stmt) => {
243 track_assign(&stmt.target, state);
244 tracker_visit_expr(&stmt.expr, state);
245 }
246 ast::Stmt::AutoEscape(stmt) => {
247 state.push();
248 stmt.body.iter().for_each(|x| track_walk(x, state));
249 state.pop();
250 }
251 ast::Stmt::FilterBlock(stmt) => {
252 state.push();
253 stmt.body.iter().for_each(|x| track_walk(x, state));
254 state.pop();
255 }
256 ast::Stmt::SetBlock(stmt) => {
257 track_assign(&stmt.target, state);
258 state.push();
259 stmt.body.iter().for_each(|x| track_walk(x, state));
260 state.pop();
261 }
262 #[cfg(feature = "multi_template")]
263 ast::Stmt::Block(stmt) => {
264 state.push();
265 state.assign("super");
266 stmt.body.iter().for_each(|x| track_walk(x, state));
267 state.pop();
268 }
269 #[cfg(feature = "multi_template")]
270 ast::Stmt::Extends(_) | ast::Stmt::Include(_) => {}
271 #[cfg(feature = "multi_template")]
272 ast::Stmt::Import(stmt) => {
273 track_assign(&stmt.name, state);
274 }
275 #[cfg(feature = "multi_template")]
276 ast::Stmt::FromImport(stmt) => stmt.names.iter().for_each(|(arg, alias)| {
277 track_assign(alias.as_ref().unwrap_or(arg), state);
278 }),
279 #[cfg(feature = "macros")]
280 ast::Stmt::Macro(stmt) => {
281 state.assign(stmt.name);
282 state.push();
283 tracker_visit_macro(stmt, state, true);
284 state.pop();
285 }
286 #[cfg(feature = "macros")]
287 ast::Stmt::CallBlock(stmt) => {
288 tracker_visit_expr(&stmt.call.expr, state);
289 stmt.call
290 .args
291 .iter()
292 .for_each(|x| tracker_visit_callarg(x, state));
293 state.push();
294 tracker_visit_macro(&stmt.macro_decl, state, true);
295 state.pop();
296 }
297 #[cfg(feature = "loop_controls")]
298 ast::Stmt::Continue(_) | ast::Stmt::Break(_) => {}
299 ast::Stmt::Do(stmt) => {
300 tracker_visit_expr(&stmt.call.expr, state);
301 stmt.call
302 .args
303 .iter()
304 .for_each(|x| tracker_visit_callarg(x, state));
305 }
306 }
307}