Skip to main content

depyler_analysis/
optimizer.rs

1//! Optimization passes for generated Rust code
2
3use depyler_hir::hir::{
4    AssignTarget, BinOp, HirExpr, HirFunction, HirProgram, HirStmt, Literal, UnaryOp,
5};
6use std::collections::{HashMap, HashSet};
7use std::hash::{Hash, Hasher};
8
9/// Main optimizer that runs various optimization passes
10pub struct Optimizer {
11    /// Configuration for optimization passes
12    config: OptimizerConfig,
13}
14
15#[derive(Debug, Clone)]
16pub struct OptimizerConfig {
17    /// Enable inlining of small functions
18    pub inline_functions: bool,
19    /// Enable dead code elimination
20    pub eliminate_dead_code: bool,
21    /// Enable constant propagation
22    pub propagate_constants: bool,
23    /// Enable common subexpression elimination
24    pub eliminate_common_subexpressions: bool,
25    /// Maximum function size for inlining (in HIR nodes)
26    pub inline_threshold: usize,
27}
28
29impl Default for OptimizerConfig {
30    fn default() -> Self {
31        Self {
32            // DEPYLER-0161: Disabled incomplete inlining optimization
33            // KNOWN ISSUE: Inlining pass marks functions as "Trivial" but doesn't inline them,
34            // then dead code elimination removes assignments, leaving undefined variables.
35            // NOTE: Fix inlining logic before re-enabling (tracked in DEPYLER-0161)
36            inline_functions: false,
37            // DEPYLER-0508: Re-enabled DCE - unused variables should be eliminated
38            // DEPYLER-0363: Previously disabled for argparse debugging, now re-enabled
39            eliminate_dead_code: true,
40            propagate_constants: true,
41            eliminate_common_subexpressions: true,
42            inline_threshold: 20,
43        }
44    }
45}
46
47impl Optimizer {
48    pub fn new(config: OptimizerConfig) -> Self {
49        Self { config }
50    }
51
52    /// Run all optimization passes on a HIR program
53    pub fn optimize_program(&mut self, mut program: HirProgram) -> HirProgram {
54        // Pass 0: DEPYLER-0188 Walrus operator hoisting
55        // Must run BEFORE CSE to properly hoist (n := expr) to let n = expr
56        program = self.hoist_walrus_operators(program);
57
58        // Pass 1: Constant propagation
59        if self.config.propagate_constants {
60            program = self.propagate_constants_program(program);
61        }
62
63        // Pass 2: Dead code elimination
64        if self.config.eliminate_dead_code {
65            program = self.eliminate_dead_code_program(program);
66        }
67
68        // Pass 3: Function inlining
69        if self.config.inline_functions {
70            program = self.inline_functions_program(program);
71        }
72
73        // Pass 4: Common subexpression elimination
74        if self.config.eliminate_common_subexpressions {
75            program = self.eliminate_common_subexpressions_program(program);
76        }
77
78        program
79    }
80
81    /// DEPYLER-0188: Hoist walrus operator (NamedExpr) assignments to separate statements
82    ///
83    /// Transforms: if (n := len(text)) > 5: return n
84    /// Into:       n = len(text); if n > 5: return n
85    ///
86    /// This must run before CSE to avoid CSE creating temps for the entire condition.
87    fn hoist_walrus_operators(&self, mut program: HirProgram) -> HirProgram {
88        for func in &mut program.functions {
89            func.body = self.hoist_walrus_in_body(&func.body);
90        }
91        program
92    }
93
94    /// Process a statement body for walrus hoisting
95    fn hoist_walrus_in_body(&self, body: &[HirStmt]) -> Vec<HirStmt> {
96        let mut new_body = Vec::new();
97
98        for stmt in body {
99            match stmt {
100                HirStmt::If {
101                    condition,
102                    then_body,
103                    else_body,
104                } => {
105                    // Extract walrus operators from condition
106                    let (walrus_assigns, simplified_condition) =
107                        self.extract_walrus_from_expr(condition);
108
109                    // Add hoisted let statements
110                    for (name, value) in walrus_assigns {
111                        new_body.push(HirStmt::Assign {
112                            target: AssignTarget::Symbol(name),
113                            value,
114                            type_annotation: None,
115                        });
116                    }
117
118                    // Recursively process then/else bodies
119                    let new_then = self.hoist_walrus_in_body(then_body);
120                    let new_else = else_body
121                        .as_ref()
122                        .map(|stmts| self.hoist_walrus_in_body(stmts));
123
124                    new_body.push(HirStmt::If {
125                        condition: simplified_condition,
126                        then_body: new_then,
127                        else_body: new_else,
128                    });
129                }
130                HirStmt::While {
131                    condition,
132                    body: while_body,
133                } => {
134                    // Extract walrus from while condition
135                    let (walrus_assigns, simplified_condition) =
136                        self.extract_walrus_from_expr(condition);
137
138                    // For while loops, walrus needs special handling - hoist once before
139                    for (name, value) in walrus_assigns {
140                        new_body.push(HirStmt::Assign {
141                            target: AssignTarget::Symbol(name),
142                            value,
143                            type_annotation: None,
144                        });
145                    }
146
147                    let new_while_body = self.hoist_walrus_in_body(while_body);
148                    new_body.push(HirStmt::While {
149                        condition: simplified_condition,
150                        body: new_while_body,
151                    });
152                }
153                HirStmt::For {
154                    target,
155                    iter,
156                    body: for_body,
157                } => {
158                    // Recursively process for body
159                    let new_for_body = self.hoist_walrus_in_body(for_body);
160                    new_body.push(HirStmt::For {
161                        target: target.clone(),
162                        iter: iter.clone(),
163                        body: new_for_body,
164                    });
165                }
166                HirStmt::Try {
167                    body: try_body,
168                    handlers,
169                    orelse,
170                    finalbody,
171                } => {
172                    // Recursively process try/except blocks
173                    let new_try_body = self.hoist_walrus_in_body(try_body);
174                    let new_handlers: Vec<_> = handlers
175                        .iter()
176                        .map(|h| depyler_hir::hir::ExceptHandler {
177                            exception_type: h.exception_type.clone(),
178                            name: h.name.clone(),
179                            body: self.hoist_walrus_in_body(&h.body),
180                        })
181                        .collect();
182                    let new_orelse = orelse
183                        .as_ref()
184                        .map(|stmts| self.hoist_walrus_in_body(stmts));
185                    let new_finalbody = finalbody
186                        .as_ref()
187                        .map(|stmts| self.hoist_walrus_in_body(stmts));
188                    new_body.push(HirStmt::Try {
189                        body: new_try_body,
190                        handlers: new_handlers,
191                        orelse: new_orelse,
192                        finalbody: new_finalbody,
193                    });
194                }
195                HirStmt::With {
196                    context,
197                    target,
198                    body: with_body,
199                    is_async,
200                } => {
201                    let new_with_body = self.hoist_walrus_in_body(with_body);
202                    new_body.push(HirStmt::With {
203                        context: context.clone(),
204                        target: target.clone(),
205                        body: new_with_body,
206                        is_async: *is_async,
207                    });
208                }
209                _ => new_body.push(stmt.clone()),
210            }
211        }
212
213        new_body
214    }
215
216    /// Extract NamedExpr from an expression, returning hoisted assignments and simplified expr
217    fn extract_walrus_from_expr(&self, expr: &HirExpr) -> (Vec<(String, HirExpr)>, HirExpr) {
218        let mut assigns = Vec::new();
219        let simplified = self.extract_walrus_recursive(expr, &mut assigns);
220        (assigns, simplified)
221    }
222
223    /// Recursively extract NamedExpr from expression tree
224    fn extract_walrus_recursive(
225        &self,
226        expr: &HirExpr,
227        assigns: &mut Vec<(String, HirExpr)>,
228    ) -> HirExpr {
229        match expr {
230            HirExpr::NamedExpr { target, value } => {
231                // Recursively process value first (handle nested walrus)
232                let simplified_value = self.extract_walrus_recursive(value, assigns);
233                assigns.push((target.clone(), simplified_value));
234                // Replace with variable reference
235                HirExpr::Var(target.clone())
236            }
237            HirExpr::Binary { op, left, right } => HirExpr::Binary {
238                op: *op,
239                left: Box::new(self.extract_walrus_recursive(left, assigns)),
240                right: Box::new(self.extract_walrus_recursive(right, assigns)),
241            },
242            HirExpr::Unary { op, operand } => HirExpr::Unary {
243                op: *op,
244                operand: Box::new(self.extract_walrus_recursive(operand, assigns)),
245            },
246            HirExpr::Call { func, args, kwargs } => HirExpr::Call {
247                func: func.clone(),
248                args: args
249                    .iter()
250                    .map(|a| self.extract_walrus_recursive(a, assigns))
251                    .collect(),
252                kwargs: kwargs
253                    .iter()
254                    .map(|(k, v)| (k.clone(), self.extract_walrus_recursive(v, assigns)))
255                    .collect(),
256            },
257            HirExpr::MethodCall {
258                object,
259                method,
260                args,
261                kwargs,
262            } => HirExpr::MethodCall {
263                object: Box::new(self.extract_walrus_recursive(object, assigns)),
264                method: method.clone(),
265                args: args
266                    .iter()
267                    .map(|a| self.extract_walrus_recursive(a, assigns))
268                    .collect(),
269                kwargs: kwargs
270                    .iter()
271                    .map(|(k, v)| (k.clone(), self.extract_walrus_recursive(v, assigns)))
272                    .collect(),
273            },
274            HirExpr::IfExpr { test, body, orelse } => HirExpr::IfExpr {
275                test: Box::new(self.extract_walrus_recursive(test, assigns)),
276                body: Box::new(self.extract_walrus_recursive(body, assigns)),
277                orelse: Box::new(self.extract_walrus_recursive(orelse, assigns)),
278            },
279            // Other expressions - just clone (walrus rare in these contexts)
280            _ => expr.clone(),
281        }
282    }
283
284    /// Propagate constant values through the program
285    fn propagate_constants_program(&self, mut program: HirProgram) -> HirProgram {
286        let mut constants = HashMap::new();
287
288        // First pass: find which variables are mutated (assigned more than once)
289        let mut mutated_vars = HashSet::new();
290        for func in &program.functions {
291            self.collect_mutated_vars_function(func, &mut mutated_vars);
292        }
293
294        // DEPYLER-0269 Fix: Second pass - collect all variable READS
295        let mut read_vars = HashSet::new();
296        for func in &program.functions {
297            self.collect_read_vars_function(func, &mut read_vars);
298        }
299
300        // Third pass: collect constants (but skip mutated OR read variables)
301        // DEPYLER-0269: Only propagate constants for dead code (assigned but never read)
302        for func in &program.functions {
303            self.collect_constants_function(func, &mut constants, &mutated_vars, &read_vars);
304        }
305
306        // Fourth pass: propagate constants
307        for func in &mut program.functions {
308            self.propagate_constants_function(func, &constants);
309        }
310
311        program
312    }
313
314    fn collect_mutated_vars_function(
315        &self,
316        func: &HirFunction,
317        mutated_vars: &mut HashSet<String>,
318    ) {
319        let mut assignments = HashMap::new();
320        self.count_assignments_stmt(&func.body, &mut assignments);
321
322        // Any variable assigned more than once is mutated
323        for (var, count) in assignments {
324            if count > 1 {
325                mutated_vars.insert(var);
326            }
327        }
328    }
329
330    /// DEPYLER-0269: Collect all variables that are actually USED (read)
331    fn collect_read_vars_function(&self, func: &HirFunction, read_vars: &mut HashSet<String>) {
332        for stmt in &func.body {
333            Self::collect_read_vars_stmt(stmt, read_vars);
334        }
335    }
336
337    /// DEPYLER-0269: Recursively collect variable reads from statements
338    fn collect_read_vars_stmt(stmt: &HirStmt, read_vars: &mut HashSet<String>) {
339        match stmt {
340            HirStmt::Assign { value, .. } => {
341                // Variable reads in the RHS of assignment
342                Self::collect_read_vars_expr(value, read_vars);
343            }
344            HirStmt::Expr(expr) => {
345                Self::collect_read_vars_expr(expr, read_vars);
346            }
347            HirStmt::If {
348                condition,
349                then_body,
350                else_body,
351            } => {
352                Self::collect_read_vars_expr(condition, read_vars);
353                for s in then_body {
354                    Self::collect_read_vars_stmt(s, read_vars);
355                }
356                if let Some(else_stmts) = else_body {
357                    for s in else_stmts {
358                        Self::collect_read_vars_stmt(s, read_vars);
359                    }
360                }
361            }
362            HirStmt::While { condition, body } => {
363                Self::collect_read_vars_expr(condition, read_vars);
364                for s in body {
365                    Self::collect_read_vars_stmt(s, read_vars);
366                }
367            }
368            HirStmt::For { iter, body, .. } => {
369                Self::collect_read_vars_expr(iter, read_vars);
370                for s in body {
371                    Self::collect_read_vars_stmt(s, read_vars);
372                }
373            }
374            HirStmt::Return(Some(expr)) => {
375                Self::collect_read_vars_expr(expr, read_vars);
376            }
377            _ => {}
378        }
379    }
380
381    /// DEPYLER-0269: Recursively collect variable reads from expressions
382    fn collect_read_vars_expr(expr: &HirExpr, read_vars: &mut HashSet<String>) {
383        match expr {
384            HirExpr::Var(name) => {
385                // This is a variable READ - mark as used
386                read_vars.insert(name.clone());
387            }
388            HirExpr::Binary { left, right, .. } => {
389                Self::collect_read_vars_expr(left, read_vars);
390                Self::collect_read_vars_expr(right, read_vars);
391            }
392            HirExpr::Unary { operand, .. } => {
393                Self::collect_read_vars_expr(operand, read_vars);
394            }
395            HirExpr::List(items) => {
396                for item in items {
397                    Self::collect_read_vars_expr(item, read_vars);
398                }
399            }
400            HirExpr::Dict(pairs) => {
401                for (k, v) in pairs {
402                    Self::collect_read_vars_expr(k, read_vars);
403                    Self::collect_read_vars_expr(v, read_vars);
404                }
405            }
406            HirExpr::Call { args, .. } => {
407                for arg in args {
408                    Self::collect_read_vars_expr(arg, read_vars);
409                }
410            }
411            HirExpr::MethodCall { object, args, .. } => {
412                Self::collect_read_vars_expr(object, read_vars);
413                for arg in args {
414                    Self::collect_read_vars_expr(arg, read_vars);
415                }
416            }
417            HirExpr::Lambda { body, .. } => {
418                Self::collect_read_vars_expr(body, read_vars);
419            }
420            _ => {}
421        }
422    }
423
424    fn count_assignments_stmt(&self, stmts: &[HirStmt], assignments: &mut HashMap<String, usize>) {
425        for stmt in stmts {
426            self.count_assignments_in_single_stmt(stmt, assignments);
427        }
428    }
429
430    fn count_assignments_in_single_stmt(
431        &self,
432        stmt: &HirStmt,
433        assignments: &mut HashMap<String, usize>,
434    ) {
435        match stmt {
436            HirStmt::Assign {
437                target: AssignTarget::Symbol(name),
438                ..
439            } => {
440                *assignments.entry(name.clone()).or_insert(0) += 1;
441            }
442            HirStmt::If {
443                then_body,
444                else_body,
445                ..
446            } => {
447                self.count_assignments_stmt(then_body, assignments);
448                if let Some(else_stmts) = else_body {
449                    self.count_assignments_stmt(else_stmts, assignments);
450                }
451            }
452            HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
453                self.count_assignments_stmt(body, assignments);
454            }
455            _ => {}
456        }
457    }
458
459    fn collect_constants_function(
460        &self,
461        func: &HirFunction,
462        constants: &mut HashMap<String, HirExpr>,
463        mutated_vars: &HashSet<String>,
464        used_vars: &HashSet<String>,
465    ) {
466        for stmt in &func.body {
467            self.collect_constants_stmt(stmt, constants, mutated_vars, used_vars);
468        }
469    }
470
471    fn collect_constants_stmt(
472        &self,
473        stmt: &HirStmt,
474        constants: &mut HashMap<String, HirExpr>,
475        mutated_vars: &HashSet<String>,
476        used_vars: &HashSet<String>,
477    ) {
478        match stmt {
479            HirStmt::Assign {
480                target: AssignTarget::Symbol(name),
481                value,
482                ..
483            } => {
484                // DEPYLER-0269 Fix: Only propagate constants for dead code
485                // Skip variables that are:
486                // 1. Mutated (assigned more than once) - already checked
487                // 2. Actually USED (read anywhere) - NEW CHECK
488                // This prevents unused variable warnings for user-defined constants
489                if !mutated_vars.contains(name)
490                    && !used_vars.contains(name)  // DEPYLER-0269: Skip used variables!
491                    && self.is_constant_expr(value)
492                {
493                    constants.insert(name.clone(), value.clone());
494                }
495            }
496            HirStmt::Assign { .. } => {}
497            HirStmt::If {
498                then_body,
499                else_body,
500                ..
501            } => {
502                for s in then_body {
503                    self.collect_constants_stmt(s, constants, mutated_vars, used_vars);
504                }
505                if let Some(else_stmts) = else_body {
506                    for s in else_stmts {
507                        self.collect_constants_stmt(s, constants, mutated_vars, used_vars);
508                    }
509                }
510            }
511            HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
512                for s in body {
513                    self.collect_constants_stmt(s, constants, mutated_vars, used_vars);
514                }
515            }
516            _ => {}
517        }
518    }
519
520    fn is_constant_expr(&self, expr: &HirExpr) -> bool {
521        is_constant_expr_inner(expr)
522    }
523
524    fn propagate_constants_function(
525        &self,
526        func: &mut HirFunction,
527        constants: &HashMap<String, HirExpr>,
528    ) {
529        for stmt in &mut func.body {
530            self.propagate_constants_stmt(stmt, constants);
531        }
532    }
533
534    fn propagate_constants_stmt(&self, stmt: &mut HirStmt, constants: &HashMap<String, HirExpr>) {
535        match stmt {
536            HirStmt::Assign { value, .. } => {
537                self.propagate_constants_expr(value, constants);
538            }
539            HirStmt::Return(Some(expr)) => {
540                self.propagate_constants_expr(expr, constants);
541            }
542            HirStmt::If {
543                condition,
544                then_body,
545                else_body,
546            } => {
547                self.propagate_constants_expr(condition, constants);
548                for s in then_body {
549                    self.propagate_constants_stmt(s, constants);
550                }
551                if let Some(else_stmts) = else_body {
552                    for s in else_stmts {
553                        self.propagate_constants_stmt(s, constants);
554                    }
555                }
556            }
557            HirStmt::While { condition, body } => {
558                self.propagate_constants_expr(condition, constants);
559                for s in body {
560                    self.propagate_constants_stmt(s, constants);
561                }
562            }
563            HirStmt::For { iter, body, .. } => {
564                self.propagate_constants_expr(iter, constants);
565                for s in body {
566                    self.propagate_constants_stmt(s, constants);
567                }
568            }
569            HirStmt::Expr(expr) => {
570                self.propagate_constants_expr(expr, constants);
571            }
572            _ => {}
573        }
574    }
575
576    fn propagate_constants_expr(&self, expr: &mut HirExpr, constants: &HashMap<String, HirExpr>) {
577        match expr {
578            HirExpr::Var(name) => {
579                if let Some(const_expr) = constants.get(name) {
580                    *expr = const_expr.clone();
581                }
582            }
583            HirExpr::Binary { left, right, .. } => {
584                self.propagate_constants_expr(left, constants);
585                self.propagate_constants_expr(right, constants);
586
587                // Try to evaluate constant expressions
588                if let Some(result) = self.evaluate_constant_binop(expr) {
589                    *expr = result;
590                }
591            }
592            HirExpr::Unary { operand, .. } => {
593                self.propagate_constants_expr(operand, constants);
594
595                // Try to evaluate constant expressions
596                if let Some(result) = self.evaluate_constant_unaryop(expr) {
597                    *expr = result;
598                }
599            }
600            HirExpr::List(items) => {
601                for item in items {
602                    self.propagate_constants_expr(item, constants);
603                }
604            }
605            HirExpr::Dict(pairs) => {
606                for (k, v) in pairs {
607                    self.propagate_constants_expr(k, constants);
608                    self.propagate_constants_expr(v, constants);
609                }
610            }
611            HirExpr::Call { args, .. } => {
612                for arg in args {
613                    self.propagate_constants_expr(arg, constants);
614                }
615            }
616            HirExpr::MethodCall { object, args, .. } => {
617                self.propagate_constants_expr(object, constants);
618                for arg in args {
619                    self.propagate_constants_expr(arg, constants);
620                }
621            }
622            HirExpr::Lambda { body, .. } => {
623                self.propagate_constants_expr(body, constants);
624            }
625            _ => {}
626        }
627    }
628
629    fn evaluate_constant_binop(&self, expr: &HirExpr) -> Option<HirExpr> {
630        if let HirExpr::Binary { left, right, op } = expr {
631            match (left.as_ref(), right.as_ref(), op) {
632                (
633                    HirExpr::Literal(Literal::Int(a)),
634                    HirExpr::Literal(Literal::Int(b)),
635                    BinOp::Add,
636                ) => Some(HirExpr::Literal(Literal::Int(a + b))),
637                (
638                    HirExpr::Literal(Literal::Int(a)),
639                    HirExpr::Literal(Literal::Int(b)),
640                    BinOp::Sub,
641                ) => Some(HirExpr::Literal(Literal::Int(a - b))),
642                (
643                    HirExpr::Literal(Literal::Int(a)),
644                    HirExpr::Literal(Literal::Int(b)),
645                    BinOp::Mul,
646                ) => Some(HirExpr::Literal(Literal::Int(a * b))),
647                (
648                    HirExpr::Literal(Literal::Int(a)),
649                    HirExpr::Literal(Literal::Int(b)),
650                    BinOp::Div,
651                ) if *b != 0 => Some(HirExpr::Literal(Literal::Int(a / b))),
652                (
653                    HirExpr::Literal(Literal::Float(a)),
654                    HirExpr::Literal(Literal::Float(b)),
655                    BinOp::Add,
656                ) => Some(HirExpr::Literal(Literal::Float(a + b))),
657                (
658                    HirExpr::Literal(Literal::Float(a)),
659                    HirExpr::Literal(Literal::Float(b)),
660                    BinOp::Sub,
661                ) => Some(HirExpr::Literal(Literal::Float(a - b))),
662                (
663                    HirExpr::Literal(Literal::Float(a)),
664                    HirExpr::Literal(Literal::Float(b)),
665                    BinOp::Mul,
666                ) => Some(HirExpr::Literal(Literal::Float(a * b))),
667                (
668                    HirExpr::Literal(Literal::Float(a)),
669                    HirExpr::Literal(Literal::Float(b)),
670                    BinOp::Div,
671                ) if *b != 0.0 => Some(HirExpr::Literal(Literal::Float(a / b))),
672                _ => None,
673            }
674        } else {
675            None
676        }
677    }
678
679    fn evaluate_constant_unaryop(&self, expr: &HirExpr) -> Option<HirExpr> {
680        if let HirExpr::Unary { op, operand } = expr {
681            match (operand.as_ref(), op) {
682                (HirExpr::Literal(Literal::Int(n)), UnaryOp::Neg) => {
683                    Some(HirExpr::Literal(Literal::Int(-n)))
684                }
685                (HirExpr::Literal(Literal::Float(f)), UnaryOp::Neg) => {
686                    Some(HirExpr::Literal(Literal::Float(-f)))
687                }
688                (HirExpr::Literal(Literal::Bool(b)), UnaryOp::Not) => {
689                    Some(HirExpr::Literal(Literal::Bool(!b)))
690                }
691                _ => None,
692            }
693        } else {
694            None
695        }
696    }
697
698    /// Eliminate dead code from the program
699    fn eliminate_dead_code_program(&self, mut program: HirProgram) -> HirProgram {
700        for func in &mut program.functions {
701            self.eliminate_dead_code_function(func);
702        }
703        program
704    }
705
706    fn eliminate_dead_code_function(&self, func: &mut HirFunction) {
707        // DEPYLER-0703: Iterate DCE until no more statements are removed
708        // This handles transitive dead code (e.g., `sq = Foo(); is_sq = sq.bar()` where
709        // is_sq is unused → remove is_sq → sq becomes unused → remove sq)
710        const MAX_ITERATIONS: usize = 10;
711
712        for _ in 0..MAX_ITERATIONS {
713            let initial_len = func.body.len();
714
715            // Collect truly used variables (referenced after assignment)
716            let mut used_vars = HashMap::new();
717            for stmt in &func.body {
718                self.collect_truly_used_vars_stmt(stmt, &mut used_vars);
719            }
720
721            // DEPYLER-0703: Collect assignments with side effects (calls, indexing, etc.)
722            // that need to be preserved even if the variable is unused
723            let mut side_effect_vars = HashSet::new();
724            for stmt in &func.body {
725                if let HirStmt::Assign { target, value, .. } = stmt {
726                    if Self::expr_has_side_effects(value) {
727                        if let AssignTarget::Symbol(name) = target {
728                            side_effect_vars.insert(name.clone());
729                        }
730                    }
731                }
732            }
733
734            // DEPYLER-0934: DISABLED variable renaming to `_varname`
735            // The previous approach only renamed definitions, not usages, causing E0425 errors.
736            // Example: `let _args = Args::parse();` but `args.command` still used original name.
737            // Instead of renaming, we suppress warnings with #[allow(unused_variables)] at file level.
738            // A proper fix would require a full rename pass that updates all references.
739
740            // Remove truly dead assignments (not used and no side effects)
741            func.body.retain(|stmt| {
742                if let HirStmt::Assign {
743                    target: AssignTarget::Symbol(name),
744                    ..
745                } = stmt
746                {
747                    // Keep if: truly used OR has side effects
748                    // DEPYLER-0934: Removed .trim_start_matches('_') since we no longer rename
749                    used_vars.contains_key(name) || side_effect_vars.contains(name)
750                } else {
751                    true
752                }
753            });
754
755            // If no statements were removed, we're done
756            if func.body.len() == initial_len {
757                break;
758            }
759        }
760    }
761
762    /// DEPYLER-0270 Fix #1 (Updated): Collect truly used variables (referenced, not just assigned)
763    /// This version does NOT mark side-effect assignments as used - that's handled separately
764    /// in eliminate_dead_code_function which preserves side-effect assignments.
765    fn collect_truly_used_vars_stmt(&self, stmt: &HirStmt, used: &mut HashMap<String, bool>) {
766        match stmt {
767            HirStmt::Assign { target, value, .. } => {
768                // DEPYLER-0235 FIX: Collect variables from assignment targets
769                // This fixes property writes like `b.size = 20` where `b` is used on LHS
770                self.collect_used_vars_assign_target(target, used);
771                self.collect_used_vars_expr(value, used);
772                // NOTE: We do NOT mark side-effect assignments as used here
773                // That's now handled in eliminate_dead_code_function
774            }
775            HirStmt::Return(Some(expr)) => {
776                self.collect_used_vars_expr(expr, used);
777            }
778            HirStmt::If {
779                condition,
780                then_body,
781                else_body,
782            } => {
783                self.collect_used_vars_expr(condition, used);
784                for s in then_body {
785                    self.collect_truly_used_vars_stmt(s, used);
786                }
787                if let Some(else_stmts) = else_body {
788                    for s in else_stmts {
789                        self.collect_truly_used_vars_stmt(s, used);
790                    }
791                }
792            }
793            HirStmt::While { condition, body } => {
794                self.collect_used_vars_expr(condition, used);
795                for s in body {
796                    self.collect_truly_used_vars_stmt(s, used);
797                }
798            }
799            HirStmt::For { iter, body, .. } => {
800                self.collect_used_vars_expr(iter, used);
801                for s in body {
802                    self.collect_truly_used_vars_stmt(s, used);
803                }
804            }
805            HirStmt::Expr(expr) => {
806                self.collect_used_vars_expr(expr, used);
807            }
808            // DEPYLER-0514: Handle Try statements - recurse into all blocks
809            HirStmt::Try {
810                body,
811                handlers,
812                orelse,
813                finalbody,
814            } => {
815                // Recurse into try body
816                for s in body {
817                    self.collect_truly_used_vars_stmt(s, used);
818                }
819                // Recurse into exception handlers
820                for handler in handlers {
821                    for s in &handler.body {
822                        self.collect_truly_used_vars_stmt(s, used);
823                    }
824                }
825                // Recurse into else block (executed if no exception)
826                if let Some(orelse_stmts) = orelse {
827                    for s in orelse_stmts {
828                        self.collect_truly_used_vars_stmt(s, used);
829                    }
830                }
831                // Recurse into finally block
832                if let Some(finalbody_stmts) = finalbody {
833                    for s in finalbody_stmts {
834                        self.collect_truly_used_vars_stmt(s, used);
835                    }
836                }
837            }
838            // DEPYLER-0514: Handle With statements - recurse into body
839            HirStmt::With { context, body, .. } => {
840                self.collect_used_vars_expr(context, used);
841                for s in body {
842                    self.collect_truly_used_vars_stmt(s, used);
843                }
844            }
845            // DEPYLER-0627: Handle Assert statements - variables in test/msg are used
846            HirStmt::Assert { test, msg } => {
847                self.collect_used_vars_expr(test, used);
848                if let Some(msg_expr) = msg {
849                    self.collect_used_vars_expr(msg_expr, used);
850                }
851            }
852            // DEPYLER-0627: Handle Raise statements - variables in exception are used
853            HirStmt::Raise { exception, cause } => {
854                if let Some(exc) = exception {
855                    self.collect_used_vars_expr(exc, used);
856                }
857                if let Some(c) = cause {
858                    self.collect_used_vars_expr(c, used);
859                }
860            }
861            // DEPYLER-0688: Handle FunctionDef (nested functions) - must recurse into body
862            // Nested functions can capture variables from outer scope (closures)
863            // e.g., `cache = {}; def fib(): cache[x] = v` - `cache` is used in nested function
864            HirStmt::FunctionDef { body, .. } => {
865                for s in body {
866                    self.collect_truly_used_vars_stmt(s, used);
867                }
868            }
869            // DEPYLER-0688: Handle Block statements - must recurse into nested statements
870            HirStmt::Block(stmts) => {
871                for s in stmts {
872                    self.collect_truly_used_vars_stmt(s, used);
873                }
874            }
875            // Other statements (Pass, Break, Continue, Return(None))
876            // don't reference variables that need tracking for DCE purposes
877            _ => {}
878        }
879    }
880
881    /// DEPYLER-0270 Fix #1: Check if expression contains indexing operations
882    /// Returns true if the expression tree contains any Index nodes, which indicate
883    /// operations that can fail (e.g., list[0], dict["key"]) and have side effects.
884    ///
885    /// # Complexity
886    /// DEPYLER-0703: Check if expression has side effects (calls, indexing, etc.)
887    /// that should not be eliminated even if the result is unused.
888    /// Complexity: 5 (recursive expression traversal with early return)
889    fn expr_has_side_effects(expr: &HirExpr) -> bool {
890        match expr {
891            // Indexing has side effects (may panic)
892            HirExpr::Index { .. } => true,
893            // Function calls have side effects (may modify state, print, etc.)
894            HirExpr::Call { .. } => true,
895            // Method calls have side effects
896            HirExpr::MethodCall { .. } => true,
897            // Binary/unary ops have side effects if operands do
898            HirExpr::Binary { left, right, .. } => {
899                Self::expr_has_side_effects(left) || Self::expr_has_side_effects(right)
900            }
901            HirExpr::Unary { operand, .. } => Self::expr_has_side_effects(operand),
902            // Collections have side effects if elements do
903            HirExpr::List(items) | HirExpr::Tuple(items) => {
904                items.iter().any(Self::expr_has_side_effects)
905            }
906            HirExpr::Dict(pairs) => pairs
907                .iter()
908                .any(|(k, v)| Self::expr_has_side_effects(k) || Self::expr_has_side_effects(v)),
909            HirExpr::Set(items) => items.iter().any(Self::expr_has_side_effects),
910            HirExpr::Attribute { value, .. } => Self::expr_has_side_effects(value),
911            HirExpr::Slice {
912                base,
913                start,
914                stop,
915                step,
916            } => {
917                Self::expr_has_side_effects(base)
918                    || start
919                        .as_ref()
920                        .is_some_and(|e| Self::expr_has_side_effects(e))
921                    || stop
922                        .as_ref()
923                        .is_some_and(|e| Self::expr_has_side_effects(e))
924                    || step
925                        .as_ref()
926                        .is_some_and(|e| Self::expr_has_side_effects(e))
927            }
928            _ => false,
929        }
930    }
931
932    fn collect_used_vars_expr(&self, expr: &HirExpr, used: &mut HashMap<String, bool>) {
933        collect_used_vars_expr_inner(expr, used);
934    }
935
936    fn collect_used_vars_assign_target(
937        &self,
938        target: &AssignTarget,
939        used: &mut HashMap<String, bool>,
940    ) {
941        match target {
942            AssignTarget::Symbol(_) => {
943                // Simple variable assignment - no variables used on LHS
944            }
945            AssignTarget::Index { base, index } => {
946                // Collect from both base and index expressions
947                // e.g., `arr[i] = value` uses both `arr` and `i`
948                self.collect_used_vars_expr(base, used);
949                self.collect_used_vars_expr(index, used);
950            }
951            AssignTarget::Attribute { value, .. } => {
952                // Collect from the base object
953                // e.g., `obj.attr = value` uses `obj`
954                self.collect_used_vars_expr(value, used);
955            }
956            AssignTarget::Tuple(targets) => {
957                // Recursively collect from tuple elements
958                for t in targets {
959                    self.collect_used_vars_assign_target(t, used);
960                }
961            }
962        }
963    }
964
965    /// Inline small functions using sophisticated heuristics
966    fn inline_functions_program(&self, program: HirProgram) -> HirProgram {
967        use crate::inlining::{InliningAnalyzer, InliningConfig};
968
969        // Configure inlining based on optimizer settings
970        let config = InliningConfig {
971            max_inline_size: self.config.inline_threshold,
972            max_inline_depth: 3,
973            inline_single_use: true,
974            inline_trivial: true,
975            cost_threshold: 1.5,
976            inline_loops: false,
977        };
978
979        // Analyze the program for inlining opportunities
980        let mut analyzer = InliningAnalyzer::new(config);
981        let decisions = analyzer.analyze_program(&program);
982
983        // Report inlining decisions if verbose
984        for (func_name, decision) in &decisions {
985            if decision.should_inline {
986                eprintln!(
987                    "Inlining function '{}': {:?} (cost-benefit: {:.2})",
988                    func_name, decision.reason, decision.cost_benefit
989                );
990            }
991        }
992
993        // Apply the inlining transformations
994        analyzer.apply_inlining(program, &decisions)
995    }
996
997    /// Eliminate common subexpressions
998    fn eliminate_common_subexpressions_program(&self, mut program: HirProgram) -> HirProgram {
999        for func in &mut program.functions {
1000            let mut cse_map: HashMap<u64, (HirExpr, String)> = HashMap::new();
1001            let mut temp_counter = 0;
1002
1003            func.body = self.eliminate_cse_in_body(&func.body, &mut cse_map, &mut temp_counter);
1004        }
1005
1006        program
1007    }
1008
1009    fn eliminate_cse_in_body(
1010        &self,
1011        body: &[HirStmt],
1012        cse_map: &mut HashMap<u64, (HirExpr, String)>,
1013        temp_counter: &mut usize,
1014    ) -> Vec<HirStmt> {
1015        let mut new_body = Vec::new();
1016
1017        for (idx, stmt) in body.iter().enumerate() {
1018            let is_final_stmt = idx == body.len() - 1;
1019
1020            match stmt {
1021                HirStmt::Assign {
1022                    target,
1023                    value,
1024                    type_annotation,
1025                } => {
1026                    let (new_value, extra_stmts) =
1027                        self.process_expr_for_cse(value, cse_map, temp_counter);
1028                    new_body.extend(extra_stmts);
1029                    new_body.push(HirStmt::Assign {
1030                        target: target.clone(),
1031                        value: new_value,
1032                        type_annotation: type_annotation.clone(),
1033                    });
1034                }
1035                HirStmt::Return(Some(expr)) => {
1036                    // DEPYLER-0275 FIX: Skip CSE for final return with simple expressions
1037                    // This avoids unnecessary `let _cse_temp_0 = expr; _cse_temp_0` pattern
1038                    if is_final_stmt && self.is_simple_return_expr(expr) {
1039                        // Don't create CSE temp for final simple returns
1040                        new_body.push(HirStmt::Return(Some(expr.clone())));
1041                    } else {
1042                        let (new_expr, extra_stmts) =
1043                            self.process_expr_for_cse(expr, cse_map, temp_counter);
1044                        new_body.extend(extra_stmts);
1045                        new_body.push(HirStmt::Return(Some(new_expr)));
1046                    }
1047                }
1048                HirStmt::If {
1049                    condition,
1050                    then_body,
1051                    else_body,
1052                } => {
1053                    let (new_condition, extra_stmts) =
1054                        self.process_expr_for_cse(condition, cse_map, temp_counter);
1055                    new_body.extend(extra_stmts);
1056
1057                    // CSE within branches (with separate scopes)
1058                    let mut then_cse = cse_map.clone();
1059                    let new_then =
1060                        self.eliminate_cse_in_body(then_body, &mut then_cse, temp_counter);
1061
1062                    let new_else = else_body.as_ref().map(|else_stmts| {
1063                        let mut else_cse = cse_map.clone();
1064                        self.eliminate_cse_in_body(else_stmts, &mut else_cse, temp_counter)
1065                    });
1066
1067                    new_body.push(HirStmt::If {
1068                        condition: new_condition,
1069                        then_body: new_then,
1070                        else_body: new_else,
1071                    });
1072                }
1073                _ => new_body.push(stmt.clone()),
1074            }
1075        }
1076
1077        new_body
1078    }
1079
1080    fn process_expr_for_cse(
1081        &self,
1082        expr: &HirExpr,
1083        cse_map: &mut HashMap<u64, (HirExpr, String)>,
1084        temp_counter: &mut usize,
1085    ) -> (HirExpr, Vec<HirStmt>) {
1086        let mut extra_stmts = Vec::new();
1087
1088        // Only process complex expressions
1089        match expr {
1090            HirExpr::Binary { left, right, op } => {
1091                // Recursively process operands
1092                let (new_left, left_stmts) = self.process_expr_for_cse(left, cse_map, temp_counter);
1093                let (new_right, right_stmts) =
1094                    self.process_expr_for_cse(right, cse_map, temp_counter);
1095                extra_stmts.extend(left_stmts);
1096                extra_stmts.extend(right_stmts);
1097
1098                let new_expr = HirExpr::Binary {
1099                    op: *op,
1100                    left: Box::new(new_left),
1101                    right: Box::new(new_right),
1102                };
1103
1104                // Check if this expression is worth caching (not trivial)
1105                if self.is_complex_expr(&new_expr) {
1106                    let hash = self.hash_expr(&new_expr);
1107
1108                    if let Some((_, var_name)) = cse_map.get(&hash) {
1109                        // Reuse existing computation
1110                        (HirExpr::Var(var_name.clone()), extra_stmts)
1111                    } else {
1112                        // Create new temporary
1113                        let temp_name = format!("_cse_temp_{}", temp_counter);
1114                        *temp_counter += 1;
1115
1116                        extra_stmts.push(HirStmt::Assign {
1117                            target: AssignTarget::Symbol(temp_name.clone()),
1118                            value: new_expr.clone(),
1119                            type_annotation: None,
1120                        });
1121
1122                        cse_map.insert(hash, (new_expr, temp_name.clone()));
1123                        (HirExpr::Var(temp_name), extra_stmts)
1124                    }
1125                } else {
1126                    (new_expr, extra_stmts)
1127                }
1128            }
1129            HirExpr::Call { func, args, .. } if self.is_pure_function(func) => {
1130                // Process arguments
1131                let mut new_args = Vec::new();
1132                for arg in args {
1133                    let (new_arg, arg_stmts) =
1134                        self.process_expr_for_cse(arg, cse_map, temp_counter);
1135                    extra_stmts.extend(arg_stmts);
1136                    new_args.push(new_arg);
1137                }
1138
1139                let new_expr = HirExpr::Call {
1140                    func: func.clone(),
1141                    args: new_args,
1142                    kwargs: vec![],
1143                };
1144
1145                let hash = self.hash_expr(&new_expr);
1146
1147                if let Some((_, var_name)) = cse_map.get(&hash) {
1148                    (HirExpr::Var(var_name.clone()), extra_stmts)
1149                } else {
1150                    let temp_name = format!("_cse_temp_{}", temp_counter);
1151                    *temp_counter += 1;
1152
1153                    extra_stmts.push(HirStmt::Assign {
1154                        target: AssignTarget::Symbol(temp_name.clone()),
1155                        value: new_expr.clone(),
1156                        type_annotation: None,
1157                    });
1158
1159                    cse_map.insert(hash, (new_expr, temp_name.clone()));
1160                    (HirExpr::Var(temp_name), extra_stmts)
1161                }
1162            }
1163            _ => (expr.clone(), extra_stmts),
1164        }
1165    }
1166
1167    fn is_complex_expr(&self, expr: &HirExpr) -> bool {
1168        match expr {
1169            HirExpr::Binary { op, left, right } => {
1170                // Consider non-trivial operations or non-literal operands
1171                !matches!(op, BinOp::Add | BinOp::Sub)
1172                    || !matches!(left.as_ref(), HirExpr::Var(_) | HirExpr::Literal(_))
1173                    || !matches!(right.as_ref(), HirExpr::Var(_) | HirExpr::Literal(_))
1174            }
1175            HirExpr::Call { .. } => true,
1176            _ => false,
1177        }
1178    }
1179
1180    /// DEPYLER-0275: Check if expression is simple enough to return directly
1181    /// without creating a CSE temporary variable.
1182    /// Simple expressions: literals, variables, basic operations, method calls
1183    fn is_simple_return_expr(&self, expr: &HirExpr) -> bool {
1184        matches!(
1185            expr,
1186            HirExpr::Literal(_)
1187                | HirExpr::Var(_)
1188                | HirExpr::Binary { .. }
1189                | HirExpr::Unary { .. }
1190                | HirExpr::MethodCall { .. }
1191                | HirExpr::Call { .. }
1192                | HirExpr::Attribute { .. }
1193        )
1194    }
1195
1196    fn is_pure_function(&self, func: &str) -> bool {
1197        // List of known pure functions
1198        let pure_functions = [
1199            "abs", "len", "min", "max", "sum", "str", "int", "float", "bool", "round", "pow",
1200            "sqrt",
1201        ];
1202        pure_functions.contains(&func)
1203    }
1204
1205    fn hash_expr(&self, expr: &HirExpr) -> u64 {
1206        use std::collections::hash_map::DefaultHasher;
1207
1208        let mut hasher = DefaultHasher::new();
1209        self.hash_expr_recursive(expr, &mut hasher);
1210        hasher.finish()
1211    }
1212
1213    fn hash_expr_recursive<H: Hasher>(&self, expr: &HirExpr, hasher: &mut H) {
1214        hash_expr_recursive_inner(expr, hasher);
1215    }
1216}
1217
1218fn hash_expr_recursive_inner<H: Hasher>(expr: &HirExpr, hasher: &mut H) {
1219    match expr {
1220        HirExpr::Literal(lit) => {
1221            "literal".hash(hasher);
1222            match lit {
1223                Literal::Int(n) => n.hash(hasher),
1224                Literal::Float(f) => f.to_bits().hash(hasher),
1225                Literal::String(s) => s.hash(hasher),
1226                Literal::Bytes(b) => b.hash(hasher),
1227                Literal::Bool(b) => b.hash(hasher),
1228                Literal::None => "none".hash(hasher),
1229            }
1230        }
1231        HirExpr::Var(name) => {
1232            "var".hash(hasher);
1233            name.hash(hasher);
1234        }
1235        HirExpr::Binary { op, left, right } => {
1236            "binary".hash(hasher);
1237            format!("{:?}", op).hash(hasher);
1238            hash_expr_recursive_inner(left, hasher);
1239            hash_expr_recursive_inner(right, hasher);
1240        }
1241        HirExpr::Call { func, args, .. } => {
1242            "call".hash(hasher);
1243            func.hash(hasher);
1244            for arg in args {
1245                hash_expr_recursive_inner(arg, hasher);
1246            }
1247        }
1248        _ => {
1249            // For other expressions, use a simple discriminant
1250            format!("{:?}", std::mem::discriminant(expr)).hash(hasher);
1251        }
1252    }
1253}
1254
1255fn is_constant_expr_inner(expr: &HirExpr) -> bool {
1256    match expr {
1257        HirExpr::Literal(_) => true,
1258        HirExpr::Unary { operand, .. } => is_constant_expr_inner(operand),
1259        HirExpr::Binary { left, right, .. } => {
1260            is_constant_expr_inner(left) && is_constant_expr_inner(right)
1261        }
1262        _ => false,
1263    }
1264}
1265
1266fn collect_used_vars_expr_inner(expr: &HirExpr, used: &mut HashMap<String, bool>) {
1267    match expr {
1268        HirExpr::Var(name) => {
1269            used.insert(name.clone(), true);
1270        }
1271        HirExpr::Binary { left, right, .. } => {
1272            collect_used_vars_expr_inner(left, used);
1273            collect_used_vars_expr_inner(right, used);
1274        }
1275        HirExpr::Unary { operand, .. } => {
1276            collect_used_vars_expr_inner(operand, used);
1277        }
1278        HirExpr::List(items) => {
1279            for item in items {
1280                collect_used_vars_expr_inner(item, used);
1281            }
1282        }
1283        HirExpr::Tuple(items) => {
1284            // DEPYLER-0161 FIX: Collect variables from tuple expressions
1285            // This was causing dead code elimination to remove assignments
1286            // for variables used in tuple returns like: return (a, b, c)
1287            for item in items {
1288                collect_used_vars_expr_inner(item, used);
1289            }
1290        }
1291        HirExpr::Dict(pairs) => {
1292            for (k, v) in pairs {
1293                collect_used_vars_expr_inner(k, used);
1294                collect_used_vars_expr_inner(v, used);
1295            }
1296        }
1297        HirExpr::Call { func, args, kwargs } => {
1298            // Mark the function name as used (important for lambda variables)
1299            used.insert(func.clone(), true);
1300            for arg in args {
1301                collect_used_vars_expr_inner(arg, used);
1302            }
1303            // DEPYLER-0935: Collect variables from kwargs values
1304            // This was causing DCE to incorrectly remove assignments like `data = rows[1:]`
1305            // when `data` was used in `sorted(data, key=lambda...)` - the kwargs lambda
1306            // body might reference variables that need to be preserved.
1307            for (_, v) in kwargs {
1308                collect_used_vars_expr_inner(v, used);
1309            }
1310        }
1311        HirExpr::MethodCall {
1312            object,
1313            args,
1314            kwargs,
1315            ..
1316        } => {
1317            collect_used_vars_expr_inner(object, used);
1318            for arg in args {
1319                collect_used_vars_expr_inner(arg, used);
1320            }
1321            // DEPYLER-0935: Collect variables from kwargs values
1322            for (_, v) in kwargs {
1323                collect_used_vars_expr_inner(v, used);
1324            }
1325        }
1326        HirExpr::Lambda { body, .. } => {
1327            collect_used_vars_expr_inner(body, used);
1328        }
1329        HirExpr::ListComp {
1330            element,
1331            generators,
1332        } => {
1333            // DEPYLER-0504: Support multiple generators
1334            collect_used_vars_expr_inner(element, used);
1335            for gen in generators {
1336                collect_used_vars_expr_inner(&gen.iter, used);
1337                for cond in &gen.conditions {
1338                    collect_used_vars_expr_inner(cond, used);
1339                }
1340            }
1341        }
1342        HirExpr::SetComp {
1343            element,
1344            generators,
1345        } => {
1346            // DEPYLER-0504: Support multiple generators
1347            collect_used_vars_expr_inner(element, used);
1348            for gen in generators {
1349                collect_used_vars_expr_inner(&gen.iter, used);
1350                for cond in &gen.conditions {
1351                    collect_used_vars_expr_inner(cond, used);
1352                }
1353            }
1354        }
1355        // DEPYLER-0600 #5: DictComp was missing from DCE analysis
1356        // This caused variables used only in dict comprehension iterators to be
1357        // incorrectly removed. Example: `d = {str(n): n*n for n in nums}` lost `nums`
1358        HirExpr::DictComp {
1359            key,
1360            value,
1361            generators,
1362        } => {
1363            // Collect used vars from key and value expressions
1364            collect_used_vars_expr_inner(key, used);
1365            collect_used_vars_expr_inner(value, used);
1366            // DEPYLER-0504: Support multiple generators
1367            for gen in generators {
1368                collect_used_vars_expr_inner(&gen.iter, used);
1369                for cond in &gen.conditions {
1370                    collect_used_vars_expr_inner(cond, used);
1371                }
1372            }
1373        }
1374        HirExpr::Await { value } => {
1375            collect_used_vars_expr_inner(value, used);
1376        }
1377        HirExpr::Slice {
1378            base,
1379            start,
1380            stop,
1381            step,
1382        } => {
1383            // DEPYLER-0209 FIX: Collect variables from slice expressions
1384            // This was causing dead code elimination to remove assignments
1385            // for variables used in slice operations like: numbers[2:7]
1386            collect_used_vars_expr_inner(base, used);
1387            if let Some(start_expr) = start {
1388                collect_used_vars_expr_inner(start_expr, used);
1389            }
1390            if let Some(stop_expr) = stop {
1391                collect_used_vars_expr_inner(stop_expr, used);
1392            }
1393            if let Some(step_expr) = step {
1394                collect_used_vars_expr_inner(step_expr, used);
1395            }
1396        }
1397        HirExpr::Attribute { value, .. } => {
1398            // DEPYLER-0229 FIX: Collect variables from attribute access expressions
1399            // This was causing dead code elimination to remove assignments
1400            // for variables used in attribute access like: p.x + p.y
1401            collect_used_vars_expr_inner(value, used);
1402        }
1403        HirExpr::Index { base, index } => {
1404            // DEPYLER-0229 FIX: Collect variables from index expressions
1405            // This was causing dead code elimination to remove assignments
1406            // for variables used in indexing like: data[key]
1407            collect_used_vars_expr_inner(base, used);
1408            collect_used_vars_expr_inner(index, used);
1409        }
1410        HirExpr::FString { parts } => {
1411            // DEPYLER-0516 / GH-103: Collect variables from f-string expressions
1412            // F-strings can contain embedded expressions that reference variables
1413            // Example: f"Hello {args.name}" uses `args` variable
1414            // Without this, DCE incorrectly removes `args = parser.parse_args()`
1415            for part in parts {
1416                if let depyler_hir::hir::FStringPart::Expr(expr) = part {
1417                    collect_used_vars_expr_inner(expr, used);
1418                }
1419            }
1420        }
1421        // DEPYLER-0618: Collect variables from ternary (if-expression) expressions
1422        // Example: `out = sys.stdout if verbose else open(...)`
1423        // Without this, DCE incorrectly removes `verbose = True`
1424        HirExpr::IfExpr { test, body, orelse } => {
1425            collect_used_vars_expr_inner(test, used);
1426            collect_used_vars_expr_inner(body, used);
1427            collect_used_vars_expr_inner(orelse, used);
1428        }
1429        // DEPYLER-0935: Collect variables from SortByKey expression
1430        // sorted(data, key=lambda r: r[0]) is converted to HirExpr::SortByKey
1431        // We must collect variables from iterable, key_body, and reverse_expr
1432        HirExpr::SortByKey {
1433            iterable,
1434            key_body,
1435            reverse_expr,
1436            ..
1437        } => {
1438            collect_used_vars_expr_inner(iterable, used);
1439            collect_used_vars_expr_inner(key_body, used);
1440            if let Some(rev) = reverse_expr {
1441                collect_used_vars_expr_inner(rev, used);
1442            }
1443        }
1444        _ => {}
1445    }
1446}
1447
1448#[cfg(test)]
1449mod tests {
1450    use super::*;
1451    use depyler_hir::hir::{
1452        AssignTarget, FunctionProperties, HirExpr, HirFunction, HirProgram, HirStmt, Literal, Type,
1453    };
1454    use depyler_annotations::TranspilationAnnotations;
1455    use smallvec::smallvec;
1456
1457    #[test]
1458    fn test_constant_propagation() {
1459        // DEPYLER-0508: Dead code elimination is ENABLED by default
1460        // Unused variables should be eliminated
1461        // This test validates that DCE correctly removes dead assignments
1462        let mut optimizer = Optimizer::new(OptimizerConfig::default());
1463
1464        let program = HirProgram {
1465            functions: vec![HirFunction {
1466                name: "test".to_string(),
1467                params: smallvec![],
1468                ret_type: Type::Int,
1469                body: vec![
1470                    // Dead assignment - never read → eliminated by DCE
1471                    HirStmt::Assign {
1472                        target: AssignTarget::Symbol("unused".to_string()),
1473                        value: HirExpr::Literal(Literal::Int(42)),
1474                        type_annotation: None,
1475                    },
1476                    // Dead assignment - never read → eliminated by DCE
1477                    HirStmt::Assign {
1478                        target: AssignTarget::Symbol("result".to_string()),
1479                        value: HirExpr::Literal(Literal::Int(10)),
1480                        type_annotation: None,
1481                    },
1482                    HirStmt::Return(Some(HirExpr::Literal(Literal::Int(5)))),
1483                ],
1484                properties: FunctionProperties::default(),
1485                annotations: TranspilationAnnotations::default(),
1486                docstring: None,
1487            }],
1488            classes: vec![],
1489            imports: vec![],
1490        };
1491
1492        let optimized = optimizer.optimize_program(program);
1493
1494        // DEPYLER-0508: With DCE enabled, unused variables are eliminated
1495        // Only the return statement remains
1496        let func = &optimized.functions[0];
1497        assert_eq!(
1498            func.body.len(),
1499            1,
1500            "Dead assignments eliminated, only return statement remains (DCE enabled)"
1501        );
1502
1503        // Verify only the return statement remains
1504        assert!(matches!(&func.body[0], HirStmt::Return(_)));
1505    }
1506
1507    #[test]
1508    fn test_dead_code_elimination() {
1509        // DEPYLER-0508: Dead code elimination is now ENABLED by default
1510        // Unused variables should be eliminated
1511        let mut optimizer = Optimizer::new(OptimizerConfig::default());
1512
1513        let program = HirProgram {
1514            functions: vec![HirFunction {
1515                name: "test".to_string(),
1516                params: smallvec![],
1517                ret_type: Type::Int,
1518                body: vec![
1519                    HirStmt::Assign {
1520                        target: AssignTarget::Symbol("unused".to_string()),
1521                        value: HirExpr::Literal(Literal::Int(42)),
1522                        type_annotation: None,
1523                    },
1524                    HirStmt::Assign {
1525                        target: AssignTarget::Symbol("used".to_string()),
1526                        value: HirExpr::Literal(Literal::Int(10)),
1527                        type_annotation: None,
1528                    },
1529                    HirStmt::Return(Some(HirExpr::Var("used".to_string()))),
1530                ],
1531                properties: FunctionProperties::default(),
1532                annotations: TranspilationAnnotations::default(),
1533                docstring: None,
1534            }],
1535            classes: vec![],
1536            imports: vec![],
1537        };
1538
1539        let optimized = optimizer.optimize_program(program);
1540
1541        // Check optimization results - DCE is now enabled by default
1542        let func = &optimized.functions[0];
1543
1544        // DEPYLER-0508: DCE is enabled - unused variable should be eliminated
1545        // Statements: assignment for "used" + return = 2 statements
1546        assert_eq!(
1547            func.body.len(),
1548            2,
1549            "Unused 'unused' variable should be eliminated (DCE enabled by default)"
1550        );
1551
1552        // Verify the "used" variable and return are preserved
1553        assert!(matches!(&func.body[0], HirStmt::Assign { .. }));
1554        assert!(matches!(&func.body[1], HirStmt::Return(_)));
1555    }
1556
1557    #[test]
1558    fn test_dead_code_elimination_when_enabled() {
1559        // Test what happens when dead code elimination IS explicitly enabled
1560        let config = OptimizerConfig {
1561            eliminate_dead_code: true,
1562            ..Default::default()
1563        };
1564        let mut optimizer = Optimizer::new(config);
1565
1566        let program = HirProgram {
1567            functions: vec![HirFunction {
1568                name: "test".to_string(),
1569                params: smallvec![],
1570                ret_type: Type::Int,
1571                body: vec![
1572                    HirStmt::Assign {
1573                        target: AssignTarget::Symbol("unused".to_string()),
1574                        value: HirExpr::Literal(Literal::Int(42)),
1575                        type_annotation: None,
1576                    },
1577                    HirStmt::Assign {
1578                        target: AssignTarget::Symbol("used".to_string()),
1579                        value: HirExpr::Literal(Literal::Int(10)),
1580                        type_annotation: None,
1581                    },
1582                    HirStmt::Return(Some(HirExpr::Var("used".to_string()))),
1583                ],
1584                properties: FunctionProperties::default(),
1585                annotations: TranspilationAnnotations::default(),
1586                docstring: None,
1587            }],
1588            classes: vec![],
1589            imports: vec![],
1590        };
1591
1592        let optimized = optimizer.optimize_program(program);
1593
1594        let func = &optimized.functions[0];
1595
1596        // When enabled, unused assignment should be removed
1597        // "used" assignment is kept because it's read in return
1598        assert!(
1599            func.body.len() <= 3,
1600            "Dead code elimination should remove or preserve statements"
1601        );
1602
1603        // At minimum, the return statement should exist
1604        let has_return = func
1605            .body
1606            .iter()
1607            .any(|stmt| matches!(stmt, HirStmt::Return(_)));
1608        assert!(has_return, "Return statement should be preserved");
1609    }
1610
1611    #[test]
1612    #[allow(non_snake_case)]
1613    fn test_DEPYLER_0508_dead_code_elimination_enabled_by_default() {
1614        // DEPYLER-0508: DCE should be enabled by default
1615        // Unused variables should be eliminated without explicit opt-in
1616        let config = OptimizerConfig::default();
1617
1618        // Verify DCE is enabled by default
1619        assert!(
1620            config.eliminate_dead_code,
1621            "DEPYLER-0508: Dead code elimination should be enabled by default"
1622        );
1623
1624        let mut optimizer = Optimizer::new(config);
1625
1626        let program = HirProgram {
1627            functions: vec![HirFunction {
1628                name: "test".to_string(),
1629                params: smallvec![],
1630                ret_type: Type::Int,
1631                body: vec![
1632                    // Unused variable - should be eliminated
1633                    HirStmt::Assign {
1634                        target: AssignTarget::Symbol("unused".to_string()),
1635                        value: HirExpr::Literal(Literal::Int(42)),
1636                        type_annotation: None,
1637                    },
1638                    // Used variable - should be kept
1639                    HirStmt::Assign {
1640                        target: AssignTarget::Symbol("used".to_string()),
1641                        value: HirExpr::Literal(Literal::Int(10)),
1642                        type_annotation: None,
1643                    },
1644                    HirStmt::Return(Some(HirExpr::Var("used".to_string()))),
1645                ],
1646                properties: FunctionProperties::default(),
1647                annotations: TranspilationAnnotations::default(),
1648                docstring: None,
1649            }],
1650            classes: vec![],
1651            imports: vec![],
1652        };
1653
1654        let optimized = optimizer.optimize_program(program);
1655        let func = &optimized.functions[0];
1656
1657        // With DCE enabled by default, unused assignment should be removed
1658        // Statements should be: assignment for "used" + return = 2 statements
1659        assert_eq!(
1660            func.body.len(),
1661            2,
1662            "DEPYLER-0508: Unused 'unused' variable should be eliminated by default"
1663        );
1664
1665        // Verify the unused variable was removed, not just renamed
1666        let has_unused = func.body.iter().any(|stmt| {
1667            if let HirStmt::Assign {
1668                target: AssignTarget::Symbol(name),
1669                ..
1670            } = stmt
1671            {
1672                return name == "unused" || name == "_unused";
1673            }
1674            false
1675        });
1676        assert!(
1677            !has_unused,
1678            "DEPYLER-0508: 'unused' variable should be completely eliminated"
1679        );
1680    }
1681
1682    // ==========================================================================
1683    // DEPYLER-COVERAGE-95: Lines-to-test ratio improvement tests
1684    // Target: ratio 405 → <30
1685    // ==========================================================================
1686
1687    #[test]
1688    fn test_optimizer_config_default() {
1689        let config = OptimizerConfig::default();
1690        // DEPYLER-0161: inlining is disabled
1691        assert!(!config.inline_functions);
1692        assert!(config.eliminate_dead_code);
1693        assert!(config.propagate_constants);
1694        assert!(config.eliminate_common_subexpressions);
1695        assert_eq!(config.inline_threshold, 20);
1696    }
1697
1698    #[test]
1699    fn test_optimizer_new() {
1700        let config = OptimizerConfig::default();
1701        let optimizer = Optimizer::new(config.clone());
1702        assert_eq!(optimizer.config.inline_threshold, config.inline_threshold);
1703    }
1704
1705    #[test]
1706    fn test_is_constant_expr_literals() {
1707        let optimizer = Optimizer::new(OptimizerConfig::default());
1708        assert!(optimizer.is_constant_expr(&HirExpr::Literal(Literal::Int(42))));
1709        assert!(optimizer.is_constant_expr(&HirExpr::Literal(Literal::Float(3.15))));
1710        assert!(optimizer.is_constant_expr(&HirExpr::Literal(Literal::Bool(true))));
1711        assert!(optimizer.is_constant_expr(&HirExpr::Literal(Literal::String("hello".into()))));
1712    }
1713
1714    #[test]
1715    fn test_is_constant_expr_non_constant() {
1716        let optimizer = Optimizer::new(OptimizerConfig::default());
1717        assert!(!optimizer.is_constant_expr(&HirExpr::Var("x".to_string())));
1718        assert!(!optimizer.is_constant_expr(&HirExpr::Call {
1719            func: "foo".to_string(),
1720            args: vec![],
1721            kwargs: vec![],
1722        }));
1723    }
1724
1725    #[test]
1726    fn test_evaluate_constant_binop_int_add() {
1727        let optimizer = Optimizer::new(OptimizerConfig::default());
1728        let expr = HirExpr::Binary {
1729            left: Box::new(HirExpr::Literal(Literal::Int(5))),
1730            op: BinOp::Add,
1731            right: Box::new(HirExpr::Literal(Literal::Int(3))),
1732        };
1733        let result = optimizer.evaluate_constant_binop(&expr);
1734        assert!(result.is_some());
1735        if let Some(HirExpr::Literal(Literal::Int(v))) = result {
1736            assert_eq!(v, 8);
1737        }
1738    }
1739
1740    #[test]
1741    fn test_evaluate_constant_binop_int_sub() {
1742        let optimizer = Optimizer::new(OptimizerConfig::default());
1743        let expr = HirExpr::Binary {
1744            left: Box::new(HirExpr::Literal(Literal::Int(10))),
1745            op: BinOp::Sub,
1746            right: Box::new(HirExpr::Literal(Literal::Int(3))),
1747        };
1748        let result = optimizer.evaluate_constant_binop(&expr);
1749        assert!(result.is_some());
1750        if let Some(HirExpr::Literal(Literal::Int(v))) = result {
1751            assert_eq!(v, 7);
1752        }
1753    }
1754
1755    #[test]
1756    fn test_evaluate_constant_binop_int_mul() {
1757        let optimizer = Optimizer::new(OptimizerConfig::default());
1758        let expr = HirExpr::Binary {
1759            left: Box::new(HirExpr::Literal(Literal::Int(4))),
1760            op: BinOp::Mul,
1761            right: Box::new(HirExpr::Literal(Literal::Int(3))),
1762        };
1763        let result = optimizer.evaluate_constant_binop(&expr);
1764        assert!(result.is_some());
1765        if let Some(HirExpr::Literal(Literal::Int(v))) = result {
1766            assert_eq!(v, 12);
1767        }
1768    }
1769
1770    #[test]
1771    fn test_evaluate_constant_binop_int_div() {
1772        let optimizer = Optimizer::new(OptimizerConfig::default());
1773        let expr = HirExpr::Binary {
1774            left: Box::new(HirExpr::Literal(Literal::Int(10))),
1775            op: BinOp::Div,
1776            right: Box::new(HirExpr::Literal(Literal::Int(2))),
1777        };
1778        let result = optimizer.evaluate_constant_binop(&expr);
1779        assert!(result.is_some());
1780        if let Some(HirExpr::Literal(Literal::Int(v))) = result {
1781            assert_eq!(v, 5);
1782        }
1783    }
1784
1785    #[test]
1786    fn test_evaluate_constant_binop_int_mod_unsupported() {
1787        // Mod is not supported in constant folding - returns None
1788        let optimizer = Optimizer::new(OptimizerConfig::default());
1789        let expr = HirExpr::Binary {
1790            left: Box::new(HirExpr::Literal(Literal::Int(10))),
1791            op: BinOp::Mod,
1792            right: Box::new(HirExpr::Literal(Literal::Int(3))),
1793        };
1794        let result = optimizer.evaluate_constant_binop(&expr);
1795        assert!(result.is_none());
1796    }
1797
1798    #[test]
1799    fn test_evaluate_constant_binop_bool_and_unsupported() {
1800        // Boolean And is not supported in constant folding - returns None
1801        let optimizer = Optimizer::new(OptimizerConfig::default());
1802        let expr = HirExpr::Binary {
1803            left: Box::new(HirExpr::Literal(Literal::Bool(true))),
1804            op: BinOp::And,
1805            right: Box::new(HirExpr::Literal(Literal::Bool(false))),
1806        };
1807        let result = optimizer.evaluate_constant_binop(&expr);
1808        assert!(result.is_none());
1809    }
1810
1811    #[test]
1812    fn test_evaluate_constant_binop_bool_or_unsupported() {
1813        // Boolean Or is not supported in constant folding - returns None
1814        let optimizer = Optimizer::new(OptimizerConfig::default());
1815        let expr = HirExpr::Binary {
1816            left: Box::new(HirExpr::Literal(Literal::Bool(true))),
1817            op: BinOp::Or,
1818            right: Box::new(HirExpr::Literal(Literal::Bool(false))),
1819        };
1820        let result = optimizer.evaluate_constant_binop(&expr);
1821        assert!(result.is_none());
1822    }
1823
1824    #[test]
1825    fn test_evaluate_constant_binop_comparison_eq_unsupported() {
1826        // Comparison Eq is not supported in constant folding - returns None
1827        let optimizer = Optimizer::new(OptimizerConfig::default());
1828        let expr = HirExpr::Binary {
1829            left: Box::new(HirExpr::Literal(Literal::Int(5))),
1830            op: BinOp::Eq,
1831            right: Box::new(HirExpr::Literal(Literal::Int(5))),
1832        };
1833        let result = optimizer.evaluate_constant_binop(&expr);
1834        assert!(result.is_none());
1835    }
1836
1837    #[test]
1838    fn test_evaluate_constant_binop_comparison_lt_unsupported() {
1839        // Comparison Lt is not supported in constant folding - returns None
1840        let optimizer = Optimizer::new(OptimizerConfig::default());
1841        let expr = HirExpr::Binary {
1842            left: Box::new(HirExpr::Literal(Literal::Int(3))),
1843            op: BinOp::Lt,
1844            right: Box::new(HirExpr::Literal(Literal::Int(5))),
1845        };
1846        let result = optimizer.evaluate_constant_binop(&expr);
1847        assert!(result.is_none());
1848    }
1849
1850    #[test]
1851    fn test_evaluate_constant_unaryop_not() {
1852        let optimizer = Optimizer::new(OptimizerConfig::default());
1853        let expr = HirExpr::Unary {
1854            op: UnaryOp::Not,
1855            operand: Box::new(HirExpr::Literal(Literal::Bool(true))),
1856        };
1857        let result = optimizer.evaluate_constant_unaryop(&expr);
1858        assert!(result.is_some());
1859        if let Some(HirExpr::Literal(Literal::Bool(v))) = result {
1860            assert!(!v);
1861        }
1862    }
1863
1864    #[test]
1865    fn test_evaluate_constant_unaryop_neg_int() {
1866        let optimizer = Optimizer::new(OptimizerConfig::default());
1867        let expr = HirExpr::Unary {
1868            op: UnaryOp::Neg,
1869            operand: Box::new(HirExpr::Literal(Literal::Int(5))),
1870        };
1871        let result = optimizer.evaluate_constant_unaryop(&expr);
1872        assert!(result.is_some());
1873        if let Some(HirExpr::Literal(Literal::Int(v))) = result {
1874            assert_eq!(v, -5);
1875        }
1876    }
1877
1878    #[test]
1879    fn test_evaluate_constant_unaryop_neg_float() {
1880        let optimizer = Optimizer::new(OptimizerConfig::default());
1881        let expr = HirExpr::Unary {
1882            op: UnaryOp::Neg,
1883            operand: Box::new(HirExpr::Literal(Literal::Float(3.15))),
1884        };
1885        let result = optimizer.evaluate_constant_unaryop(&expr);
1886        assert!(result.is_some());
1887        if let Some(HirExpr::Literal(Literal::Float(v))) = result {
1888            assert!((v - (-3.15)).abs() < 0.001);
1889        }
1890    }
1891
1892    #[test]
1893    fn test_expr_has_side_effects_call() {
1894        // Function calls have side effects
1895        let expr = HirExpr::Call {
1896            func: "print".to_string(),
1897            args: vec![],
1898            kwargs: vec![],
1899        };
1900        assert!(Optimizer::expr_has_side_effects(&expr));
1901    }
1902
1903    #[test]
1904    fn test_expr_has_side_effects_method_call() {
1905        let expr = HirExpr::MethodCall {
1906            object: Box::new(HirExpr::Var("x".to_string())),
1907            method: "append".to_string(),
1908            args: vec![],
1909            kwargs: vec![],
1910        };
1911        assert!(Optimizer::expr_has_side_effects(&expr));
1912    }
1913
1914    #[test]
1915    fn test_expr_has_side_effects_literal() {
1916        // Literals don't have side effects
1917        let expr = HirExpr::Literal(Literal::Int(42));
1918        assert!(!Optimizer::expr_has_side_effects(&expr));
1919    }
1920
1921    #[test]
1922    fn test_expr_has_side_effects_var() {
1923        // Variable reads don't have side effects
1924        let expr = HirExpr::Var("x".to_string());
1925        assert!(!Optimizer::expr_has_side_effects(&expr));
1926    }
1927
1928    #[test]
1929    fn test_expr_has_side_effects_binary() {
1930        // Pure binary ops don't have side effects
1931        let expr = HirExpr::Binary {
1932            left: Box::new(HirExpr::Literal(Literal::Int(1))),
1933            op: BinOp::Add,
1934            right: Box::new(HirExpr::Literal(Literal::Int(2))),
1935        };
1936        assert!(!Optimizer::expr_has_side_effects(&expr));
1937    }
1938
1939    #[test]
1940    fn test_is_pure_function() {
1941        let optimizer = Optimizer::new(OptimizerConfig::default());
1942        assert!(optimizer.is_pure_function("len"));
1943        assert!(optimizer.is_pure_function("str"));
1944        assert!(optimizer.is_pure_function("int"));
1945        assert!(optimizer.is_pure_function("float"));
1946        assert!(optimizer.is_pure_function("bool"));
1947        assert!(optimizer.is_pure_function("abs"));
1948        assert!(optimizer.is_pure_function("min"));
1949        assert!(optimizer.is_pure_function("max"));
1950    }
1951
1952    #[test]
1953    fn test_is_pure_function_impure() {
1954        let optimizer = Optimizer::new(OptimizerConfig::default());
1955        assert!(!optimizer.is_pure_function("print"));
1956        assert!(!optimizer.is_pure_function("input"));
1957        assert!(!optimizer.is_pure_function("open"));
1958        assert!(!optimizer.is_pure_function("custom_func"));
1959    }
1960
1961    #[test]
1962    fn test_is_complex_expr_literal() {
1963        let optimizer = Optimizer::new(OptimizerConfig::default());
1964        let expr = HirExpr::Literal(Literal::Int(42));
1965        assert!(!optimizer.is_complex_expr(&expr));
1966    }
1967
1968    #[test]
1969    fn test_is_complex_expr_var() {
1970        let optimizer = Optimizer::new(OptimizerConfig::default());
1971        let expr = HirExpr::Var("x".to_string());
1972        assert!(!optimizer.is_complex_expr(&expr));
1973    }
1974
1975    #[test]
1976    fn test_is_complex_expr_binary_mul() {
1977        // Mul is considered complex (only Add/Sub with simple operands are simple)
1978        let optimizer = Optimizer::new(OptimizerConfig::default());
1979        let expr = HirExpr::Binary {
1980            left: Box::new(HirExpr::Var("x".to_string())),
1981            op: BinOp::Mul,
1982            right: Box::new(HirExpr::Var("y".to_string())),
1983        };
1984        assert!(optimizer.is_complex_expr(&expr));
1985    }
1986
1987    #[test]
1988    fn test_is_complex_expr_binary_add_simple() {
1989        // Add with Var operands is NOT complex
1990        let optimizer = Optimizer::new(OptimizerConfig::default());
1991        let expr = HirExpr::Binary {
1992            left: Box::new(HirExpr::Var("x".to_string())),
1993            op: BinOp::Add,
1994            right: Box::new(HirExpr::Var("y".to_string())),
1995        };
1996        assert!(!optimizer.is_complex_expr(&expr));
1997    }
1998
1999    #[test]
2000    fn test_is_complex_expr_call() {
2001        let optimizer = Optimizer::new(OptimizerConfig::default());
2002        let expr = HirExpr::Call {
2003            func: "foo".to_string(),
2004            args: vec![HirExpr::Var("x".to_string())],
2005            kwargs: vec![],
2006        };
2007        assert!(optimizer.is_complex_expr(&expr));
2008    }
2009
2010    #[test]
2011    fn test_is_simple_return_expr() {
2012        let optimizer = Optimizer::new(OptimizerConfig::default());
2013        assert!(optimizer.is_simple_return_expr(&HirExpr::Literal(Literal::Int(42))));
2014        assert!(optimizer.is_simple_return_expr(&HirExpr::Var("x".to_string())));
2015        assert!(optimizer.is_simple_return_expr(&HirExpr::Literal(Literal::Bool(true))));
2016    }
2017
2018    #[test]
2019    fn test_is_simple_return_expr_binary() {
2020        // Binary expressions ARE considered simple return expressions
2021        let optimizer = Optimizer::new(OptimizerConfig::default());
2022        let expr = HirExpr::Binary {
2023            left: Box::new(HirExpr::Var("x".to_string())),
2024            op: BinOp::Add,
2025            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2026        };
2027        assert!(optimizer.is_simple_return_expr(&expr));
2028    }
2029
2030    #[test]
2031    fn test_is_simple_return_expr_list() {
2032        // List expressions are NOT simple return expressions
2033        let optimizer = Optimizer::new(OptimizerConfig::default());
2034        let expr = HirExpr::List(vec![HirExpr::Literal(Literal::Int(1))]);
2035        assert!(!optimizer.is_simple_return_expr(&expr));
2036    }
2037
2038    #[test]
2039    fn test_hash_expr_same_expr_same_hash() {
2040        let optimizer = Optimizer::new(OptimizerConfig::default());
2041        let expr1 = HirExpr::Binary {
2042            left: Box::new(HirExpr::Var("x".to_string())),
2043            op: BinOp::Add,
2044            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2045        };
2046        let expr2 = HirExpr::Binary {
2047            left: Box::new(HirExpr::Var("x".to_string())),
2048            op: BinOp::Add,
2049            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2050        };
2051        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2052    }
2053
2054    #[test]
2055    fn test_hash_expr_different_expr_different_hash() {
2056        let optimizer = Optimizer::new(OptimizerConfig::default());
2057        let expr1 = HirExpr::Var("x".to_string());
2058        let expr2 = HirExpr::Var("y".to_string());
2059        assert_ne!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2060    }
2061
2062    #[test]
2063    fn test_hoist_walrus_operators_basic() {
2064        let optimizer = Optimizer::new(OptimizerConfig::default());
2065        let program = HirProgram {
2066            functions: vec![HirFunction {
2067                name: "test".to_string(),
2068                params: smallvec![],
2069                ret_type: Type::Int,
2070                body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
2071                properties: FunctionProperties::default(),
2072                annotations: TranspilationAnnotations::default(),
2073                docstring: None,
2074            }],
2075            classes: vec![],
2076            imports: vec![],
2077        };
2078        let result = optimizer.hoist_walrus_operators(program);
2079        // Basic case with no walrus operators should remain unchanged
2080        assert_eq!(result.functions[0].body.len(), 1);
2081    }
2082
2083    #[test]
2084    fn test_extract_walrus_from_expr_no_walrus() {
2085        let optimizer = Optimizer::new(OptimizerConfig::default());
2086        let expr = HirExpr::Binary {
2087            left: Box::new(HirExpr::Var("x".to_string())),
2088            op: BinOp::Add,
2089            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2090        };
2091        let (assigns, result) = optimizer.extract_walrus_from_expr(&expr);
2092        assert!(assigns.is_empty());
2093        // Result should be equivalent to original
2094        assert!(matches!(result, HirExpr::Binary { .. }));
2095    }
2096
2097    #[test]
2098    fn test_collect_read_vars_expr() {
2099        let mut read_vars = HashSet::new();
2100        let expr = HirExpr::Binary {
2101            left: Box::new(HirExpr::Var("x".to_string())),
2102            op: BinOp::Add,
2103            right: Box::new(HirExpr::Var("y".to_string())),
2104        };
2105        Optimizer::collect_read_vars_expr(&expr, &mut read_vars);
2106        assert!(read_vars.contains("x"));
2107        assert!(read_vars.contains("y"));
2108    }
2109
2110    #[test]
2111    fn test_collect_read_vars_expr_call() {
2112        let mut read_vars = HashSet::new();
2113        let expr = HirExpr::Call {
2114            func: "foo".to_string(),
2115            args: vec![HirExpr::Var("a".to_string()), HirExpr::Var("b".to_string())],
2116            kwargs: vec![],
2117        };
2118        Optimizer::collect_read_vars_expr(&expr, &mut read_vars);
2119        assert!(read_vars.contains("a"));
2120        assert!(read_vars.contains("b"));
2121    }
2122
2123    #[test]
2124    fn test_collect_read_vars_stmt_assign() {
2125        let mut read_vars = HashSet::new();
2126        let stmt = HirStmt::Assign {
2127            target: AssignTarget::Symbol("result".to_string()),
2128            value: HirExpr::Var("input".to_string()),
2129            type_annotation: None,
2130        };
2131        Optimizer::collect_read_vars_stmt(&stmt, &mut read_vars);
2132        assert!(read_vars.contains("input"));
2133        assert!(!read_vars.contains("result")); // target is written, not read
2134    }
2135
2136    #[test]
2137    fn test_collect_read_vars_stmt_return() {
2138        let mut read_vars = HashSet::new();
2139        let stmt = HirStmt::Return(Some(HirExpr::Var("value".to_string())));
2140        Optimizer::collect_read_vars_stmt(&stmt, &mut read_vars);
2141        assert!(read_vars.contains("value"));
2142    }
2143
2144    #[test]
2145    fn test_is_constant_expr_inner_literals() {
2146        assert!(is_constant_expr_inner(&HirExpr::Literal(Literal::Int(42))));
2147        assert!(is_constant_expr_inner(&HirExpr::Literal(Literal::Float(
2148            3.15
2149        ))));
2150        assert!(is_constant_expr_inner(&HirExpr::Literal(Literal::Bool(
2151            true
2152        ))));
2153        assert!(is_constant_expr_inner(&HirExpr::Literal(Literal::String(
2154            "hi".into()
2155        ))));
2156        assert!(is_constant_expr_inner(&HirExpr::Literal(Literal::None)));
2157    }
2158
2159    #[test]
2160    fn test_is_constant_expr_inner_non_constant() {
2161        assert!(!is_constant_expr_inner(&HirExpr::Var("x".to_string())));
2162        assert!(!is_constant_expr_inner(&HirExpr::Call {
2163            func: "foo".to_string(),
2164            args: vec![],
2165            kwargs: vec![],
2166        }));
2167    }
2168
2169    #[test]
2170    fn test_collect_used_vars_expr_inner() {
2171        let mut used = HashMap::new();
2172        let expr = HirExpr::Binary {
2173            left: Box::new(HirExpr::Var("a".to_string())),
2174            op: BinOp::Mul,
2175            right: Box::new(HirExpr::Var("b".to_string())),
2176        };
2177        collect_used_vars_expr_inner(&expr, &mut used);
2178        assert!(used.contains_key("a"));
2179        assert!(used.contains_key("b"));
2180    }
2181
2182    #[test]
2183    fn test_optimizer_with_empty_program() {
2184        let mut optimizer = Optimizer::new(OptimizerConfig::default());
2185        let program = HirProgram {
2186            functions: vec![],
2187            classes: vec![],
2188            imports: vec![],
2189        };
2190        let result = optimizer.optimize_program(program);
2191        assert!(result.functions.is_empty());
2192    }
2193
2194    #[test]
2195    fn test_optimizer_preserves_return_statements() {
2196        let mut optimizer = Optimizer::new(OptimizerConfig::default());
2197        let program = HirProgram {
2198            functions: vec![HirFunction {
2199                name: "test".to_string(),
2200                params: smallvec![],
2201                ret_type: Type::Int,
2202                body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
2203                properties: FunctionProperties::default(),
2204                annotations: TranspilationAnnotations::default(),
2205                docstring: None,
2206            }],
2207            classes: vec![],
2208            imports: vec![],
2209        };
2210        let result = optimizer.optimize_program(program);
2211        assert_eq!(result.functions[0].body.len(), 1);
2212        assert!(matches!(result.functions[0].body[0], HirStmt::Return(_)));
2213    }
2214
2215    #[test]
2216    fn test_optimizer_config_clone() {
2217        let config = OptimizerConfig::default();
2218        let cloned = config.clone();
2219        assert_eq!(config.inline_functions, cloned.inline_functions);
2220        assert_eq!(config.eliminate_dead_code, cloned.eliminate_dead_code);
2221        assert_eq!(config.propagate_constants, cloned.propagate_constants);
2222    }
2223
2224    #[test]
2225    fn test_evaluate_constant_binop_non_constant() {
2226        let optimizer = Optimizer::new(OptimizerConfig::default());
2227        let expr = HirExpr::Binary {
2228            left: Box::new(HirExpr::Var("x".to_string())),
2229            op: BinOp::Add,
2230            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2231        };
2232        let result = optimizer.evaluate_constant_binop(&expr);
2233        assert!(result.is_none());
2234    }
2235
2236    #[test]
2237    fn test_evaluate_constant_unaryop_non_constant() {
2238        let optimizer = Optimizer::new(OptimizerConfig::default());
2239        let expr = HirExpr::Unary {
2240            op: UnaryOp::Not,
2241            operand: Box::new(HirExpr::Var("x".to_string())),
2242        };
2243        let result = optimizer.evaluate_constant_unaryop(&expr);
2244        assert!(result.is_none());
2245    }
2246
2247    #[test]
2248    fn test_constant_propagation_multiple_vars() {
2249        let config = OptimizerConfig {
2250            propagate_constants: true,
2251            eliminate_dead_code: false,
2252            ..Default::default()
2253        };
2254        let mut optimizer = Optimizer::new(config);
2255
2256        let program = HirProgram {
2257            functions: vec![HirFunction {
2258                name: "test".to_string(),
2259                params: smallvec![],
2260                ret_type: Type::Int,
2261                body: vec![
2262                    HirStmt::Assign {
2263                        target: AssignTarget::Symbol("a".to_string()),
2264                        value: HirExpr::Literal(Literal::Int(10)),
2265                        type_annotation: None,
2266                    },
2267                    HirStmt::Assign {
2268                        target: AssignTarget::Symbol("b".to_string()),
2269                        value: HirExpr::Literal(Literal::Int(20)),
2270                        type_annotation: None,
2271                    },
2272                    HirStmt::Return(Some(HirExpr::Binary {
2273                        left: Box::new(HirExpr::Var("a".to_string())),
2274                        op: BinOp::Add,
2275                        right: Box::new(HirExpr::Var("b".to_string())),
2276                    })),
2277                ],
2278                properties: FunctionProperties::default(),
2279                annotations: TranspilationAnnotations::default(),
2280                docstring: None,
2281            }],
2282            classes: vec![],
2283            imports: vec![],
2284        };
2285
2286        let result = optimizer.optimize_program(program);
2287        assert!(!result.functions.is_empty());
2288    }
2289
2290    #[test]
2291    fn test_cse_eliminates_common_subexpressions() {
2292        let config = OptimizerConfig {
2293            eliminate_common_subexpressions: true,
2294            eliminate_dead_code: false,
2295            propagate_constants: false,
2296            ..Default::default()
2297        };
2298        let mut optimizer = Optimizer::new(config);
2299
2300        let complex_expr = HirExpr::Binary {
2301            left: Box::new(HirExpr::Var("x".to_string())),
2302            op: BinOp::Mul,
2303            right: Box::new(HirExpr::Var("y".to_string())),
2304        };
2305
2306        let program = HirProgram {
2307            functions: vec![HirFunction {
2308                name: "test".to_string(),
2309                params: smallvec![],
2310                ret_type: Type::Int,
2311                body: vec![
2312                    HirStmt::Assign {
2313                        target: AssignTarget::Symbol("a".to_string()),
2314                        value: complex_expr.clone(),
2315                        type_annotation: None,
2316                    },
2317                    HirStmt::Assign {
2318                        target: AssignTarget::Symbol("b".to_string()),
2319                        value: complex_expr,
2320                        type_annotation: None,
2321                    },
2322                    HirStmt::Return(Some(HirExpr::Binary {
2323                        left: Box::new(HirExpr::Var("a".to_string())),
2324                        op: BinOp::Add,
2325                        right: Box::new(HirExpr::Var("b".to_string())),
2326                    })),
2327                ],
2328                properties: FunctionProperties::default(),
2329                annotations: TranspilationAnnotations::default(),
2330                docstring: None,
2331            }],
2332            classes: vec![],
2333            imports: vec![],
2334        };
2335
2336        let result = optimizer.optimize_program(program);
2337        // CSE should run and produce a valid program
2338        assert!(!result.functions.is_empty());
2339    }
2340
2341    // ========================================================
2342    // DEPYLER-COVERAGE-95: Additional collect_used_vars tests
2343    // ========================================================
2344
2345    #[test]
2346    fn test_collect_used_vars_tuple() {
2347        let mut used = HashMap::new();
2348        let expr = HirExpr::Tuple(vec![
2349            HirExpr::Var("a".to_string()),
2350            HirExpr::Var("b".to_string()),
2351        ]);
2352        collect_used_vars_expr_inner(&expr, &mut used);
2353        assert!(used.contains_key("a"));
2354        assert!(used.contains_key("b"));
2355    }
2356
2357    #[test]
2358    fn test_collect_used_vars_list() {
2359        let mut used = HashMap::new();
2360        let expr = HirExpr::List(vec![
2361            HirExpr::Var("x".to_string()),
2362            HirExpr::Var("y".to_string()),
2363        ]);
2364        collect_used_vars_expr_inner(&expr, &mut used);
2365        assert!(used.contains_key("x"));
2366        assert!(used.contains_key("y"));
2367    }
2368
2369    #[test]
2370    fn test_collect_used_vars_dict() {
2371        let mut used = HashMap::new();
2372        let expr = HirExpr::Dict(vec![(
2373            HirExpr::Var("key".to_string()),
2374            HirExpr::Var("val".to_string()),
2375        )]);
2376        collect_used_vars_expr_inner(&expr, &mut used);
2377        assert!(used.contains_key("key"));
2378        assert!(used.contains_key("val"));
2379    }
2380
2381    #[test]
2382    fn test_collect_used_vars_unary() {
2383        let mut used = HashMap::new();
2384        let expr = HirExpr::Unary {
2385            op: UnaryOp::Not,
2386            operand: Box::new(HirExpr::Var("flag".to_string())),
2387        };
2388        collect_used_vars_expr_inner(&expr, &mut used);
2389        assert!(used.contains_key("flag"));
2390    }
2391
2392    #[test]
2393    fn test_collect_used_vars_call_with_kwargs() {
2394        let mut used = HashMap::new();
2395        let expr = HirExpr::Call {
2396            func: "func".to_string(),
2397            args: vec![HirExpr::Var("arg".to_string())],
2398            kwargs: vec![("k".to_string(), HirExpr::Var("kwval".to_string()))],
2399        };
2400        collect_used_vars_expr_inner(&expr, &mut used);
2401        assert!(used.contains_key("arg"));
2402        assert!(used.contains_key("kwval"));
2403        assert!(used.contains_key("func"));
2404    }
2405
2406    #[test]
2407    fn test_collect_used_vars_method_call() {
2408        let mut used = HashMap::new();
2409        let expr = HirExpr::MethodCall {
2410            object: Box::new(HirExpr::Var("obj".to_string())),
2411            method: "method".to_string(),
2412            args: vec![HirExpr::Var("arg".to_string())],
2413            kwargs: vec![],
2414        };
2415        collect_used_vars_expr_inner(&expr, &mut used);
2416        assert!(used.contains_key("obj"));
2417        assert!(used.contains_key("arg"));
2418    }
2419
2420    #[test]
2421    fn test_collect_used_vars_method_call_kwargs() {
2422        let mut used = HashMap::new();
2423        let expr = HirExpr::MethodCall {
2424            object: Box::new(HirExpr::Var("obj".to_string())),
2425            method: "m".to_string(),
2426            args: vec![],
2427            kwargs: vec![("k".to_string(), HirExpr::Var("v".to_string()))],
2428        };
2429        collect_used_vars_expr_inner(&expr, &mut used);
2430        assert!(used.contains_key("v"));
2431    }
2432
2433    #[test]
2434    fn test_collect_used_vars_lambda() {
2435        let mut used = HashMap::new();
2436        let expr = HirExpr::Lambda {
2437            params: vec!["x".to_string()],
2438            body: Box::new(HirExpr::Var("captured".to_string())),
2439        };
2440        collect_used_vars_expr_inner(&expr, &mut used);
2441        assert!(used.contains_key("captured"));
2442    }
2443
2444    #[test]
2445    fn test_collect_used_vars_list_comp() {
2446        use depyler_hir::hir::HirComprehension;
2447        let mut used = HashMap::new();
2448        let expr = HirExpr::ListComp {
2449            element: Box::new(HirExpr::Var("elem".to_string())),
2450            generators: vec![HirComprehension {
2451                target: "i".to_string(),
2452                iter: Box::new(HirExpr::Var("items".to_string())),
2453                conditions: vec![HirExpr::Var("cond".to_string())],
2454            }],
2455        };
2456        collect_used_vars_expr_inner(&expr, &mut used);
2457        assert!(used.contains_key("elem"));
2458        assert!(used.contains_key("items"));
2459        assert!(used.contains_key("cond"));
2460    }
2461
2462    #[test]
2463    fn test_collect_used_vars_set_comp() {
2464        use depyler_hir::hir::HirComprehension;
2465        let mut used = HashMap::new();
2466        let expr = HirExpr::SetComp {
2467            element: Box::new(HirExpr::Var("elem".to_string())),
2468            generators: vec![HirComprehension {
2469                target: "i".to_string(),
2470                iter: Box::new(HirExpr::Var("items".to_string())),
2471                conditions: vec![],
2472            }],
2473        };
2474        collect_used_vars_expr_inner(&expr, &mut used);
2475        assert!(used.contains_key("elem"));
2476        assert!(used.contains_key("items"));
2477    }
2478
2479    #[test]
2480    fn test_collect_used_vars_dict_comp() {
2481        use depyler_hir::hir::HirComprehension;
2482        let mut used = HashMap::new();
2483        let expr = HirExpr::DictComp {
2484            key: Box::new(HirExpr::Var("k".to_string())),
2485            value: Box::new(HirExpr::Var("v".to_string())),
2486            generators: vec![HirComprehension {
2487                target: "i".to_string(),
2488                iter: Box::new(HirExpr::Var("pairs".to_string())),
2489                conditions: vec![HirExpr::Var("filter".to_string())],
2490            }],
2491        };
2492        collect_used_vars_expr_inner(&expr, &mut used);
2493        assert!(used.contains_key("k"));
2494        assert!(used.contains_key("v"));
2495        assert!(used.contains_key("pairs"));
2496        assert!(used.contains_key("filter"));
2497    }
2498
2499    #[test]
2500    fn test_collect_used_vars_await() {
2501        let mut used = HashMap::new();
2502        let expr = HirExpr::Await {
2503            value: Box::new(HirExpr::Var("future".to_string())),
2504        };
2505        collect_used_vars_expr_inner(&expr, &mut used);
2506        assert!(used.contains_key("future"));
2507    }
2508
2509    #[test]
2510    fn test_collect_used_vars_slice() {
2511        let mut used = HashMap::new();
2512        let expr = HirExpr::Slice {
2513            base: Box::new(HirExpr::Var("arr".to_string())),
2514            start: Some(Box::new(HirExpr::Var("s".to_string()))),
2515            stop: Some(Box::new(HirExpr::Var("e".to_string()))),
2516            step: Some(Box::new(HirExpr::Var("st".to_string()))),
2517        };
2518        collect_used_vars_expr_inner(&expr, &mut used);
2519        assert!(used.contains_key("arr"));
2520        assert!(used.contains_key("s"));
2521        assert!(used.contains_key("e"));
2522        assert!(used.contains_key("st"));
2523    }
2524
2525    #[test]
2526    fn test_collect_used_vars_slice_partial() {
2527        let mut used = HashMap::new();
2528        let expr = HirExpr::Slice {
2529            base: Box::new(HirExpr::Var("arr".to_string())),
2530            start: None,
2531            stop: Some(Box::new(HirExpr::Var("end".to_string()))),
2532            step: None,
2533        };
2534        collect_used_vars_expr_inner(&expr, &mut used);
2535        assert!(used.contains_key("arr"));
2536        assert!(used.contains_key("end"));
2537    }
2538
2539    #[test]
2540    fn test_collect_used_vars_attribute() {
2541        let mut used = HashMap::new();
2542        let expr = HirExpr::Attribute {
2543            value: Box::new(HirExpr::Var("obj".to_string())),
2544            attr: "attr".to_string(),
2545        };
2546        collect_used_vars_expr_inner(&expr, &mut used);
2547        assert!(used.contains_key("obj"));
2548    }
2549
2550    #[test]
2551    fn test_collect_used_vars_index() {
2552        let mut used = HashMap::new();
2553        let expr = HirExpr::Index {
2554            base: Box::new(HirExpr::Var("arr".to_string())),
2555            index: Box::new(HirExpr::Var("idx".to_string())),
2556        };
2557        collect_used_vars_expr_inner(&expr, &mut used);
2558        assert!(used.contains_key("arr"));
2559        assert!(used.contains_key("idx"));
2560    }
2561
2562    #[test]
2563    fn test_collect_used_vars_fstring() {
2564        use depyler_hir::hir::FStringPart;
2565        let mut used = HashMap::new();
2566        let expr = HirExpr::FString {
2567            parts: vec![
2568                FStringPart::Literal("Hello ".to_string()),
2569                FStringPart::Expr(Box::new(HirExpr::Var("name".to_string()))),
2570            ],
2571        };
2572        collect_used_vars_expr_inner(&expr, &mut used);
2573        assert!(used.contains_key("name"));
2574    }
2575
2576    #[test]
2577    fn test_collect_used_vars_if_expr() {
2578        let mut used = HashMap::new();
2579        let expr = HirExpr::IfExpr {
2580            test: Box::new(HirExpr::Var("cond".to_string())),
2581            body: Box::new(HirExpr::Var("then".to_string())),
2582            orelse: Box::new(HirExpr::Var("else_".to_string())),
2583        };
2584        collect_used_vars_expr_inner(&expr, &mut used);
2585        assert!(used.contains_key("cond"));
2586        assert!(used.contains_key("then"));
2587        assert!(used.contains_key("else_"));
2588    }
2589
2590    #[test]
2591    fn test_collect_used_vars_sort_by_key() {
2592        let mut used = HashMap::new();
2593        let expr = HirExpr::SortByKey {
2594            iterable: Box::new(HirExpr::Var("data".to_string())),
2595            key_params: vec!["x".to_string()],
2596            key_body: Box::new(HirExpr::Var("key".to_string())),
2597            reverse_expr: Some(Box::new(HirExpr::Var("rev".to_string()))),
2598        };
2599        collect_used_vars_expr_inner(&expr, &mut used);
2600        assert!(used.contains_key("data"));
2601        assert!(used.contains_key("key"));
2602        assert!(used.contains_key("rev"));
2603    }
2604
2605    #[test]
2606    fn test_collect_used_vars_sort_by_key_no_reverse() {
2607        let mut used = HashMap::new();
2608        let expr = HirExpr::SortByKey {
2609            iterable: Box::new(HirExpr::Var("items".to_string())),
2610            key_params: vec!["x".to_string()],
2611            key_body: Box::new(HirExpr::Var("k".to_string())),
2612            reverse_expr: None,
2613        };
2614        collect_used_vars_expr_inner(&expr, &mut used);
2615        assert!(used.contains_key("items"));
2616        assert!(used.contains_key("k"));
2617    }
2618
2619    // ========================================================
2620    // hash_expr tests for coverage
2621    // ========================================================
2622
2623    #[test]
2624    fn test_hash_expr_literal_float() {
2625        let expr1 = HirExpr::Literal(Literal::Float(3.15));
2626        let expr2 = HirExpr::Literal(Literal::Float(3.15));
2627        let optimizer = Optimizer::new(OptimizerConfig::default());
2628        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2629    }
2630
2631    #[test]
2632    fn test_hash_expr_literal_string() {
2633        let expr1 = HirExpr::Literal(Literal::String("hello".to_string()));
2634        let expr2 = HirExpr::Literal(Literal::String("hello".to_string()));
2635        let optimizer = Optimizer::new(OptimizerConfig::default());
2636        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2637    }
2638
2639    #[test]
2640    fn test_hash_expr_literal_bytes() {
2641        let expr1 = HirExpr::Literal(Literal::Bytes(vec![1, 2, 3]));
2642        let expr2 = HirExpr::Literal(Literal::Bytes(vec![1, 2, 3]));
2643        let optimizer = Optimizer::new(OptimizerConfig::default());
2644        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2645    }
2646
2647    #[test]
2648    fn test_hash_expr_literal_bool() {
2649        let expr1 = HirExpr::Literal(Literal::Bool(true));
2650        let expr2 = HirExpr::Literal(Literal::Bool(true));
2651        let optimizer = Optimizer::new(OptimizerConfig::default());
2652        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2653    }
2654
2655    #[test]
2656    fn test_hash_expr_literal_none() {
2657        let expr1 = HirExpr::Literal(Literal::None);
2658        let expr2 = HirExpr::Literal(Literal::None);
2659        let optimizer = Optimizer::new(OptimizerConfig::default());
2660        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2661    }
2662
2663    #[test]
2664    fn test_hash_expr_call() {
2665        let expr1 = HirExpr::Call {
2666            func: "len".to_string(),
2667            args: vec![HirExpr::Var("x".to_string())],
2668            kwargs: vec![],
2669        };
2670        let expr2 = HirExpr::Call {
2671            func: "len".to_string(),
2672            args: vec![HirExpr::Var("x".to_string())],
2673            kwargs: vec![],
2674        };
2675        let optimizer = Optimizer::new(OptimizerConfig::default());
2676        assert_eq!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2677    }
2678
2679    #[test]
2680    fn test_hash_expr_call_different_func() {
2681        let expr1 = HirExpr::Call {
2682            func: "len".to_string(),
2683            args: vec![],
2684            kwargs: vec![],
2685        };
2686        let expr2 = HirExpr::Call {
2687            func: "abs".to_string(),
2688            args: vec![],
2689            kwargs: vec![],
2690        };
2691        let optimizer = Optimizer::new(OptimizerConfig::default());
2692        assert_ne!(optimizer.hash_expr(&expr1), optimizer.hash_expr(&expr2));
2693    }
2694
2695    #[test]
2696    fn test_is_pure_function_extended() {
2697        let optimizer = Optimizer::new(OptimizerConfig::default());
2698        assert!(optimizer.is_pure_function("abs"));
2699        assert!(optimizer.is_pure_function("len"));
2700        assert!(optimizer.is_pure_function("min"));
2701        assert!(optimizer.is_pure_function("max"));
2702        assert!(optimizer.is_pure_function("sum"));
2703        assert!(optimizer.is_pure_function("str"));
2704        assert!(optimizer.is_pure_function("int"));
2705        assert!(optimizer.is_pure_function("float"));
2706        assert!(optimizer.is_pure_function("bool"));
2707        assert!(optimizer.is_pure_function("round"));
2708        assert!(optimizer.is_pure_function("pow"));
2709        assert!(optimizer.is_pure_function("sqrt"));
2710        assert!(!optimizer.is_pure_function("print"));
2711        assert!(!optimizer.is_pure_function("input"));
2712    }
2713
2714    #[test]
2715    fn test_optimizer_config_debug() {
2716        let config = OptimizerConfig::default();
2717        let debug = format!("{:?}", config);
2718        assert!(debug.contains("OptimizerConfig"));
2719    }
2720
2721    // === Additional unique optimizer tests ===
2722
2723    #[test]
2724    fn test_evaluate_constant_binop_division_by_zero() {
2725        let optimizer = Optimizer::new(OptimizerConfig::default());
2726        let expr = HirExpr::Binary {
2727            left: Box::new(HirExpr::Literal(Literal::Int(10))),
2728            right: Box::new(HirExpr::Literal(Literal::Int(0))),
2729            op: BinOp::Div,
2730        };
2731        let result = optimizer.evaluate_constant_binop(&expr);
2732        // Division by zero should not be optimized
2733        assert!(result.is_none());
2734    }
2735
2736    #[test]
2737    fn test_evaluate_constant_binop_float_add() {
2738        let optimizer = Optimizer::new(OptimizerConfig::default());
2739        let expr = HirExpr::Binary {
2740            left: Box::new(HirExpr::Literal(Literal::Float(3.5))),
2741            right: Box::new(HirExpr::Literal(Literal::Float(2.0))),
2742            op: BinOp::Add,
2743        };
2744        let result = optimizer.evaluate_constant_binop(&expr);
2745        assert!(result.is_some());
2746        if let Some(HirExpr::Literal(Literal::Float(f))) = result {
2747            assert!((f - 5.5).abs() < 0.001);
2748        }
2749    }
2750
2751    #[test]
2752    fn test_evaluate_constant_binop_string_concat_unsupported() {
2753        // String concat is not supported in constant folding
2754        let optimizer = Optimizer::new(OptimizerConfig::default());
2755        let expr = HirExpr::Binary {
2756            left: Box::new(HirExpr::Literal(Literal::String("hello".to_string()))),
2757            right: Box::new(HirExpr::Literal(Literal::String(" world".to_string()))),
2758            op: BinOp::Add,
2759        };
2760        let result = optimizer.evaluate_constant_binop(&expr);
2761        assert!(result.is_none()); // String ops not supported
2762    }
2763
2764    #[test]
2765    fn test_walrus_operator_hoisting() {
2766        let mut optimizer = Optimizer::new(OptimizerConfig::default());
2767
2768        // Create a function with walrus operator in if condition
2769        let program = HirProgram {
2770            functions: vec![HirFunction {
2771                name: "test".to_string(),
2772                params: smallvec![],
2773                ret_type: Type::Int,
2774                body: vec![HirStmt::If {
2775                    condition: HirExpr::NamedExpr {
2776                        target: "n".to_string(),
2777                        value: Box::new(HirExpr::Literal(Literal::Int(5))),
2778                    },
2779                    then_body: vec![HirStmt::Return(Some(HirExpr::Var("n".to_string())))],
2780                    else_body: None,
2781                }],
2782                properties: FunctionProperties::default(),
2783                annotations: TranspilationAnnotations::default(),
2784                docstring: None,
2785            }],
2786            classes: vec![],
2787            imports: vec![],
2788        };
2789
2790        let optimized = optimizer.optimize_program(program);
2791
2792        // After hoisting, there should be an assignment before the if
2793        let func = &optimized.functions[0];
2794        assert!(!func.body.is_empty(), "Should have at least one statement");
2795    }
2796
2797    #[test]
2798    fn test_eliminate_dead_code_preserves_side_effects() {
2799        let config = OptimizerConfig {
2800            eliminate_dead_code: true,
2801            ..Default::default()
2802        };
2803        let mut optimizer = Optimizer::new(config);
2804
2805        let program = HirProgram {
2806            functions: vec![HirFunction {
2807                name: "test".to_string(),
2808                params: smallvec![],
2809                ret_type: Type::None,
2810                body: vec![
2811                    HirStmt::Expr(HirExpr::Call {
2812                        func: "print".to_string(),
2813                        args: vec![HirExpr::Literal(Literal::String("hello".to_string()))],
2814                        kwargs: vec![],
2815                    }),
2816                    HirStmt::Return(None),
2817                ],
2818                properties: FunctionProperties::default(),
2819                annotations: TranspilationAnnotations::default(),
2820                docstring: None,
2821            }],
2822            classes: vec![],
2823            imports: vec![],
2824        };
2825
2826        let optimized = optimizer.optimize_program(program);
2827
2828        // print() has side effects, should be preserved
2829        let func = &optimized.functions[0];
2830        assert!(
2831            !func.body.is_empty(),
2832            "Side effect statement should be preserved"
2833        );
2834    }
2835
2836    // === Additional tests for expr_has_side_effects ===
2837
2838    #[test]
2839    fn test_expr_has_side_effects_index() {
2840        // Indexing can fail, has side effects
2841        let expr = HirExpr::Index {
2842            base: Box::new(HirExpr::Var("arr".to_string())),
2843            index: Box::new(HirExpr::Literal(Literal::Int(0))),
2844        };
2845        assert!(Optimizer::expr_has_side_effects(&expr));
2846    }
2847
2848    #[test]
2849    fn test_expr_has_side_effects_nested_call() {
2850        // Call nested inside binary expression still has side effects
2851        let expr = HirExpr::Binary {
2852            left: Box::new(HirExpr::Call {
2853                func: "get_value".to_string(),
2854                args: vec![],
2855                kwargs: vec![],
2856            }),
2857            op: BinOp::Add,
2858            right: Box::new(HirExpr::Literal(Literal::Int(1))),
2859        };
2860        assert!(Optimizer::expr_has_side_effects(&expr));
2861    }
2862
2863    #[test]
2864    fn test_expr_has_side_effects_list_literal() {
2865        // List literals are pure
2866        let expr = HirExpr::List(vec![
2867            HirExpr::Literal(Literal::Int(1)),
2868            HirExpr::Literal(Literal::Int(2)),
2869        ]);
2870        assert!(!Optimizer::expr_has_side_effects(&expr));
2871    }
2872
2873    #[test]
2874    fn test_expr_has_side_effects_tuple() {
2875        // Tuples are pure
2876        let expr = HirExpr::Tuple(vec![
2877            HirExpr::Literal(Literal::Int(1)),
2878            HirExpr::Literal(Literal::Int(2)),
2879        ]);
2880        assert!(!Optimizer::expr_has_side_effects(&expr));
2881    }
2882
2883    // === Additional tests for is_pure_function ===
2884
2885    #[test]
2886    fn test_is_pure_function_math_functions() {
2887        let optimizer = Optimizer::new(OptimizerConfig::default());
2888        assert!(optimizer.is_pure_function("abs"));
2889        assert!(optimizer.is_pure_function("min"));
2890        assert!(optimizer.is_pure_function("max"));
2891        assert!(optimizer.is_pure_function("sum"));
2892        assert!(optimizer.is_pure_function("pow"));
2893        assert!(optimizer.is_pure_function("round"));
2894    }
2895
2896    #[test]
2897    fn test_is_pure_function_type_conversions() {
2898        let optimizer = Optimizer::new(OptimizerConfig::default());
2899        assert!(optimizer.is_pure_function("int"));
2900        assert!(optimizer.is_pure_function("float"));
2901        assert!(optimizer.is_pure_function("str"));
2902        assert!(optimizer.is_pure_function("bool"));
2903        // list() is NOT in the pure list (may allocate)
2904        assert!(!optimizer.is_pure_function("list"));
2905    }
2906
2907    #[test]
2908    fn test_is_pure_function_impure_io() {
2909        let optimizer = Optimizer::new(OptimizerConfig::default());
2910        assert!(!optimizer.is_pure_function("print"));
2911        assert!(!optimizer.is_pure_function("input"));
2912        assert!(!optimizer.is_pure_function("open"));
2913    }
2914
2915    // === Additional tests for is_complex_expr ===
2916
2917    #[test]
2918    fn test_is_complex_expr_method_call() {
2919        // MethodCall is NOT considered complex by is_complex_expr
2920        // Only Binary and Call are handled
2921        let optimizer = Optimizer::new(OptimizerConfig::default());
2922        let expr = HirExpr::MethodCall {
2923            object: Box::new(HirExpr::Var("s".to_string())),
2924            method: "upper".to_string(),
2925            args: vec![],
2926            kwargs: vec![],
2927        };
2928        assert!(!optimizer.is_complex_expr(&expr));
2929    }
2930
2931    #[test]
2932    fn test_is_complex_expr_nested_binary() {
2933        let optimizer = Optimizer::new(OptimizerConfig::default());
2934        let expr = HirExpr::Binary {
2935            left: Box::new(HirExpr::Binary {
2936                left: Box::new(HirExpr::Var("a".to_string())),
2937                op: BinOp::Add,
2938                right: Box::new(HirExpr::Var("b".to_string())),
2939            }),
2940            op: BinOp::Mul,
2941            right: Box::new(HirExpr::Var("c".to_string())),
2942        };
2943        assert!(optimizer.is_complex_expr(&expr));
2944    }
2945
2946    // === Tests for hash_expr edge cases ===
2947
2948    #[test]
2949    fn test_hash_expr_list() {
2950        let optimizer = Optimizer::new(OptimizerConfig::default());
2951        let list1 = HirExpr::List(vec![
2952            HirExpr::Literal(Literal::Int(1)),
2953            HirExpr::Literal(Literal::Int(2)),
2954        ]);
2955        let list2 = HirExpr::List(vec![
2956            HirExpr::Literal(Literal::Int(1)),
2957            HirExpr::Literal(Literal::Int(2)),
2958        ]);
2959        assert_eq!(optimizer.hash_expr(&list1), optimizer.hash_expr(&list2));
2960    }
2961
2962    #[test]
2963    fn test_hash_expr_different_lists() {
2964        // Note: List hashing uses only discriminant, so different lists hash the same
2965        // This is intentional - Lists are not deeply hashed
2966        let optimizer = Optimizer::new(OptimizerConfig::default());
2967        let list1 = HirExpr::List(vec![HirExpr::Literal(Literal::Int(1))]);
2968        let list2 = HirExpr::List(vec![HirExpr::Literal(Literal::Int(2))]);
2969        // They hash equal because List uses discriminant-only hashing
2970        assert_eq!(optimizer.hash_expr(&list1), optimizer.hash_expr(&list2));
2971    }
2972
2973    // === Tests for optimizer config variants ===
2974
2975    #[test]
2976    fn test_optimizer_config_all_disabled() {
2977        let config = OptimizerConfig {
2978            propagate_constants: false,
2979            eliminate_dead_code: false,
2980            inline_functions: false,
2981            eliminate_common_subexpressions: false,
2982            inline_threshold: 20,
2983        };
2984        let mut optimizer = Optimizer::new(config);
2985
2986        let program = HirProgram {
2987            functions: vec![HirFunction {
2988                name: "test".to_string(),
2989                params: smallvec![],
2990                ret_type: Type::Int,
2991                body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
2992                properties: FunctionProperties::default(),
2993                annotations: TranspilationAnnotations::default(),
2994                docstring: None,
2995            }],
2996            classes: vec![],
2997            imports: vec![],
2998        };
2999
3000        // With all optimizations disabled, program should be unchanged
3001        let result = optimizer.optimize_program(program.clone());
3002        assert_eq!(result.functions.len(), 1);
3003    }
3004
3005    #[test]
3006    fn test_optimizer_config_clone_identical_behavior() {
3007        let config = OptimizerConfig::default();
3008        let cloned = config.clone();
3009        // Both should behave identically
3010        let mut opt1 = Optimizer::new(config);
3011        let mut opt2 = Optimizer::new(cloned);
3012        let program = HirProgram {
3013            functions: vec![],
3014            classes: vec![],
3015            imports: vec![],
3016        };
3017        let result1 = opt1.optimize_program(program.clone());
3018        let result2 = opt2.optimize_program(program);
3019        assert_eq!(result1.functions.len(), result2.functions.len());
3020    }
3021
3022    // === Tests for is_simple_return_expr edge cases ===
3023
3024    #[test]
3025    fn test_is_simple_return_expr_attribute() {
3026        let optimizer = Optimizer::new(OptimizerConfig::default());
3027        let expr = HirExpr::Attribute {
3028            value: Box::new(HirExpr::Var("obj".to_string())),
3029            attr: "field".to_string(),
3030        };
3031        // Attribute access is relatively simple
3032        assert!(optimizer.is_simple_return_expr(&expr));
3033    }
3034
3035    #[test]
3036    fn test_is_simple_return_expr_if_expr() {
3037        let optimizer = Optimizer::new(OptimizerConfig::default());
3038        let expr = HirExpr::IfExpr {
3039            test: Box::new(HirExpr::Var("cond".to_string())),
3040            body: Box::new(HirExpr::Literal(Literal::Int(1))),
3041            orelse: Box::new(HirExpr::Literal(Literal::Int(0))),
3042        };
3043        // IfExpr (ternary) is not simple
3044        assert!(!optimizer.is_simple_return_expr(&expr));
3045    }
3046
3047    // === Tests for collect_used_vars in different expressions ===
3048
3049    #[test]
3050    fn test_collect_used_vars_expr_attribute() {
3051        let optimizer = Optimizer::new(OptimizerConfig::default());
3052        let expr = HirExpr::Attribute {
3053            value: Box::new(HirExpr::Var("obj".to_string())),
3054            attr: "field".to_string(),
3055        };
3056        let mut used = HashMap::new();
3057        optimizer.collect_used_vars_expr(&expr, &mut used);
3058        assert!(used.contains_key("obj"));
3059    }
3060
3061    #[test]
3062    fn test_collect_used_vars_expr_index() {
3063        let optimizer = Optimizer::new(OptimizerConfig::default());
3064        let expr = HirExpr::Index {
3065            base: Box::new(HirExpr::Var("arr".to_string())),
3066            index: Box::new(HirExpr::Var("idx".to_string())),
3067        };
3068        let mut used = HashMap::new();
3069        optimizer.collect_used_vars_expr(&expr, &mut used);
3070        assert!(used.contains_key("arr"));
3071        assert!(used.contains_key("idx"));
3072    }
3073
3074    #[test]
3075    fn test_collect_used_vars_expr_if_expr() {
3076        let optimizer = Optimizer::new(OptimizerConfig::default());
3077        let expr = HirExpr::IfExpr {
3078            test: Box::new(HirExpr::Var("cond".to_string())),
3079            body: Box::new(HirExpr::Var("a".to_string())),
3080            orelse: Box::new(HirExpr::Var("b".to_string())),
3081        };
3082        let mut used = HashMap::new();
3083        optimizer.collect_used_vars_expr(&expr, &mut used);
3084        assert!(used.contains_key("cond"));
3085        assert!(used.contains_key("a"));
3086        assert!(used.contains_key("b"));
3087    }
3088
3089    // === Tests for evaluate_constant_binop with different operations ===
3090
3091    #[test]
3092    fn test_evaluate_constant_binop_floor_div() {
3093        let optimizer = Optimizer::new(OptimizerConfig::default());
3094        let expr = HirExpr::Binary {
3095            left: Box::new(HirExpr::Literal(Literal::Int(7))),
3096            op: BinOp::FloorDiv,
3097            right: Box::new(HirExpr::Literal(Literal::Int(2))),
3098        };
3099        // Floor division may or may not be supported
3100        let _ = optimizer.evaluate_constant_binop(&expr);
3101    }
3102
3103    #[test]
3104    fn test_evaluate_constant_binop_power() {
3105        let optimizer = Optimizer::new(OptimizerConfig::default());
3106        let expr = HirExpr::Binary {
3107            left: Box::new(HirExpr::Literal(Literal::Int(2))),
3108            op: BinOp::Pow,
3109            right: Box::new(HirExpr::Literal(Literal::Int(3))),
3110        };
3111        // Power may or may not be supported for constant folding
3112        let _ = optimizer.evaluate_constant_binop(&expr);
3113    }
3114}