1use depyler_hir::hir::{
4 AssignTarget, BinOp, HirExpr, HirFunction, HirProgram, HirStmt, Literal, UnaryOp,
5};
6use std::collections::{HashMap, HashSet};
7use std::hash::{Hash, Hasher};
8
9pub struct Optimizer {
11 config: OptimizerConfig,
13}
14
15#[derive(Debug, Clone)]
16pub struct OptimizerConfig {
17 pub inline_functions: bool,
19 pub eliminate_dead_code: bool,
21 pub propagate_constants: bool,
23 pub eliminate_common_subexpressions: bool,
25 pub inline_threshold: usize,
27}
28
29impl Default for OptimizerConfig {
30 fn default() -> Self {
31 Self {
32 inline_functions: false,
37 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 pub fn optimize_program(&mut self, mut program: HirProgram) -> HirProgram {
54 program = self.hoist_walrus_operators(program);
57
58 if self.config.propagate_constants {
60 program = self.propagate_constants_program(program);
61 }
62
63 if self.config.eliminate_dead_code {
65 program = self.eliminate_dead_code_program(program);
66 }
67
68 if self.config.inline_functions {
70 program = self.inline_functions_program(program);
71 }
72
73 if self.config.eliminate_common_subexpressions {
75 program = self.eliminate_common_subexpressions_program(program);
76 }
77
78 program
79 }
80
81 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 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 let (walrus_assigns, simplified_condition) =
107 self.extract_walrus_from_expr(condition);
108
109 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 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 let (walrus_assigns, simplified_condition) =
136 self.extract_walrus_from_expr(condition);
137
138 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 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 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 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 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 let simplified_value = self.extract_walrus_recursive(value, assigns);
233 assigns.push((target.clone(), simplified_value));
234 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 _ => expr.clone(),
281 }
282 }
283
284 fn propagate_constants_program(&self, mut program: HirProgram) -> HirProgram {
286 let mut constants = HashMap::new();
287
288 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 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 for func in &program.functions {
303 self.collect_constants_function(func, &mut constants, &mutated_vars, &read_vars);
304 }
305
306 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 for (var, count) in assignments {
324 if count > 1 {
325 mutated_vars.insert(var);
326 }
327 }
328 }
329
330 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 fn collect_read_vars_stmt(stmt: &HirStmt, read_vars: &mut HashSet<String>) {
339 match stmt {
340 HirStmt::Assign { value, .. } => {
341 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 fn collect_read_vars_expr(expr: &HirExpr, read_vars: &mut HashSet<String>) {
383 match expr {
384 HirExpr::Var(name) => {
385 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 if !mutated_vars.contains(name)
490 && !used_vars.contains(name) && 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 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 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 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 const MAX_ITERATIONS: usize = 10;
711
712 for _ in 0..MAX_ITERATIONS {
713 let initial_len = func.body.len();
714
715 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 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 func.body.retain(|stmt| {
742 if let HirStmt::Assign {
743 target: AssignTarget::Symbol(name),
744 ..
745 } = stmt
746 {
747 used_vars.contains_key(name) || side_effect_vars.contains(name)
750 } else {
751 true
752 }
753 });
754
755 if func.body.len() == initial_len {
757 break;
758 }
759 }
760 }
761
762 fn collect_truly_used_vars_stmt(&self, stmt: &HirStmt, used: &mut HashMap<String, bool>) {
766 match stmt {
767 HirStmt::Assign { target, value, .. } => {
768 self.collect_used_vars_assign_target(target, used);
771 self.collect_used_vars_expr(value, used);
772 }
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 HirStmt::Try {
810 body,
811 handlers,
812 orelse,
813 finalbody,
814 } => {
815 for s in body {
817 self.collect_truly_used_vars_stmt(s, used);
818 }
819 for handler in handlers {
821 for s in &handler.body {
822 self.collect_truly_used_vars_stmt(s, used);
823 }
824 }
825 if let Some(orelse_stmts) = orelse {
827 for s in orelse_stmts {
828 self.collect_truly_used_vars_stmt(s, used);
829 }
830 }
831 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 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 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 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 HirStmt::FunctionDef { body, .. } => {
865 for s in body {
866 self.collect_truly_used_vars_stmt(s, used);
867 }
868 }
869 HirStmt::Block(stmts) => {
871 for s in stmts {
872 self.collect_truly_used_vars_stmt(s, used);
873 }
874 }
875 _ => {}
878 }
879 }
880
881 fn expr_has_side_effects(expr: &HirExpr) -> bool {
890 match expr {
891 HirExpr::Index { .. } => true,
893 HirExpr::Call { .. } => true,
895 HirExpr::MethodCall { .. } => true,
897 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 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 }
945 AssignTarget::Index { base, index } => {
946 self.collect_used_vars_expr(base, used);
949 self.collect_used_vars_expr(index, used);
950 }
951 AssignTarget::Attribute { value, .. } => {
952 self.collect_used_vars_expr(value, used);
955 }
956 AssignTarget::Tuple(targets) => {
957 for t in targets {
959 self.collect_used_vars_assign_target(t, used);
960 }
961 }
962 }
963 }
964
965 fn inline_functions_program(&self, program: HirProgram) -> HirProgram {
967 use crate::inlining::{InliningAnalyzer, InliningConfig};
968
969 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 let mut analyzer = InliningAnalyzer::new(config);
981 let decisions = analyzer.analyze_program(&program);
982
983 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 analyzer.apply_inlining(program, &decisions)
995 }
996
997 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 if is_final_stmt && self.is_simple_return_expr(expr) {
1039 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 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 match expr {
1090 HirExpr::Binary { left, right, op } => {
1091 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 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 (HirExpr::Var(var_name.clone()), extra_stmts)
1111 } else {
1112 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 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 !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 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 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 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 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 used.insert(func.clone(), true);
1300 for arg in args {
1301 collect_used_vars_expr_inner(arg, used);
1302 }
1303 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 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 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 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 HirExpr::DictComp {
1359 key,
1360 value,
1361 generators,
1362 } => {
1363 collect_used_vars_expr_inner(key, used);
1365 collect_used_vars_expr_inner(value, used);
1366 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 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 collect_used_vars_expr_inner(value, used);
1402 }
1403 HirExpr::Index { base, index } => {
1404 collect_used_vars_expr_inner(base, used);
1408 collect_used_vars_expr_inner(index, used);
1409 }
1410 HirExpr::FString { parts } => {
1411 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 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 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 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 HirStmt::Assign {
1472 target: AssignTarget::Symbol("unused".to_string()),
1473 value: HirExpr::Literal(Literal::Int(42)),
1474 type_annotation: None,
1475 },
1476 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 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 assert!(matches!(&func.body[0], HirStmt::Return(_)));
1505 }
1506
1507 #[test]
1508 fn test_dead_code_elimination() {
1509 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 let func = &optimized.functions[0];
1543
1544 assert_eq!(
1547 func.body.len(),
1548 2,
1549 "Unused 'unused' variable should be eliminated (DCE enabled by default)"
1550 );
1551
1552 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 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 assert!(
1599 func.body.len() <= 3,
1600 "Dead code elimination should remove or preserve statements"
1601 );
1602
1603 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 let config = OptimizerConfig::default();
1617
1618 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 HirStmt::Assign {
1634 target: AssignTarget::Symbol("unused".to_string()),
1635 value: HirExpr::Literal(Literal::Int(42)),
1636 type_annotation: None,
1637 },
1638 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 assert_eq!(
1660 func.body.len(),
1661 2,
1662 "DEPYLER-0508: Unused 'unused' variable should be eliminated by default"
1663 );
1664
1665 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 #[test]
1688 fn test_optimizer_config_default() {
1689 let config = OptimizerConfig::default();
1690 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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")); }
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 assert!(!result.functions.is_empty());
2339 }
2340
2341 #[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 #[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 #[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 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 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()); }
2763
2764 #[test]
2765 fn test_walrus_operator_hoisting() {
2766 let mut optimizer = Optimizer::new(OptimizerConfig::default());
2767
2768 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 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 let func = &optimized.functions[0];
2830 assert!(
2831 !func.body.is_empty(),
2832 "Side effect statement should be preserved"
2833 );
2834 }
2835
2836 #[test]
2839 fn test_expr_has_side_effects_index() {
2840 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 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 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 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 #[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 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 #[test]
2918 fn test_is_complex_expr_method_call() {
2919 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 #[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 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 assert_eq!(optimizer.hash_expr(&list1), optimizer.hash_expr(&list2));
2971 }
2972
2973 #[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 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 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 #[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 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 assert!(!optimizer.is_simple_return_expr(&expr));
3045 }
3046
3047 #[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 #[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 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 let _ = optimizer.evaluate_constant_binop(&expr);
3113 }
3114}