1use std::collections::{HashMap, HashSet};
20
21use crate::{ExprType, Statement, StatementType};
22
23pub struct ScopeBindings {
25 pub assigned: Vec<String>,
27 pub needs_mut: HashSet<String>,
30 pub optional: HashSet<String>,
33}
34
35#[derive(Clone, Copy, PartialEq)]
37enum Init {
38 No,
39 Maybe,
40 Yes,
41}
42
43pub(crate) const MUTATING_METHODS: &[&str] = &[
47 "append",
48 "extend",
49 "insert",
50 "remove",
51 "pop",
52 "popitem",
53 "clear",
54 "sort",
55 "reverse",
56 "update",
57 "add",
58 "discard",
59 "setdefault",
60 "intersection_update",
61 "difference_update",
62 "symmetric_difference_update",
63 "push",
64 "read",
67 "readline",
68 "readlines",
69 "write",
70 "writelines",
71 "close",
72 "writerow",
73 "writerows",
74];
75
76struct Analysis<'r> {
77 assigned: Vec<String>,
78 needs_mut: HashSet<String>,
79 optional: HashSet<String>,
80 state: HashMap<String, Init>,
81 resolve_call: &'r dyn Fn(&crate::Call) -> Option<bool>,
87}
88
89impl Analysis<'_> {
90 fn init_of(&self, name: &str) -> Init {
91 self.state.get(name).copied().unwrap_or(Init::No)
92 }
93
94 fn record_store(&mut self, name: &str, multi: bool) {
97 if !self.assigned.contains(&name.to_string()) {
98 self.assigned.push(name.to_string());
99 }
100 if multi || self.init_of(name) != Init::No {
101 self.needs_mut.insert(name.to_string());
102 }
103 self.state.insert(name.to_string(), Init::Yes);
104 }
105
106 fn record_mutation(&mut self, name: &str) {
109 self.needs_mut.insert(name.to_string());
110 }
111}
112
113fn merge_states(
115 branches: Vec<HashMap<String, Init>>,
116) -> HashMap<String, Init> {
117 let mut merged: HashMap<String, Init> = HashMap::new();
118 let keys: HashSet<String> = branches
119 .iter()
120 .flat_map(|b| b.keys().cloned())
121 .collect();
122 for key in keys {
123 let states: Vec<Init> = branches
124 .iter()
125 .map(|b| b.get(&key).copied().unwrap_or(Init::No))
126 .collect();
127 let all_yes = states.iter().all(|s| *s == Init::Yes);
128 let all_no = states.iter().all(|s| *s == Init::No);
129 let v = if all_yes {
130 Init::Yes
131 } else if all_no {
132 Init::No
133 } else {
134 Init::Maybe
135 };
136 merged.insert(key, v);
137 }
138 merged
139}
140
141pub fn analyze_scope(body: &[Statement], initialized: &[String]) -> ScopeBindings {
145 analyze_scope_with(body, initialized, &|_| None)
146}
147
148pub(crate) fn analyze_scope_with(
153 body: &[Statement],
154 initialized: &[String],
155 resolve_call: &dyn Fn(&crate::Call) -> Option<bool>,
156) -> ScopeBindings {
157 let mut a = Analysis {
158 assigned: Vec::new(),
159 needs_mut: HashSet::new(),
160 optional: HashSet::new(),
161 state: initialized
162 .iter()
163 .map(|n| (n.clone(), Init::Yes))
164 .collect(),
165 resolve_call,
166 };
167 walk_stmts(body, &mut a, false);
168 let assigned = a
170 .assigned
171 .into_iter()
172 .filter(|n| !initialized.contains(n))
173 .collect();
174 ScopeBindings {
175 assigned,
176 needs_mut: a.needs_mut,
177 optional: a.optional,
178 }
179}
180
181fn record_target(target: &ExprType, a: &mut Analysis<'_>, multi: bool) {
182 match target {
183 ExprType::Name(name) => a.record_store(&name.id, multi),
184 ExprType::Tuple(tuple) => {
185 for elt in &tuple.elts {
186 if let ExprType::Name(name) = elt {
188 a.record_store(&name.id, multi);
189 a.record_mutation(&name.id);
190 } else {
191 record_target(elt, a, multi);
192 }
193 }
194 }
195 ExprType::Subscript(sub) => {
198 if let Some(name) = chain_base_name(&sub.value) {
199 a.record_mutation(name);
200 }
201 walk_subscript_kind(&sub.kind, a);
202 }
203 ExprType::Attribute(attr) => {
204 if let Some(name) = chain_base_name(&attr.value) {
205 a.record_mutation(name);
206 }
207 }
208 _ => {}
209 }
210}
211
212pub(crate) fn chain_base_name(expr: &ExprType) -> Option<&str> {
215 match expr {
216 ExprType::Name(name) => Some(&name.id),
217 ExprType::Subscript(sub) => chain_base_name(&sub.value),
218 ExprType::Attribute(attr) => chain_base_name(&attr.value),
219 _ => None,
220 }
221}
222
223pub(crate) fn class_call_resolver<'a>(
228 ctx: &'a crate::CodeGenContext,
229 symbols: &'a crate::SymbolTableScopes,
230) -> impl Fn(&crate::Call) -> Option<bool> + 'a {
231 move |call| {
232 let ExprType::Attribute(attr) = call.func.as_ref() else {
233 return None;
234 };
235 let class = crate::receiver_class(&attr.value, ctx, symbols)?;
236 if !class.methods().any(|m| m.name == attr.attr) {
237 return None;
238 }
239 Some(class.method_needs_mut_self(&attr.attr, symbols))
240 }
241}
242
243fn walk_stmts(body: &[Statement], a: &mut Analysis<'_>, multi: bool) {
244 for stmt in body {
245 match &stmt.statement {
246 StatementType::Assign(assign) => {
247 walk_expr(&assign.value, a);
248 let value_is_none = crate::is_none_expr(&assign.value);
249 for target in &assign.targets {
250 record_target(target, a, multi);
251 if value_is_none {
252 if let ExprType::Name(name) = target {
253 a.optional.insert(name.id.clone());
254 }
255 }
256 }
257 }
258 StatementType::AugAssign(aug) => {
259 walk_expr(&aug.value, a);
260 if let ExprType::Name(name) = &aug.target {
261 a.record_store(&name.id, multi);
263 a.record_mutation(&name.id);
264 } else {
265 record_target(&aug.target, a, multi);
266 }
267 }
268 StatementType::Expr(e) => walk_expr(&e.value, a),
269 StatementType::Call(call) => walk_call(call, a),
270 StatementType::Return(Some(e)) => walk_expr(&e.value, a),
271 StatementType::Assert { test, msg } => {
272 walk_expr(test, a);
273 if let Some(m) = msg {
274 walk_expr(m, a);
275 }
276 }
277 StatementType::Raise(raise) => {
278 if let Some(exc) = &raise.exc {
279 walk_expr(exc, a);
280 }
281 if let Some(cause) = &raise.cause {
282 walk_expr(cause, a);
283 }
284 }
285 StatementType::If(s) => {
286 walk_expr(&s.test, a);
287 let before = a.state.clone();
288 walk_stmts(&s.body, a, multi);
289 let after_body = std::mem::replace(&mut a.state, before);
290 walk_stmts(&s.orelse, a, multi);
291 let after_else = std::mem::replace(&mut a.state, HashMap::new());
292 a.state = merge_states(vec![after_body, after_else]);
293 }
294 StatementType::While(s) => {
295 walk_expr(&s.test, a);
296 walk_loop(&s.body, &s.orelse, a, multi);
297 }
298 StatementType::For(s) => {
299 walk_expr(&s.iter, a);
300 walk_loop(&s.body, &s.orelse, a, multi);
301 }
302 StatementType::AsyncFor(s) => {
303 walk_expr(&s.iter, a);
304 walk_loop(&s.body, &s.orelse, a, multi);
305 }
306 StatementType::Try(s) => {
307 let before = a.state.clone();
310 walk_stmts(&s.body, a, true);
311 let after_body = a.state.clone();
312 let handler_entry =
314 merge_states(vec![before, after_body.clone()]);
315 let mut exits = Vec::new();
316 for handler in &s.handlers {
317 a.state = handler_entry.clone();
318 walk_stmts(&handler.body, a, multi);
319 exits.push(std::mem::replace(&mut a.state, HashMap::new()));
320 }
321 a.state = after_body;
323 walk_stmts(&s.orelse, a, multi);
324 exits.push(std::mem::replace(&mut a.state, HashMap::new()));
325 a.state = merge_states(exits);
326 walk_stmts(&s.finalbody, a, multi);
327 }
328 StatementType::With(s) => {
329 for item in &s.items {
330 walk_expr(&item.context_expr, a);
331 }
332 walk_stmts(&s.body, a, multi);
333 }
334 StatementType::AsyncWith(s) => {
335 for item in &s.items {
336 walk_expr(&item.context_expr, a);
337 }
338 walk_stmts(&s.body, a, multi);
339 }
340 _ => {}
342 }
343 }
344}
345
346fn walk_loop(
349 body: &[Statement],
350 orelse: &[Statement],
351 a: &mut Analysis<'_>,
352 outer_multi: bool,
353) {
354 let before = a.state.clone();
355 walk_stmts(body, a, true);
356 let after_body = std::mem::replace(&mut a.state, HashMap::new());
357 a.state = merge_states(vec![before, after_body]);
358 walk_stmts(orelse, a, outer_multi);
359}
360
361const FIRST_ARG_MUTATORS: &[&str] = &[
365 "heappush",
366 "heappop",
367 "heapify",
368 "heappushpop",
369 "heapreplace",
370 "writer",
372];
373
374fn walk_call(call: &crate::Call, a: &mut Analysis<'_>) {
375 if let ExprType::Attribute(attr) = call.func.as_ref() {
376 if let ExprType::Name(m) = attr.value.as_ref() {
380 if matches!(m.id.as_str(), "heapq" | "csv")
381 && FIRST_ARG_MUTATORS.contains(&attr.attr.as_str())
382 {
383 if let Some(first) = call.args.first() {
384 if let Some(name) = chain_base_name(first) {
385 a.record_mutation(name);
386 }
387 }
388 }
389 }
390 let mutates = match (a.resolve_call)(call) {
397 Some(verdict) => verdict,
398 None => MUTATING_METHODS.contains(&attr.attr.as_str()),
399 };
400 if mutates {
401 if let Some(name) = chain_base_name(&attr.value) {
402 a.record_mutation(name);
403 }
404 }
405 walk_expr(&attr.value, a);
406 } else {
407 if let ExprType::Name(n) = call.func.as_ref() {
410 if FIRST_ARG_MUTATORS.contains(&n.id.as_str()) {
411 if let Some(first) = call.args.first() {
412 if let Some(name) = chain_base_name(first) {
413 a.record_mutation(name);
414 }
415 }
416 }
417 }
418 walk_expr(&call.func, a);
419 }
420 for arg in &call.args {
421 walk_expr(arg, a);
422 }
423 for kw in &call.keywords {
426 walk_expr(&kw.value, a);
427 }
428}
429
430fn walk_subscript_kind(kind: &crate::SubscriptKind, a: &mut Analysis<'_>) {
431 match kind {
432 crate::SubscriptKind::Index(i) => walk_expr(i, a),
433 crate::SubscriptKind::Slice { lower, upper, step } => {
434 for bound in [lower, upper, step].into_iter().flatten() {
435 walk_expr(bound, a);
436 }
437 }
438 }
439}
440
441fn walk_expr(expr: &ExprType, a: &mut Analysis<'_>) {
442 match expr {
443 ExprType::Call(call) => walk_call(call, a),
444 ExprType::BinOp(op) => {
445 walk_expr(&op.left, a);
446 walk_expr(&op.right, a);
447 }
448 ExprType::BoolOp(op) => {
449 for v in &op.values {
450 walk_expr(v, a);
451 }
452 }
453 ExprType::UnaryOp(op) => walk_expr(&op.operand, a),
454 ExprType::Compare(cmp) => {
455 walk_expr(&cmp.left, a);
456 for c in &cmp.comparators {
457 walk_expr(c, a);
458 }
459 }
460 ExprType::IfExp(e) => {
461 walk_expr(&e.test, a);
462 walk_expr(&e.body, a);
463 walk_expr(&e.orelse, a);
464 }
465 ExprType::NamedExpr(e) => {
466 walk_expr(&e.left, a);
467 walk_expr(&e.right, a);
468 }
469 ExprType::Dict(d) => {
470 for k in d.keys.iter().flatten() {
471 walk_expr(k, a);
472 }
473 for v in &d.values {
474 walk_expr(v, a);
475 }
476 }
477 ExprType::Set(s) => {
478 for e in &s.elts {
479 walk_expr(e, a);
480 }
481 }
482 ExprType::List(elts) => {
483 for e in elts {
484 walk_expr(e, a);
485 }
486 }
487 ExprType::Tuple(t) => {
488 for e in &t.elts {
489 walk_expr(e, a);
490 }
491 }
492 ExprType::Attribute(attr) => walk_expr(&attr.value, a),
493 ExprType::Subscript(sub) => {
494 walk_expr(&sub.value, a);
495 walk_subscript_kind(&sub.kind, a);
496 }
497 ExprType::Starred(s) => walk_expr(&s.value, a),
498 ExprType::Await(e) => walk_expr(&e.value, a),
499 ExprType::Yield(y) => {
500 if let Some(v) = &y.value {
501 walk_expr(v, a);
502 }
503 }
504 ExprType::YieldFrom(y) => walk_expr(&y.value, a),
505 ExprType::FormattedValue(f) => walk_expr(&f.value, a),
506 ExprType::JoinedStr(j) => {
507 for v in &j.values {
508 walk_expr(v, a);
509 }
510 }
511 _ => {}
514 }
515}