1use depyler_hir::hir::{HirExpr, HirFunction, HirProgram, HirStmt};
3use std::collections::{HashMap, HashSet};
4
5pub struct InliningAnalyzer {
7 config: InliningConfig,
9 call_graph: CallGraph,
11 function_metrics: HashMap<String, FunctionMetrics>,
13}
14
15#[derive(Debug, Clone)]
16pub struct InliningConfig {
17 pub max_inline_size: usize,
19 pub max_inline_depth: usize,
21 pub inline_single_use: bool,
23 pub inline_trivial: bool,
25 pub cost_threshold: f64,
27 pub inline_loops: bool,
29}
30
31impl Default for InliningConfig {
32 fn default() -> Self {
33 Self {
34 max_inline_size: 20,
35 max_inline_depth: 3,
36 inline_single_use: true,
37 inline_trivial: true,
38 cost_threshold: 1.5,
39 inline_loops: false,
40 }
41 }
42}
43
44#[derive(Debug, Default)]
45struct CallGraph {
46 calls: HashMap<String, HashSet<String>>,
48 called_by: HashMap<String, HashSet<String>>,
50 recursive: HashSet<String>,
52}
53
54#[derive(Debug, Clone)]
55struct FunctionMetrics {
56 size: usize,
58 _param_count: usize,
60 _return_count: usize,
62 has_loops: bool,
64 has_side_effects: bool,
66 is_trivial: bool,
68 call_count: usize,
70 cost: f64,
72}
73
74#[derive(Debug, Clone)]
76pub struct InliningDecision {
77 pub should_inline: bool,
78 pub reason: InliningReason,
79 pub cost_benefit: f64,
80}
81
82#[derive(Debug, Clone)]
83pub enum InliningReason {
84 Trivial,
86 SingleUse,
88 SmallHotFunction,
90 EnablesOptimization,
92 TooLarge,
94 Recursive,
96 HasSideEffects,
98 ContainsLoops,
100 CostTooHigh,
102}
103
104impl InliningAnalyzer {
105 pub fn new(config: InliningConfig) -> Self {
106 Self {
107 config,
108 call_graph: CallGraph::default(),
109 function_metrics: HashMap::new(),
110 }
111 }
112
113 pub fn analyze_program(&mut self, program: &HirProgram) -> HashMap<String, InliningDecision> {
115 self.build_call_graph(program);
117
118 self.detect_recursion();
120
121 self.calculate_metrics(program);
123
124 self.make_decisions()
126 }
127
128 pub fn apply_inlining(
130 &self,
131 mut program: HirProgram,
132 decisions: &HashMap<String, InliningDecision>,
133 ) -> HirProgram {
134 let function_map: HashMap<String, HirFunction> = program
136 .functions
137 .iter()
138 .map(|f| (f.name.clone(), f.clone()))
139 .collect();
140
141 let mut inlined_functions = HashSet::new();
143
144 for func_idx in 0..program.functions.len() {
146 let func = &mut program.functions[func_idx];
147 let mut modified_body = Vec::new();
148
149 for stmt in &func.body {
150 match self.try_inline_stmt(stmt, &function_map, decisions, 0) {
151 Some(inlined_stmts) => {
152 modified_body.extend(inlined_stmts);
153 if let HirStmt::Expr(HirExpr::Call { func: callee, .. }) = stmt {
155 if decisions
156 .get(callee)
157 .map(|d| d.should_inline)
158 .unwrap_or(false)
159 {
160 inlined_functions.insert(callee.clone());
161 }
162 }
163 }
164 None => modified_body.push(stmt.clone()),
165 }
166 }
167
168 func.body = modified_body;
169 }
170
171 if self.config.inline_single_use {
173 program.functions.retain(|f| {
174 !inlined_functions.contains(&f.name)
175 || self
176 .function_metrics
177 .get(&f.name)
178 .map(|m| m.call_count > 1)
179 .unwrap_or(true)
180 });
181 }
182
183 program
184 }
185
186 fn build_call_graph(&mut self, program: &HirProgram) {
187 for func in &program.functions {
188 let calls = self.extract_calls_from_function(func);
189
190 self.call_graph
191 .calls
192 .insert(func.name.clone(), calls.clone());
193
194 for callee in calls {
195 self.call_graph
196 .called_by
197 .entry(callee)
198 .or_default()
199 .insert(func.name.clone());
200 }
201 }
202 }
203
204 fn extract_calls_from_function(&self, func: &HirFunction) -> HashSet<String> {
205 let mut calls = HashSet::new();
206
207 for stmt in &func.body {
208 self.extract_calls_from_stmt(stmt, &mut calls);
209 }
210
211 calls
212 }
213
214 fn extract_calls_from_stmt(&self, stmt: &HirStmt, calls: &mut HashSet<String>) {
215 match stmt {
216 HirStmt::Expr(expr) => self.extract_calls_from_expr(expr, calls),
217 HirStmt::Assign { value, .. } => self.extract_calls_from_expr(value, calls),
218 HirStmt::Return(Some(expr)) => self.extract_calls_from_expr(expr, calls),
219 HirStmt::If {
220 condition,
221 then_body,
222 else_body,
223 } => self.extract_calls_from_if(condition, then_body, else_body, calls),
224 HirStmt::While { condition, body }
225 | HirStmt::For {
226 iter: condition,
227 body,
228 ..
229 } => self.extract_calls_from_loop(condition, body, calls),
230 _ => {}
231 }
232 }
233
234 fn extract_calls_from_if(
235 &self,
236 condition: &HirExpr,
237 then_body: &[HirStmt],
238 else_body: &Option<Vec<HirStmt>>,
239 calls: &mut HashSet<String>,
240 ) {
241 self.extract_calls_from_expr(condition, calls);
242 self.extract_calls_from_body(then_body, calls);
243 if let Some(else_stmts) = else_body {
244 self.extract_calls_from_body(else_stmts, calls);
245 }
246 }
247
248 fn extract_calls_from_loop(
249 &self,
250 condition: &HirExpr,
251 body: &[HirStmt],
252 calls: &mut HashSet<String>,
253 ) {
254 self.extract_calls_from_expr(condition, calls);
255 self.extract_calls_from_body(body, calls);
256 }
257
258 fn extract_calls_from_body(&self, body: &[HirStmt], calls: &mut HashSet<String>) {
259 for s in body {
260 self.extract_calls_from_stmt(s, calls);
261 }
262 }
263
264 fn extract_calls_from_expr(&self, expr: &HirExpr, calls: &mut HashSet<String>) {
265 extract_calls_from_expr_inner(expr, calls);
266 }
267}
268
269fn extract_calls_from_expr_inner(expr: &HirExpr, calls: &mut HashSet<String>) {
270 match expr {
271 HirExpr::Call { func, args, .. } => extract_from_call(func, args, calls),
272 HirExpr::Binary { left, right, .. } => extract_from_binary(left, right, calls),
273 HirExpr::Unary { operand, .. } => extract_calls_from_expr_inner(operand, calls),
274 HirExpr::List(items) | HirExpr::Tuple(items) => extract_from_items(items, calls),
275 HirExpr::Dict(pairs) => extract_from_dict(pairs, calls),
276 HirExpr::MethodCall { object, args, .. } => extract_from_method_call(object, args, calls),
277 HirExpr::Lambda { body, .. } => extract_calls_from_expr_inner(body, calls),
278 _ => {}
279 }
280}
281
282fn extract_from_call(func: &str, args: &[HirExpr], calls: &mut HashSet<String>) {
283 calls.insert(func.to_string());
284 for arg in args {
285 extract_calls_from_expr_inner(arg, calls);
286 }
287}
288
289fn extract_from_binary(left: &HirExpr, right: &HirExpr, calls: &mut HashSet<String>) {
290 extract_calls_from_expr_inner(left, calls);
291 extract_calls_from_expr_inner(right, calls);
292}
293
294fn extract_from_items(items: &[HirExpr], calls: &mut HashSet<String>) {
295 for item in items {
296 extract_calls_from_expr_inner(item, calls);
297 }
298}
299
300fn extract_from_dict(pairs: &[(HirExpr, HirExpr)], calls: &mut HashSet<String>) {
301 for (k, v) in pairs {
302 extract_calls_from_expr_inner(k, calls);
303 extract_calls_from_expr_inner(v, calls);
304 }
305}
306
307fn extract_from_method_call(object: &HirExpr, args: &[HirExpr], calls: &mut HashSet<String>) {
308 extract_calls_from_expr_inner(object, calls);
309 for arg in args {
310 extract_calls_from_expr_inner(arg, calls);
311 }
312}
313
314impl InliningAnalyzer {
315 fn detect_recursion(&mut self) {
316 for func_name in self.call_graph.calls.keys() {
318 let mut visited = HashSet::new();
319 let mut stack = HashSet::new();
320
321 if self.is_recursive_dfs(func_name, &mut visited, &mut stack) {
322 self.call_graph.recursive.insert(func_name.clone());
323 }
324 }
325 }
326
327 fn is_recursive_dfs(
328 &self,
329 func: &str,
330 visited: &mut HashSet<String>,
331 stack: &mut HashSet<String>,
332 ) -> bool {
333 visited.insert(func.to_string());
334 stack.insert(func.to_string());
335
336 if let Some(callees) = self.call_graph.calls.get(func) {
337 for callee in callees {
338 if stack.contains(callee) {
339 return true; }
341
342 if !visited.contains(callee) && self.is_recursive_dfs(callee, visited, stack) {
343 return true;
344 }
345 }
346 }
347
348 stack.remove(func);
349 false
350 }
351
352 fn calculate_metrics(&mut self, program: &HirProgram) {
353 for func in &program.functions {
354 let size = self.calculate_function_size(func);
355 let has_loops = self.contains_loops(&func.body);
356 let has_side_effects = self.has_side_effects(func);
357 let is_trivial = self.is_trivial_function(func);
358 let return_count = self.count_returns(&func.body);
359
360 let call_count = self
362 .call_graph
363 .called_by
364 .get(&func.name)
365 .map(|callers| callers.len())
366 .unwrap_or(0);
367
368 let cost = self.estimate_cost(func, size, has_loops, has_side_effects);
370
371 let metrics = FunctionMetrics {
372 size,
373 _param_count: func.params.len(),
374 _return_count: return_count,
375 has_loops,
376 has_side_effects,
377 is_trivial,
378 call_count,
379 cost,
380 };
381
382 self.function_metrics.insert(func.name.clone(), metrics);
383 }
384 }
385
386 fn calculate_function_size(&self, func: &HirFunction) -> usize {
387 let mut size = 0;
388 for stmt in &func.body {
389 size += self.calculate_stmt_size(stmt);
390 }
391 size
392 }
393
394 fn calculate_stmt_size(&self, stmt: &HirStmt) -> usize {
395 match stmt {
396 HirStmt::Expr(expr) => self.calculate_expr_size(expr),
397 HirStmt::Assign { value, .. } => 1 + self.calculate_expr_size(value),
398 HirStmt::Return(Some(expr)) => 1 + self.calculate_expr_size(expr),
399 HirStmt::Return(None) => 1,
400 HirStmt::If {
401 condition,
402 then_body,
403 else_body,
404 } => self.calculate_if_size(condition, then_body, else_body),
405 HirStmt::While { condition, body }
406 | HirStmt::For {
407 iter: condition,
408 body,
409 ..
410 } => self.calculate_loop_size(condition, body),
411 _ => 1,
412 }
413 }
414
415 fn calculate_if_size(
416 &self,
417 condition: &HirExpr,
418 then_body: &[HirStmt],
419 else_body: &Option<Vec<HirStmt>>,
420 ) -> usize {
421 let mut size = 1 + self.calculate_expr_size(condition);
422 size += self.calculate_body_size(then_body);
423 if let Some(else_stmts) = else_body {
424 size += self.calculate_body_size(else_stmts);
425 }
426 size
427 }
428
429 fn calculate_loop_size(&self, condition: &HirExpr, body: &[HirStmt]) -> usize {
430 let mut size = 1 + self.calculate_expr_size(condition);
431 size += self.calculate_body_size(body);
432 size
433 }
434
435 fn calculate_body_size(&self, body: &[HirStmt]) -> usize {
436 body.iter().map(|s| self.calculate_stmt_size(s)).sum()
437 }
438
439 fn calculate_expr_size(&self, expr: &HirExpr) -> usize {
440 calculate_expr_size_inner(expr)
441 }
442
443 fn contains_loops(&self, body: &[HirStmt]) -> bool {
444 contains_loops_inner(body)
445 }
446
447 fn has_side_effects(&self, func: &HirFunction) -> bool {
448 if !func.properties.is_pure {
451 return true;
452 }
453
454 for stmt in &func.body {
456 if self.stmt_has_side_effects(stmt) {
457 return true;
458 }
459 }
460
461 false
462 }
463
464 fn stmt_has_side_effects(&self, stmt: &HirStmt) -> bool {
465 match stmt {
466 HirStmt::Expr(expr) => self.expr_has_side_effects(expr),
467 HirStmt::Assign { value, .. } => self.expr_has_side_effects(value),
468 HirStmt::Return(Some(expr)) => self.expr_has_side_effects(expr),
469 HirStmt::If {
470 condition,
471 then_body,
472 else_body,
473 } => self.if_has_side_effects(condition, then_body, else_body),
474 HirStmt::While { condition, body }
475 | HirStmt::For {
476 iter: condition,
477 body,
478 ..
479 } => self.loop_has_side_effects(condition, body),
480 HirStmt::Raise { .. } => true,
481 _ => false,
482 }
483 }
484
485 fn if_has_side_effects(
486 &self,
487 condition: &HirExpr,
488 then_body: &[HirStmt],
489 else_body: &Option<Vec<HirStmt>>,
490 ) -> bool {
491 self.expr_has_side_effects(condition)
492 || self.body_has_side_effects(then_body)
493 || else_body
494 .as_ref()
495 .map(|stmts| self.body_has_side_effects(stmts))
496 .unwrap_or(false)
497 }
498
499 fn loop_has_side_effects(&self, condition: &HirExpr, body: &[HirStmt]) -> bool {
500 self.expr_has_side_effects(condition) || self.body_has_side_effects(body)
501 }
502
503 fn body_has_side_effects(&self, body: &[HirStmt]) -> bool {
504 body.iter().any(|s| self.stmt_has_side_effects(s))
505 }
506
507 fn expr_has_side_effects(&self, expr: &HirExpr) -> bool {
508 expr_has_side_effects_inner(expr)
509 }
510
511 fn is_trivial_function(&self, func: &HirFunction) -> bool {
512 if func.body.len() == 1 {
514 matches!(func.body[0], HirStmt::Return(_))
515 } else {
516 false
517 }
518 }
519
520 fn count_returns(&self, body: &[HirStmt]) -> usize {
521 count_returns_inner(body)
522 }
523
524 fn estimate_cost(
525 &self,
526 func: &HirFunction,
527 size: usize,
528 has_loops: bool,
529 has_side_effects: bool,
530 ) -> f64 {
531 let mut cost = size as f64;
532
533 if has_loops {
535 cost *= 10.0;
536 }
537
538 if has_side_effects {
540 cost *= 2.0;
541 }
542
543 let return_count = count_returns_inner(&func.body);
545 if return_count > 1 {
546 cost *= 1.0 + (return_count as f64 * 0.2);
547 }
548
549 cost += func.params.len() as f64 * 0.5;
551
552 cost
553 }
554
555 fn make_decisions(&self) -> HashMap<String, InliningDecision> {
556 let mut decisions = HashMap::new();
557
558 for (func_name, metrics) in &self.function_metrics {
559 let decision = self.decide_inlining(func_name, metrics);
560 decisions.insert(func_name.clone(), decision);
561 }
562
563 decisions
564 }
565
566 fn decide_inlining(&self, func_name: &str, metrics: &FunctionMetrics) -> InliningDecision {
567 if let Some(rejection) = self.check_inlining_rejections(func_name, metrics) {
569 return rejection;
570 }
571
572 if let Some(approval) = self.check_inlining_approvals(metrics) {
574 return approval;
575 }
576
577 self.decide_by_cost_benefit(metrics)
579 }
580
581 fn check_inlining_rejections(
582 &self,
583 func_name: &str,
584 metrics: &FunctionMetrics,
585 ) -> Option<InliningDecision> {
586 if self.call_graph.recursive.contains(func_name) {
587 return Some(InliningDecision {
588 should_inline: false,
589 reason: InliningReason::Recursive,
590 cost_benefit: 0.0,
591 });
592 }
593
594 if metrics.size > self.config.max_inline_size {
595 return Some(InliningDecision {
596 should_inline: false,
597 reason: InliningReason::TooLarge,
598 cost_benefit: 0.0,
599 });
600 }
601
602 if metrics.has_loops && !self.config.inline_loops {
603 return Some(InliningDecision {
604 should_inline: false,
605 reason: InliningReason::ContainsLoops,
606 cost_benefit: 0.0,
607 });
608 }
609
610 if metrics.has_side_effects {
611 return Some(InliningDecision {
612 should_inline: false,
613 reason: InliningReason::HasSideEffects,
614 cost_benefit: 0.0,
615 });
616 }
617
618 None
619 }
620
621 fn check_inlining_approvals(&self, metrics: &FunctionMetrics) -> Option<InliningDecision> {
622 if self.config.inline_trivial && metrics.is_trivial {
623 return Some(InliningDecision {
624 should_inline: true,
625 reason: InliningReason::Trivial,
626 cost_benefit: 10.0,
627 });
628 }
629
630 if self.config.inline_single_use && metrics.call_count == 1 && !metrics.has_side_effects {
631 return Some(InliningDecision {
632 should_inline: true,
633 reason: InliningReason::SingleUse,
634 cost_benefit: 5.0,
635 });
636 }
637
638 None
639 }
640
641 fn decide_by_cost_benefit(&self, metrics: &FunctionMetrics) -> InliningDecision {
642 let call_overhead = 1.0;
643 let benefit = (call_overhead * metrics.call_count as f64) - metrics.cost;
644 let cost_benefit = benefit / metrics.cost;
645
646 if cost_benefit >= self.config.cost_threshold {
647 InliningDecision {
648 should_inline: true,
649 reason: InliningReason::SmallHotFunction,
650 cost_benefit,
651 }
652 } else {
653 InliningDecision {
654 should_inline: false,
655 reason: InliningReason::CostTooHigh,
656 cost_benefit,
657 }
658 }
659 }
660
661 fn try_inline_stmt(
662 &self,
663 stmt: &HirStmt,
664 function_map: &HashMap<String, HirFunction>,
665 decisions: &HashMap<String, InliningDecision>,
666 depth: usize,
667 ) -> Option<Vec<HirStmt>> {
668 if depth >= self.config.max_inline_depth {
670 return None;
671 }
672
673 match stmt {
674 HirStmt::Expr(HirExpr::Call { func, args, .. }) => {
675 self.try_inline_expr_call(func, args, function_map, decisions, depth)
676 }
677 HirStmt::Assign { target, value, .. } => {
678 self.try_inline_assign_call(target, value, function_map, decisions, depth)
679 }
680 _ => None,
681 }
682 }
683
684 fn try_inline_expr_call(
685 &self,
686 func: &str,
687 args: &[HirExpr],
688 function_map: &HashMap<String, HirFunction>,
689 decisions: &HashMap<String, InliningDecision>,
690 depth: usize,
691 ) -> Option<Vec<HirStmt>> {
692 let decision = decisions.get(func)?;
693 if !decision.should_inline {
694 return None;
695 }
696 let target_func = function_map.get(func)?;
697 Some(self.inline_function_call(target_func, args, function_map, decisions, depth))
698 }
699
700 fn try_inline_assign_call(
701 &self,
702 target: &depyler_hir::hir::AssignTarget,
703 value: &HirExpr,
704 function_map: &HashMap<String, HirFunction>,
705 decisions: &HashMap<String, InliningDecision>,
706 depth: usize,
707 ) -> Option<Vec<HirStmt>> {
708 if let HirExpr::Call { func, args, .. } = value {
709 let decision = decisions.get(func)?;
710 if !decision.should_inline {
711 return None;
712 }
713 let target_func = function_map.get(func)?;
714 let inlined =
715 self.inline_function_call(target_func, args, function_map, decisions, depth);
716
717 if inlined.is_empty() {
718 return None;
719 }
720
721 let mut result = inlined;
722 self.replace_return_with_assign(&mut result, target);
723 Some(result)
724 } else {
725 None
726 }
727 }
728
729 fn replace_return_with_assign(
730 &self,
731 statements: &mut [HirStmt],
732 target: &depyler_hir::hir::AssignTarget,
733 ) {
734 if let Some(last) = statements.last_mut() {
735 if let HirStmt::Return(Some(expr)) = last {
736 *last = HirStmt::Assign {
737 target: target.clone(),
738 value: expr.clone(),
739 type_annotation: None,
740 };
741 }
742 }
743 }
744
745 fn inline_function_call(
746 &self,
747 func: &HirFunction,
748 args: &[HirExpr],
749 function_map: &HashMap<String, HirFunction>,
750 decisions: &HashMap<String, InliningDecision>,
751 depth: usize,
752 ) -> Vec<HirStmt> {
753 let mut inlined_body = Vec::new();
754
755 for (i, param) in func.params.iter().enumerate() {
757 if let Some(arg) = args.get(i) {
758 inlined_body.push(HirStmt::Assign {
759 target: depyler_hir::hir::AssignTarget::Symbol(format!("_inline_{}", param.name)),
760 value: arg.clone(),
761 type_annotation: None,
762 });
763 }
764 }
765
766 for stmt in &func.body {
768 let transformed = self.transform_stmt_for_inlining(stmt, &func.params, depth + 1);
769
770 if let Some(inlined) =
772 self.try_inline_stmt(&transformed, function_map, decisions, depth + 1)
773 {
774 inlined_body.extend(inlined);
775 } else {
776 inlined_body.push(transformed);
777 }
778 }
779
780 inlined_body
781 }
782
783 fn transform_stmt_for_inlining(
784 &self,
785 stmt: &HirStmt,
786 params: &[depyler_hir::hir::HirParam],
787 _depth: usize,
788 ) -> HirStmt {
789 match stmt {
790 HirStmt::Expr(expr) => HirStmt::Expr(transform_expr_for_inlining_inner(expr, params)),
791 HirStmt::Assign {
792 target,
793 value,
794 type_annotation,
795 } => HirStmt::Assign {
796 target: self.transform_assign_target_for_inlining(target, params),
797 value: transform_expr_for_inlining_inner(value, params),
798 type_annotation: type_annotation.clone(),
799 },
800 HirStmt::Return(Some(expr)) => {
801 HirStmt::Return(Some(transform_expr_for_inlining_inner(expr, params)))
802 }
803 HirStmt::If {
804 condition,
805 then_body,
806 else_body,
807 } => HirStmt::If {
808 condition: transform_expr_for_inlining_inner(condition, params),
809 then_body: then_body
810 .iter()
811 .map(|s| self.transform_stmt_for_inlining(s, params, _depth))
812 .collect(),
813 else_body: else_body.as_ref().map(|stmts| {
814 stmts
815 .iter()
816 .map(|s| self.transform_stmt_for_inlining(s, params, _depth))
817 .collect()
818 }),
819 },
820 _ => stmt.clone(),
821 }
822 }
823
824 #[allow(dead_code)]
825 fn transform_expr_for_inlining(
826 &self,
827 expr: &HirExpr,
828 params: &[depyler_hir::hir::HirParam],
829 ) -> HirExpr {
830 transform_expr_for_inlining_inner(expr, params)
831 }
832
833 fn transform_assign_target_for_inlining(
834 &self,
835 target: &depyler_hir::hir::AssignTarget,
836 params: &[depyler_hir::hir::HirParam],
837 ) -> depyler_hir::hir::AssignTarget {
838 match target {
839 depyler_hir::hir::AssignTarget::Symbol(name) => {
840 if params.iter().any(|p| &p.name == name) {
841 depyler_hir::hir::AssignTarget::Symbol(format!("_inline_{}", name))
842 } else {
843 target.clone()
844 }
845 }
846 _ => target.clone(),
847 }
848 }
849}
850
851fn calculate_expr_size_inner(expr: &HirExpr) -> usize {
852 match expr {
853 HirExpr::Literal(_) | HirExpr::Var(_) => 1,
854 HirExpr::Binary { left, right, .. } => {
855 1 + calculate_expr_size_inner(left) + calculate_expr_size_inner(right)
856 }
857 HirExpr::Unary { operand, .. } => 1 + calculate_expr_size_inner(operand),
858 HirExpr::Call { args, .. } => 1 + args.iter().map(calculate_expr_size_inner).sum::<usize>(),
859 HirExpr::List(items) | HirExpr::Tuple(items) => {
860 1 + items.iter().map(calculate_expr_size_inner).sum::<usize>()
861 }
862 HirExpr::Dict(pairs) => {
863 1 + pairs
864 .iter()
865 .map(|(k, v)| calculate_expr_size_inner(k) + calculate_expr_size_inner(v))
866 .sum::<usize>()
867 }
868 _ => 1,
869 }
870}
871
872fn contains_loops_inner(body: &[HirStmt]) -> bool {
873 for stmt in body {
874 match stmt {
875 HirStmt::While { .. } | HirStmt::For { .. } => return true,
876 HirStmt::If {
877 then_body,
878 else_body,
879 ..
880 } => {
881 if contains_loops_inner(then_body) {
882 return true;
883 }
884 if let Some(else_stmts) = else_body {
885 if contains_loops_inner(else_stmts) {
886 return true;
887 }
888 }
889 }
890 _ => {}
891 }
892 }
893 false
894}
895
896fn expr_has_side_effects_inner(expr: &HirExpr) -> bool {
897 match expr {
898 HirExpr::Call { func, args, .. } => call_has_side_effects(func, args),
899 HirExpr::MethodCall { object, method, .. } => {
900 method_has_side_effects(method) || expr_has_side_effects_inner(object)
901 }
902 HirExpr::Binary { left, right, .. } => binary_has_side_effects(left, right),
903 HirExpr::Unary { operand, .. } => expr_has_side_effects_inner(operand),
904 HirExpr::List(items) | HirExpr::Tuple(items) => collection_has_side_effects(items),
905 HirExpr::Dict(pairs) => dict_has_side_effects(pairs),
906 _ => false,
907 }
908}
909
910fn call_has_side_effects(func: &str, args: &[HirExpr]) -> bool {
911 let pure_functions = ["len", "abs", "min", "max", "sum", "str", "int", "float"];
912 !pure_functions.contains(&func) || args.iter().any(expr_has_side_effects_inner)
913}
914
915fn method_has_side_effects(method: &str) -> bool {
916 let mutating_methods = [
917 "append", "extend", "remove", "pop", "clear", "sort", "reverse", "insert", "update",
918 "write",
919 ];
920 mutating_methods.contains(&method)
921}
922
923fn binary_has_side_effects(left: &HirExpr, right: &HirExpr) -> bool {
924 expr_has_side_effects_inner(left) || expr_has_side_effects_inner(right)
925}
926
927fn collection_has_side_effects(items: &[HirExpr]) -> bool {
928 items.iter().any(expr_has_side_effects_inner)
929}
930
931fn dict_has_side_effects(pairs: &[(HirExpr, HirExpr)]) -> bool {
932 pairs
933 .iter()
934 .any(|(k, v)| expr_has_side_effects_inner(k) || expr_has_side_effects_inner(v))
935}
936
937fn count_returns_inner(body: &[HirStmt]) -> usize {
938 let mut count = 0;
939 for stmt in body {
940 match stmt {
941 HirStmt::Return(_) => count += 1,
942 HirStmt::If {
943 then_body,
944 else_body,
945 ..
946 } => {
947 count += count_returns_inner(then_body);
948 if let Some(else_stmts) = else_body {
949 count += count_returns_inner(else_stmts);
950 }
951 }
952 HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
953 count += count_returns_inner(body);
954 }
955 _ => {}
956 }
957 }
958 count
959}
960
961fn transform_expr_for_inlining_inner(expr: &HirExpr, params: &[depyler_hir::hir::HirParam]) -> HirExpr {
962 match expr {
963 HirExpr::Var(name) => {
964 if params.iter().any(|p| &p.name == name) {
966 HirExpr::Var(format!("_inline_{}", name))
967 } else {
968 expr.clone()
969 }
970 }
971 HirExpr::Binary { left, right, op } => HirExpr::Binary {
972 left: Box::new(transform_expr_for_inlining_inner(left, params)),
973 right: Box::new(transform_expr_for_inlining_inner(right, params)),
974 op: *op,
975 },
976 HirExpr::Unary { operand, op } => HirExpr::Unary {
977 operand: Box::new(transform_expr_for_inlining_inner(operand, params)),
978 op: *op,
979 },
980 HirExpr::Call { func, args, .. } => HirExpr::Call {
981 func: func.clone(),
982 args: args
983 .iter()
984 .map(|a| transform_expr_for_inlining_inner(a, params))
985 .collect(),
986 kwargs: vec![],
987 },
988 _ => expr.clone(),
989 }
990}
991
992#[cfg(test)]
993mod tests {
994 use super::*;
995 use depyler_hir::hir::*;
996 use smallvec::smallvec;
997
998 fn create_simple_function(name: &str, size: usize) -> HirFunction {
999 let mut body = Vec::new();
1000 for i in 0..size {
1001 body.push(HirStmt::Assign {
1002 target: AssignTarget::Symbol(Symbol::from(format!("x{}", i).as_str())),
1003 value: HirExpr::Literal(Literal::Int(i as i64)),
1004 type_annotation: None,
1005 });
1006 }
1007 body.push(HirStmt::Return(Some(HirExpr::Var("x0".to_string()))));
1008
1009 HirFunction {
1010 name: name.to_string(),
1011 params: smallvec![],
1012 ret_type: Type::Int,
1013 body,
1014 properties: FunctionProperties::default(),
1015 annotations: Default::default(),
1016 docstring: None,
1017 }
1018 }
1019
1020 #[test]
1021 fn test_trivial_function_detection() {
1022 let func = HirFunction {
1023 name: "identity".to_string(),
1024 params: smallvec![HirParam::new("x".to_string(), Type::Int)],
1025 ret_type: Type::Int,
1026 body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
1027 properties: FunctionProperties::default(),
1028 annotations: Default::default(),
1029 docstring: None,
1030 };
1031
1032 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1033 assert!(analyzer.is_trivial_function(&func));
1034 }
1035
1036 #[test]
1037 fn test_function_size_calculation() {
1038 let func = create_simple_function("test", 5);
1039 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1040 let size = analyzer.calculate_function_size(&func);
1041 assert_eq!(size, 12); }
1043
1044 #[test]
1045 fn test_loop_detection() {
1046 let body = vec![HirStmt::While {
1047 condition: HirExpr::Literal(Literal::Bool(true)),
1048 body: vec![HirStmt::Break { label: None }],
1049 }];
1050
1051 let _analyzer = InliningAnalyzer::new(InliningConfig::default());
1052 assert!(contains_loops_inner(&body));
1053 }
1054
1055 #[test]
1056 fn test_side_effect_detection() {
1057 let expr = HirExpr::MethodCall {
1058 object: Box::new(HirExpr::Var("list".to_string())),
1059 method: "append".to_string(),
1060 args: vec![HirExpr::Literal(Literal::Int(42))],
1061 kwargs: vec![],
1062 };
1063
1064 let _analyzer = InliningAnalyzer::new(InliningConfig::default());
1065 assert!(expr_has_side_effects_inner(&expr));
1066 }
1067
1068 #[test]
1069 fn test_inlining_config_default() {
1070 let config = InliningConfig::default();
1071 assert_eq!(config.max_inline_size, 20);
1072 assert_eq!(config.max_inline_depth, 3);
1073 assert!(config.inline_single_use);
1074 assert!(config.inline_trivial);
1075 }
1076
1077 #[test]
1078 fn test_inlining_config_custom() {
1079 let config = InliningConfig {
1080 max_inline_size: 50,
1081 max_inline_depth: 5,
1082 inline_single_use: false,
1083 inline_trivial: false,
1084 cost_threshold: 2.0,
1085 inline_loops: true,
1086 };
1087 assert_eq!(config.max_inline_size, 50);
1088 assert_eq!(config.max_inline_depth, 5);
1089 assert!(!config.inline_single_use);
1090 assert!(!config.inline_trivial);
1091 assert_eq!(config.cost_threshold, 2.0);
1092 assert!(config.inline_loops);
1093 }
1094
1095 #[test]
1096 fn test_inlining_reason_variants() {
1097 let reasons = [
1098 InliningReason::Trivial,
1099 InliningReason::SingleUse,
1100 InliningReason::SmallHotFunction,
1101 InliningReason::EnablesOptimization,
1102 InliningReason::TooLarge,
1103 InliningReason::Recursive,
1104 InliningReason::HasSideEffects,
1105 InliningReason::ContainsLoops,
1106 ];
1107 for reason in &reasons {
1108 let _ = format!("{:?}", reason);
1109 }
1110 }
1111
1112 #[test]
1113 fn test_inlining_decision_creation() {
1114 let decision = InliningDecision {
1115 should_inline: true,
1116 reason: InliningReason::Trivial,
1117 cost_benefit: 1.5,
1118 };
1119 assert!(decision.should_inline);
1120 assert_eq!(decision.cost_benefit, 1.5);
1121 }
1122
1123 #[test]
1124 fn test_analyzer_new() {
1125 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1126 assert!(std::mem::size_of_val(&analyzer) > 0);
1127 }
1128
1129 #[test]
1130 fn test_non_trivial_function() {
1131 let func = HirFunction {
1132 name: "complex".to_string(),
1133 params: smallvec![HirParam::new("x".to_string(), Type::Int)],
1134 ret_type: Type::Int,
1135 body: vec![
1136 HirStmt::Assign {
1137 target: AssignTarget::Symbol("y".to_string()),
1138 value: HirExpr::Var("x".to_string()),
1139 type_annotation: None,
1140 },
1141 HirStmt::Return(Some(HirExpr::Var("y".to_string()))),
1142 ],
1143 properties: FunctionProperties::default(),
1144 annotations: Default::default(),
1145 docstring: None,
1146 };
1147
1148 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1149 assert!(!analyzer.is_trivial_function(&func));
1150 }
1151
1152 #[test]
1153 fn test_for_loop_detection() {
1154 let body = vec![HirStmt::For {
1155 target: AssignTarget::Symbol("i".to_string()),
1156 iter: HirExpr::Call {
1157 func: "range".to_string(),
1158 args: vec![HirExpr::Literal(Literal::Int(10))],
1159 kwargs: vec![],
1160 },
1161 body: vec![],
1162 }];
1163 assert!(contains_loops_inner(&body));
1164 }
1165
1166 #[test]
1167 fn test_if_no_loop() {
1168 let body = vec![HirStmt::If {
1169 condition: HirExpr::Literal(Literal::Bool(true)),
1170 then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
1171 else_body: None,
1172 }];
1173 assert!(!contains_loops_inner(&body));
1174 }
1175
1176 #[test]
1177 fn test_print_side_effect() {
1178 let expr = HirExpr::Call {
1179 func: "print".to_string(),
1180 args: vec![HirExpr::Literal(Literal::String("hello".to_string()))],
1181 kwargs: vec![],
1182 };
1183 assert!(expr_has_side_effects_inner(&expr));
1184 }
1185
1186 #[test]
1187 fn test_append_side_effect() {
1188 let expr = HirExpr::MethodCall {
1189 object: Box::new(HirExpr::Var("list".to_string())),
1190 method: "append".to_string(),
1191 args: vec![HirExpr::Literal(Literal::Int(1))],
1192 kwargs: vec![],
1193 };
1194 assert!(expr_has_side_effects_inner(&expr));
1195 }
1196
1197 #[test]
1198 fn test_pure_function_no_side_effect() {
1199 let expr = HirExpr::Binary {
1200 left: Box::new(HirExpr::Literal(Literal::Int(1))),
1201 op: BinOp::Add,
1202 right: Box::new(HirExpr::Literal(Literal::Int(2))),
1203 };
1204 assert!(!expr_has_side_effects_inner(&expr));
1205 }
1206
1207 #[test]
1208 fn test_function_size_empty() {
1209 let func = HirFunction {
1210 name: "empty".to_string(),
1211 params: smallvec![],
1212 ret_type: Type::None,
1213 body: vec![],
1214 properties: FunctionProperties::default(),
1215 annotations: Default::default(),
1216 docstring: None,
1217 };
1218 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1219 assert_eq!(analyzer.calculate_function_size(&func), 0);
1220 }
1221
1222 #[test]
1223 fn test_call_graph_default() {
1224 let cg = CallGraph::default();
1225 assert!(cg.calls.is_empty());
1226 assert!(cg.called_by.is_empty());
1227 assert!(cg.recursive.is_empty());
1228 }
1229
1230 #[test]
1231 fn test_expr_size_literal() {
1232 let expr = HirExpr::Literal(Literal::Int(42));
1233 assert_eq!(calculate_expr_size_inner(&expr), 1);
1234 }
1235
1236 #[test]
1237 fn test_expr_size_var() {
1238 let expr = HirExpr::Var("x".to_string());
1239 assert_eq!(calculate_expr_size_inner(&expr), 1);
1240 }
1241
1242 #[test]
1243 fn test_expr_size_binary() {
1244 let expr = HirExpr::Binary {
1245 left: Box::new(HirExpr::Literal(Literal::Int(1))),
1246 op: BinOp::Add,
1247 right: Box::new(HirExpr::Literal(Literal::Int(2))),
1248 };
1249 assert_eq!(calculate_expr_size_inner(&expr), 3);
1250 }
1251
1252 #[test]
1253 fn test_expr_size_list() {
1254 let expr = HirExpr::List(vec![
1255 HirExpr::Literal(Literal::Int(1)),
1256 HirExpr::Literal(Literal::Int(2)),
1257 ]);
1258 assert_eq!(calculate_expr_size_inner(&expr), 3);
1259 }
1260
1261 #[test]
1262 fn test_stmt_size_return_none() {
1263 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1264 let stmt = HirStmt::Return(None);
1265 assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
1266 }
1267
1268 #[test]
1269 fn test_stmt_size_return_some() {
1270 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1271 let stmt = HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))));
1272 assert_eq!(analyzer.calculate_stmt_size(&stmt), 2);
1273 }
1274
1275 #[test]
1276 fn test_stmt_size_expr() {
1277 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1278 let stmt = HirStmt::Expr(HirExpr::Literal(Literal::Int(42)));
1279 assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
1280 }
1281
1282 #[test]
1283 fn test_stmt_size_pass() {
1284 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1285 let stmt = HirStmt::Pass;
1286 assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
1287 }
1288
1289 #[test]
1290 fn test_stmt_size_break() {
1291 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1292 let stmt = HirStmt::Break { label: None };
1293 assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
1294 }
1295
1296 #[test]
1297 fn test_stmt_size_continue() {
1298 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1299 let stmt = HirStmt::Continue { label: None };
1300 assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
1301 }
1302
1303 #[test]
1308 fn test_extract_calls_from_call_expr() {
1309 let mut calls = HashSet::new();
1310 let expr = HirExpr::Call {
1311 func: "foo".to_string(),
1312 args: vec![HirExpr::Literal(Literal::Int(1))],
1313 kwargs: vec![],
1314 };
1315 extract_calls_from_expr_inner(&expr, &mut calls);
1316 assert!(calls.contains("foo"));
1317 }
1318
1319 #[test]
1320 fn test_extract_calls_from_binary_expr() {
1321 let mut calls = HashSet::new();
1322 let expr = HirExpr::Binary {
1323 left: Box::new(HirExpr::Call {
1324 func: "left_fn".to_string(),
1325 args: vec![],
1326 kwargs: vec![],
1327 }),
1328 op: BinOp::Add,
1329 right: Box::new(HirExpr::Call {
1330 func: "right_fn".to_string(),
1331 args: vec![],
1332 kwargs: vec![],
1333 }),
1334 };
1335 extract_calls_from_expr_inner(&expr, &mut calls);
1336 assert!(calls.contains("left_fn"));
1337 assert!(calls.contains("right_fn"));
1338 }
1339
1340 #[test]
1341 fn test_extract_calls_from_unary_expr() {
1342 let mut calls = HashSet::new();
1343 let expr = HirExpr::Unary {
1344 op: depyler_hir::hir::UnaryOp::Neg,
1345 operand: Box::new(HirExpr::Call {
1346 func: "inner".to_string(),
1347 args: vec![],
1348 kwargs: vec![],
1349 }),
1350 };
1351 extract_calls_from_expr_inner(&expr, &mut calls);
1352 assert!(calls.contains("inner"));
1353 }
1354
1355 #[test]
1356 fn test_extract_calls_from_list() {
1357 let mut calls = HashSet::new();
1358 let expr = HirExpr::List(vec![
1359 HirExpr::Call {
1360 func: "fn1".to_string(),
1361 args: vec![],
1362 kwargs: vec![],
1363 },
1364 HirExpr::Call {
1365 func: "fn2".to_string(),
1366 args: vec![],
1367 kwargs: vec![],
1368 },
1369 ]);
1370 extract_calls_from_expr_inner(&expr, &mut calls);
1371 assert!(calls.contains("fn1"));
1372 assert!(calls.contains("fn2"));
1373 }
1374
1375 #[test]
1376 fn test_extract_calls_from_tuple() {
1377 let mut calls = HashSet::new();
1378 let expr = HirExpr::Tuple(vec![HirExpr::Call {
1379 func: "tuple_fn".to_string(),
1380 args: vec![],
1381 kwargs: vec![],
1382 }]);
1383 extract_calls_from_expr_inner(&expr, &mut calls);
1384 assert!(calls.contains("tuple_fn"));
1385 }
1386
1387 #[test]
1388 fn test_extract_calls_from_dict() {
1389 let mut calls = HashSet::new();
1390 let expr = HirExpr::Dict(vec![(
1391 HirExpr::Call {
1392 func: "key_fn".to_string(),
1393 args: vec![],
1394 kwargs: vec![],
1395 },
1396 HirExpr::Call {
1397 func: "val_fn".to_string(),
1398 args: vec![],
1399 kwargs: vec![],
1400 },
1401 )]);
1402 extract_calls_from_expr_inner(&expr, &mut calls);
1403 assert!(calls.contains("key_fn"));
1404 assert!(calls.contains("val_fn"));
1405 }
1406
1407 #[test]
1408 fn test_extract_calls_from_method_call() {
1409 let mut calls = HashSet::new();
1410 let expr = HirExpr::MethodCall {
1411 object: Box::new(HirExpr::Call {
1412 func: "get_obj".to_string(),
1413 args: vec![],
1414 kwargs: vec![],
1415 }),
1416 method: "method".to_string(),
1417 args: vec![HirExpr::Call {
1418 func: "get_arg".to_string(),
1419 args: vec![],
1420 kwargs: vec![],
1421 }],
1422 kwargs: vec![],
1423 };
1424 extract_calls_from_expr_inner(&expr, &mut calls);
1425 assert!(calls.contains("get_obj"));
1426 assert!(calls.contains("get_arg"));
1427 }
1428
1429 #[test]
1430 fn test_extract_calls_from_lambda() {
1431 let mut calls = HashSet::new();
1432 let expr = HirExpr::Lambda {
1433 params: vec!["x".to_string()],
1434 body: Box::new(HirExpr::Call {
1435 func: "lambda_call".to_string(),
1436 args: vec![],
1437 kwargs: vec![],
1438 }),
1439 };
1440 extract_calls_from_expr_inner(&expr, &mut calls);
1441 assert!(calls.contains("lambda_call"));
1442 }
1443
1444 #[test]
1445 fn test_extract_calls_from_var() {
1446 let mut calls = HashSet::new();
1447 let expr = HirExpr::Var("x".to_string());
1448 extract_calls_from_expr_inner(&expr, &mut calls);
1449 assert!(calls.is_empty());
1450 }
1451
1452 #[test]
1453 fn test_extract_calls_from_literal() {
1454 let mut calls = HashSet::new();
1455 let expr = HirExpr::Literal(Literal::Int(42));
1456 extract_calls_from_expr_inner(&expr, &mut calls);
1457 assert!(calls.is_empty());
1458 }
1459
1460 #[test]
1461 fn test_analyzer_extract_calls_from_function() {
1462 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1463 let func = HirFunction {
1464 name: "caller".to_string(),
1465 params: smallvec![],
1466 ret_type: Type::Int,
1467 body: vec![
1468 HirStmt::Expr(HirExpr::Call {
1469 func: "callee1".to_string(),
1470 args: vec![],
1471 kwargs: vec![],
1472 }),
1473 HirStmt::Assign {
1474 target: AssignTarget::Symbol("x".to_string()),
1475 value: HirExpr::Call {
1476 func: "callee2".to_string(),
1477 args: vec![],
1478 kwargs: vec![],
1479 },
1480 type_annotation: None,
1481 },
1482 HirStmt::Return(Some(HirExpr::Call {
1483 func: "callee3".to_string(),
1484 args: vec![],
1485 kwargs: vec![],
1486 })),
1487 ],
1488 properties: FunctionProperties::default(),
1489 annotations: Default::default(),
1490 docstring: None,
1491 };
1492 let calls = analyzer.extract_calls_from_function(&func);
1493 assert!(calls.contains("callee1"));
1494 assert!(calls.contains("callee2"));
1495 assert!(calls.contains("callee3"));
1496 }
1497
1498 #[test]
1499 fn test_analyzer_extract_calls_from_if() {
1500 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1501 let func = HirFunction {
1502 name: "if_caller".to_string(),
1503 params: smallvec![],
1504 ret_type: Type::Int,
1505 body: vec![HirStmt::If {
1506 condition: HirExpr::Call {
1507 func: "cond_fn".to_string(),
1508 args: vec![],
1509 kwargs: vec![],
1510 },
1511 then_body: vec![HirStmt::Expr(HirExpr::Call {
1512 func: "then_fn".to_string(),
1513 args: vec![],
1514 kwargs: vec![],
1515 })],
1516 else_body: Some(vec![HirStmt::Expr(HirExpr::Call {
1517 func: "else_fn".to_string(),
1518 args: vec![],
1519 kwargs: vec![],
1520 })]),
1521 }],
1522 properties: FunctionProperties::default(),
1523 annotations: Default::default(),
1524 docstring: None,
1525 };
1526 let calls = analyzer.extract_calls_from_function(&func);
1527 assert!(calls.contains("cond_fn"));
1528 assert!(calls.contains("then_fn"));
1529 assert!(calls.contains("else_fn"));
1530 }
1531
1532 #[test]
1533 fn test_analyzer_extract_calls_from_while() {
1534 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1535 let func = HirFunction {
1536 name: "while_caller".to_string(),
1537 params: smallvec![],
1538 ret_type: Type::Int,
1539 body: vec![HirStmt::While {
1540 condition: HirExpr::Call {
1541 func: "while_cond".to_string(),
1542 args: vec![],
1543 kwargs: vec![],
1544 },
1545 body: vec![HirStmt::Expr(HirExpr::Call {
1546 func: "while_body".to_string(),
1547 args: vec![],
1548 kwargs: vec![],
1549 })],
1550 }],
1551 properties: FunctionProperties::default(),
1552 annotations: Default::default(),
1553 docstring: None,
1554 };
1555 let calls = analyzer.extract_calls_from_function(&func);
1556 assert!(calls.contains("while_cond"));
1557 assert!(calls.contains("while_body"));
1558 }
1559
1560 #[test]
1561 fn test_analyzer_extract_calls_from_for() {
1562 let analyzer = InliningAnalyzer::new(InliningConfig::default());
1563 let func = HirFunction {
1564 name: "for_caller".to_string(),
1565 params: smallvec![],
1566 ret_type: Type::Int,
1567 body: vec![HirStmt::For {
1568 target: AssignTarget::Symbol("i".to_string()),
1569 iter: HirExpr::Call {
1570 func: "for_iter".to_string(),
1571 args: vec![],
1572 kwargs: vec![],
1573 },
1574 body: vec![HirStmt::Expr(HirExpr::Call {
1575 func: "for_body".to_string(),
1576 args: vec![],
1577 kwargs: vec![],
1578 })],
1579 }],
1580 properties: FunctionProperties::default(),
1581 annotations: Default::default(),
1582 docstring: None,
1583 };
1584 let calls = analyzer.extract_calls_from_function(&func);
1585 assert!(calls.contains("for_iter"));
1586 assert!(calls.contains("for_body"));
1587 }
1588
1589 #[test]
1590 fn test_function_metrics_default() {
1591 let metrics = FunctionMetrics {
1592 size: 10,
1593 _param_count: 2,
1594 _return_count: 1,
1595 has_loops: false,
1596 has_side_effects: false,
1597 is_trivial: true,
1598 call_count: 3,
1599 cost: 1.5,
1600 };
1601 assert_eq!(metrics.size, 10);
1602 assert!(!metrics.has_loops);
1603 assert!(!metrics.has_side_effects);
1604 assert!(metrics.is_trivial);
1605 assert_eq!(metrics.call_count, 3);
1606 }
1607
1608 #[test]
1609 fn test_expr_size_call() {
1610 let expr = HirExpr::Call {
1611 func: "fn".to_string(),
1612 args: vec![
1613 HirExpr::Literal(Literal::Int(1)),
1614 HirExpr::Literal(Literal::Int(2)),
1615 ],
1616 kwargs: vec![],
1617 };
1618 let size = calculate_expr_size_inner(&expr);
1619 assert_eq!(size, 3); }
1621
1622 #[test]
1623 fn test_expr_size_method_call() {
1624 let expr = HirExpr::MethodCall {
1625 object: Box::new(HirExpr::Var("obj".to_string())),
1626 method: "method".to_string(),
1627 args: vec![HirExpr::Literal(Literal::Int(1))],
1628 kwargs: vec![],
1629 };
1630 let size = calculate_expr_size_inner(&expr);
1631 assert_eq!(size, 1); }
1633
1634 #[test]
1635 fn test_expr_size_dict() {
1636 let expr = HirExpr::Dict(vec![
1637 (
1638 HirExpr::Literal(Literal::String("a".to_string())),
1639 HirExpr::Literal(Literal::Int(1)),
1640 ),
1641 (
1642 HirExpr::Literal(Literal::String("b".to_string())),
1643 HirExpr::Literal(Literal::Int(2)),
1644 ),
1645 ]);
1646 let size = calculate_expr_size_inner(&expr);
1647 assert!(size >= 5); }
1649
1650 #[test]
1651 fn test_expr_size_lambda() {
1652 let expr = HirExpr::Lambda {
1653 params: vec!["x".to_string()],
1654 body: Box::new(HirExpr::Binary {
1655 left: Box::new(HirExpr::Var("x".to_string())),
1656 op: BinOp::Add,
1657 right: Box::new(HirExpr::Literal(Literal::Int(1))),
1658 }),
1659 };
1660 let size = calculate_expr_size_inner(&expr);
1661 assert_eq!(size, 1); }
1663
1664 #[test]
1665 fn test_expr_size_unary() {
1666 let expr = HirExpr::Unary {
1667 op: depyler_hir::hir::UnaryOp::Neg,
1668 operand: Box::new(HirExpr::Literal(Literal::Int(5))),
1669 };
1670 let size = calculate_expr_size_inner(&expr);
1671 assert_eq!(size, 2); }
1673
1674 #[test]
1675 fn test_contains_loops_with_nested_while() {
1676 let body = vec![HirStmt::If {
1677 condition: HirExpr::Literal(Literal::Bool(true)),
1678 then_body: vec![HirStmt::While {
1679 condition: HirExpr::Literal(Literal::Bool(true)),
1680 body: vec![],
1681 }],
1682 else_body: None,
1683 }];
1684 assert!(contains_loops_inner(&body));
1685 }
1686
1687 #[test]
1688 fn test_contains_loops_with_nested_for() {
1689 let body = vec![HirStmt::If {
1690 condition: HirExpr::Literal(Literal::Bool(true)),
1691 then_body: vec![HirStmt::For {
1692 target: AssignTarget::Symbol("i".to_string()),
1693 iter: HirExpr::Var("items".to_string()),
1694 body: vec![],
1695 }],
1696 else_body: None,
1697 }];
1698 assert!(contains_loops_inner(&body));
1699 }
1700
1701 #[test]
1702 fn test_side_effect_write() {
1703 let expr = HirExpr::MethodCall {
1704 object: Box::new(HirExpr::Var("file".to_string())),
1705 method: "write".to_string(),
1706 args: vec![HirExpr::Literal(Literal::String("data".to_string()))],
1707 kwargs: vec![],
1708 };
1709 assert!(expr_has_side_effects_inner(&expr));
1710 }
1711
1712 #[test]
1713 fn test_side_effect_extend() {
1714 let expr = HirExpr::MethodCall {
1715 object: Box::new(HirExpr::Var("list".to_string())),
1716 method: "extend".to_string(),
1717 args: vec![HirExpr::List(vec![])],
1718 kwargs: vec![],
1719 };
1720 assert!(expr_has_side_effects_inner(&expr));
1721 }
1722
1723 #[test]
1724 fn test_side_effect_insert() {
1725 let expr = HirExpr::MethodCall {
1726 object: Box::new(HirExpr::Var("list".to_string())),
1727 method: "insert".to_string(),
1728 args: vec![
1729 HirExpr::Literal(Literal::Int(0)),
1730 HirExpr::Literal(Literal::Int(42)),
1731 ],
1732 kwargs: vec![],
1733 };
1734 assert!(expr_has_side_effects_inner(&expr));
1735 }
1736
1737 #[test]
1738 fn test_side_effect_pop() {
1739 let expr = HirExpr::MethodCall {
1740 object: Box::new(HirExpr::Var("list".to_string())),
1741 method: "pop".to_string(),
1742 args: vec![],
1743 kwargs: vec![],
1744 };
1745 assert!(expr_has_side_effects_inner(&expr));
1746 }
1747
1748 #[test]
1749 fn test_side_effect_remove() {
1750 let expr = HirExpr::MethodCall {
1751 object: Box::new(HirExpr::Var("list".to_string())),
1752 method: "remove".to_string(),
1753 args: vec![HirExpr::Literal(Literal::Int(1))],
1754 kwargs: vec![],
1755 };
1756 assert!(expr_has_side_effects_inner(&expr));
1757 }
1758
1759 #[test]
1760 fn test_side_effect_clear() {
1761 let expr = HirExpr::MethodCall {
1762 object: Box::new(HirExpr::Var("list".to_string())),
1763 method: "clear".to_string(),
1764 args: vec![],
1765 kwargs: vec![],
1766 };
1767 assert!(expr_has_side_effects_inner(&expr));
1768 }
1769
1770 #[test]
1771 fn test_side_effect_update() {
1772 let expr = HirExpr::MethodCall {
1773 object: Box::new(HirExpr::Var("dict".to_string())),
1774 method: "update".to_string(),
1775 args: vec![HirExpr::Dict(vec![])],
1776 kwargs: vec![],
1777 };
1778 assert!(expr_has_side_effects_inner(&expr));
1779 }
1780
1781 #[test]
1782 fn test_pure_method_call() {
1783 let expr = HirExpr::MethodCall {
1784 object: Box::new(HirExpr::Var("list".to_string())),
1785 method: "copy".to_string(),
1786 args: vec![],
1787 kwargs: vec![],
1788 };
1789 assert!(!expr_has_side_effects_inner(&expr));
1790 }
1791
1792 #[test]
1793 fn test_side_effect_in_args() {
1794 let expr = HirExpr::Call {
1795 func: "pure_fn".to_string(),
1796 args: vec![HirExpr::Call {
1797 func: "print".to_string(),
1798 args: vec![],
1799 kwargs: vec![],
1800 }],
1801 kwargs: vec![],
1802 };
1803 assert!(expr_has_side_effects_inner(&expr));
1804 }
1805
1806 #[test]
1807 fn test_side_effect_in_method_object() {
1808 let expr = HirExpr::MethodCall {
1809 object: Box::new(HirExpr::MethodCall {
1810 object: Box::new(HirExpr::Var("list".to_string())),
1811 method: "append".to_string(),
1812 args: vec![HirExpr::Literal(Literal::Int(1))],
1813 kwargs: vec![],
1814 }),
1815 method: "copy".to_string(),
1816 args: vec![],
1817 kwargs: vec![],
1818 };
1819 assert!(expr_has_side_effects_inner(&expr));
1820 }
1821
1822 #[test]
1829 fn test_call_graph_with_function() {
1830 let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
1831 let program = HirProgram {
1832 functions: vec![
1833 HirFunction {
1834 name: "caller".to_string(),
1835 params: smallvec![],
1836 ret_type: Type::Int,
1837 body: vec![HirStmt::Return(Some(HirExpr::Call {
1838 func: "callee".to_string(),
1839 args: vec![],
1840 kwargs: vec![],
1841 }))],
1842 properties: FunctionProperties::default(),
1843 annotations: Default::default(),
1844 docstring: None,
1845 },
1846 HirFunction {
1847 name: "callee".to_string(),
1848 params: smallvec![],
1849 ret_type: Type::Int,
1850 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
1851 properties: FunctionProperties::default(),
1852 annotations: Default::default(),
1853 docstring: None,
1854 },
1855 ],
1856 classes: vec![],
1857 imports: vec![],
1858 };
1859 let decisions = analyzer.analyze_program(&program);
1860 assert!(!decisions.is_empty());
1861 }
1862
1863 #[test]
1865 fn test_recursive_function_detection() {
1866 let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
1867 let program = HirProgram {
1868 functions: vec![HirFunction {
1869 name: "recursive".to_string(),
1870 params: smallvec![HirParam::new("n".to_string(), Type::Int)],
1871 ret_type: Type::Int,
1872 body: vec![HirStmt::If {
1873 condition: HirExpr::Binary {
1874 left: Box::new(HirExpr::Var("n".to_string())),
1875 op: BinOp::Eq,
1876 right: Box::new(HirExpr::Literal(Literal::Int(0))),
1877 },
1878 then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
1879 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Binary {
1880 left: Box::new(HirExpr::Var("n".to_string())),
1881 op: BinOp::Mul,
1882 right: Box::new(HirExpr::Call {
1883 func: "recursive".to_string(),
1884 args: vec![HirExpr::Binary {
1885 left: Box::new(HirExpr::Var("n".to_string())),
1886 op: BinOp::Sub,
1887 right: Box::new(HirExpr::Literal(Literal::Int(1))),
1888 }],
1889 kwargs: vec![],
1890 }),
1891 }))]),
1892 }],
1893 properties: FunctionProperties::default(),
1894 annotations: Default::default(),
1895 docstring: None,
1896 }],
1897 classes: vec![],
1898 imports: vec![],
1899 };
1900 let decisions = analyzer.analyze_program(&program);
1901 if let Some(decision) = decisions.get("recursive") {
1903 assert!(
1904 !decision.should_inline || matches!(decision.reason, InliningReason::Recursive)
1905 );
1906 }
1907 }
1908
1909 #[test]
1911 fn test_side_effect_sort() {
1912 let expr = HirExpr::MethodCall {
1913 object: Box::new(HirExpr::Var("list".to_string())),
1914 method: "sort".to_string(),
1915 args: vec![],
1916 kwargs: vec![],
1917 };
1918 assert!(expr_has_side_effects_inner(&expr));
1919 }
1920
1921 #[test]
1922 fn test_side_effect_reverse() {
1923 let expr = HirExpr::MethodCall {
1924 object: Box::new(HirExpr::Var("list".to_string())),
1925 method: "reverse".to_string(),
1926 args: vec![],
1927 kwargs: vec![],
1928 };
1929 assert!(expr_has_side_effects_inner(&expr));
1930 }
1931
1932 #[test]
1933 fn test_side_effect_dict_update() {
1934 let expr = HirExpr::MethodCall {
1935 object: Box::new(HirExpr::Var("dict".to_string())),
1936 method: "update".to_string(),
1937 args: vec![HirExpr::Dict(vec![])],
1938 kwargs: vec![],
1939 };
1940 assert!(expr_has_side_effects_inner(&expr));
1941 }
1942
1943 #[test]
1944 fn test_pure_expr_get() {
1945 let expr = HirExpr::MethodCall {
1946 object: Box::new(HirExpr::Var("dict".to_string())),
1947 method: "get".to_string(),
1948 args: vec![HirExpr::Literal(Literal::String("key".to_string()))],
1949 kwargs: vec![],
1950 };
1951 assert!(!expr_has_side_effects_inner(&expr));
1953 }
1954
1955 #[test]
1957 fn test_expr_size_set() {
1958 let expr = HirExpr::Set(vec![
1959 HirExpr::Literal(Literal::Int(1)),
1960 HirExpr::Literal(Literal::Int(2)),
1961 ]);
1962 let size = calculate_expr_size_inner(&expr);
1963 assert!(size > 0);
1964 }
1965
1966 #[test]
1967 fn test_expr_size_tuple() {
1968 let expr = HirExpr::Tuple(vec![
1969 HirExpr::Literal(Literal::Int(1)),
1970 HirExpr::Literal(Literal::String("a".to_string())),
1971 ]);
1972 let size = calculate_expr_size_inner(&expr);
1973 assert!(size >= 2);
1974 }
1975
1976 #[test]
1977 fn test_expr_size_binary_complex() {
1978 let expr = HirExpr::Binary {
1979 left: Box::new(HirExpr::Binary {
1980 left: Box::new(HirExpr::Var("a".to_string())),
1981 op: BinOp::Add,
1982 right: Box::new(HirExpr::Var("b".to_string())),
1983 }),
1984 op: BinOp::Mul,
1985 right: Box::new(HirExpr::Var("c".to_string())),
1986 };
1987 let size = calculate_expr_size_inner(&expr);
1988 assert!(size >= 3);
1989 }
1990
1991 #[test]
1992 fn test_expr_size_call_with_args() {
1993 let expr = HirExpr::Call {
1994 func: "func".to_string(),
1995 args: vec![
1996 HirExpr::Literal(Literal::Int(1)),
1997 HirExpr::Literal(Literal::Int(2)),
1998 ],
1999 kwargs: vec![],
2000 };
2001 let size = calculate_expr_size_inner(&expr);
2002 assert!(size >= 2);
2003 }
2004
2005 #[test]
2006 fn test_expr_size_method_with_args() {
2007 let expr = HirExpr::MethodCall {
2008 object: Box::new(HirExpr::Var("obj".to_string())),
2009 method: "method".to_string(),
2010 args: vec![HirExpr::Literal(Literal::Int(1))],
2011 kwargs: vec![],
2012 };
2013 let size = calculate_expr_size_inner(&expr);
2014 assert!(size >= 1);
2015 }
2016
2017 #[test]
2019 fn test_contains_loops_nested_for_in_while() {
2020 let body = vec![HirStmt::While {
2021 condition: HirExpr::Literal(Literal::Bool(true)),
2022 body: vec![HirStmt::For {
2023 target: AssignTarget::Symbol("i".to_string()),
2024 iter: HirExpr::Call {
2025 func: "range".to_string(),
2026 args: vec![HirExpr::Literal(Literal::Int(10))],
2027 kwargs: vec![],
2028 },
2029 body: vec![],
2030 }],
2031 }];
2032 assert!(contains_loops_inner(&body));
2033 }
2034
2035 #[test]
2036 fn test_contains_loops_in_while_body() {
2037 let body = vec![HirStmt::While {
2038 condition: HirExpr::Literal(Literal::Bool(true)),
2039 body: vec![HirStmt::While {
2040 condition: HirExpr::Literal(Literal::Bool(false)),
2041 body: vec![],
2042 }],
2043 }];
2044 assert!(contains_loops_inner(&body));
2045 }
2046
2047 #[test]
2048 fn test_contains_loops_in_if_then() {
2049 let body = vec![HirStmt::If {
2050 condition: HirExpr::Literal(Literal::Bool(true)),
2051 then_body: vec![HirStmt::While {
2052 condition: HirExpr::Literal(Literal::Bool(true)),
2053 body: vec![HirStmt::Break { label: None }],
2054 }],
2055 else_body: None,
2056 }];
2057 assert!(contains_loops_inner(&body));
2058 }
2059
2060 #[test]
2061 fn test_contains_loops_in_if_else() {
2062 let body = vec![HirStmt::If {
2063 condition: HirExpr::Literal(Literal::Bool(true)),
2064 then_body: vec![],
2065 else_body: Some(vec![HirStmt::For {
2066 target: AssignTarget::Symbol("i".to_string()),
2067 iter: HirExpr::List(vec![]),
2068 body: vec![],
2069 }]),
2070 }];
2071 assert!(contains_loops_inner(&body));
2072 }
2073
2074 #[test]
2076 fn test_count_returns_multiple() {
2077 let body = vec![HirStmt::If {
2078 condition: HirExpr::Literal(Literal::Bool(true)),
2079 then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
2080 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
2081 2,
2082 ))))]),
2083 }];
2084 let count = count_returns_inner(&body);
2085 assert_eq!(count, 2);
2086 }
2087
2088 #[test]
2089 fn test_count_returns_in_while() {
2090 let body = vec![HirStmt::While {
2091 condition: HirExpr::Literal(Literal::Bool(true)),
2092 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
2093 }];
2094 let count = count_returns_inner(&body);
2095 assert_eq!(count, 1);
2096 }
2097
2098 #[test]
2099 fn test_count_returns_in_loop() {
2100 let body = vec![HirStmt::For {
2101 target: AssignTarget::Symbol("i".to_string()),
2102 iter: HirExpr::List(vec![]),
2103 body: vec![HirStmt::Return(Some(HirExpr::Var("i".to_string())))],
2104 }];
2105 let count = count_returns_inner(&body);
2106 assert_eq!(count, 1);
2107 }
2108
2109 #[test]
2111 fn test_inlining_decision_too_large() {
2112 let decision = InliningDecision {
2113 should_inline: false,
2114 reason: InliningReason::TooLarge,
2115 cost_benefit: 0.0,
2116 };
2117 assert!(!decision.should_inline);
2118 assert!(matches!(decision.reason, InliningReason::TooLarge));
2119 }
2120
2121 #[test]
2122 fn test_inlining_decision_contains_loops() {
2123 let decision = InliningDecision {
2124 should_inline: false,
2125 reason: InliningReason::ContainsLoops,
2126 cost_benefit: 0.0,
2127 };
2128 assert!(!decision.should_inline);
2129 assert!(matches!(decision.reason, InliningReason::ContainsLoops));
2130 }
2131
2132 #[test]
2133 fn test_inlining_decision_cost_too_high() {
2134 let decision = InliningDecision {
2135 should_inline: false,
2136 reason: InliningReason::CostTooHigh,
2137 cost_benefit: -0.5,
2138 };
2139 assert!(!decision.should_inline);
2140 assert!(decision.cost_benefit < 0.0);
2141 }
2142
2143 #[test]
2145 fn test_apply_inlining_no_changes() {
2146 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2147 let program = HirProgram {
2148 functions: vec![create_simple_function("main", 5)],
2149 classes: vec![],
2150 imports: vec![],
2151 };
2152 let decisions = std::collections::HashMap::new();
2153 let result = analyzer.apply_inlining(program.clone(), &decisions);
2154 assert_eq!(result.functions.len(), program.functions.len());
2155 }
2156
2157 #[test]
2159 fn test_transform_expr_literal() {
2160 let expr = HirExpr::Literal(Literal::Int(42));
2161 let result = transform_expr_for_inlining_inner(&expr, &[]);
2162 assert!(matches!(result, HirExpr::Literal(Literal::Int(42))));
2163 }
2164
2165 #[test]
2166 fn test_transform_expr_var_no_param_match() {
2167 let expr = HirExpr::Var("x".to_string());
2168 let params = vec![HirParam::new("y".to_string(), Type::Int)];
2169 let result = transform_expr_for_inlining_inner(&expr, ¶ms);
2170 assert!(matches!(result, HirExpr::Var(ref name) if name == "x"));
2171 }
2172
2173 #[test]
2174 fn test_transform_expr_binary() {
2175 let expr = HirExpr::Binary {
2176 left: Box::new(HirExpr::Literal(Literal::Int(1))),
2177 op: BinOp::Add,
2178 right: Box::new(HirExpr::Literal(Literal::Int(2))),
2179 };
2180 let result = transform_expr_for_inlining_inner(&expr, &[]);
2181 assert!(matches!(result, HirExpr::Binary { .. }));
2182 }
2183
2184 #[test]
2185 fn test_transform_expr_call() {
2186 let expr = HirExpr::Call {
2187 func: "foo".to_string(),
2188 args: vec![HirExpr::Literal(Literal::Int(1))],
2189 kwargs: vec![],
2190 };
2191 let result = transform_expr_for_inlining_inner(&expr, &[]);
2192 assert!(matches!(result, HirExpr::Call { .. }));
2193 }
2194
2195 #[test]
2197 fn test_extract_calls_from_nested_call() {
2198 let expr = HirExpr::Call {
2199 func: "outer".to_string(),
2200 args: vec![HirExpr::Call {
2201 func: "inner".to_string(),
2202 args: vec![],
2203 kwargs: vec![],
2204 }],
2205 kwargs: vec![],
2206 };
2207 let mut calls = std::collections::HashSet::new();
2208 extract_calls_from_expr_inner(&expr, &mut calls);
2209 assert!(calls.contains("outer"));
2210 assert!(calls.contains("inner"));
2211 }
2212
2213 #[test]
2214 fn test_extract_calls_from_method_with_call_arg() {
2215 let expr = HirExpr::MethodCall {
2216 object: Box::new(HirExpr::Var("obj".to_string())),
2217 method: "process".to_string(),
2218 args: vec![HirExpr::Call {
2219 func: "get_value".to_string(),
2220 args: vec![],
2221 kwargs: vec![],
2222 }],
2223 kwargs: vec![],
2224 };
2225 let mut calls = std::collections::HashSet::new();
2226 extract_calls_from_expr_inner(&expr, &mut calls);
2227 assert!(calls.contains("get_value"));
2228 }
2229
2230 #[test]
2231 fn test_extract_calls_from_binary_with_calls() {
2232 let expr = HirExpr::Binary {
2233 left: Box::new(HirExpr::Call {
2234 func: "left_fn".to_string(),
2235 args: vec![],
2236 kwargs: vec![],
2237 }),
2238 op: BinOp::Add,
2239 right: Box::new(HirExpr::Call {
2240 func: "right_fn".to_string(),
2241 args: vec![],
2242 kwargs: vec![],
2243 }),
2244 };
2245 let mut calls = std::collections::HashSet::new();
2246 extract_calls_from_expr_inner(&expr, &mut calls);
2247 assert!(calls.contains("left_fn"));
2248 assert!(calls.contains("right_fn"));
2249 }
2250
2251 #[test]
2252 fn test_extract_calls_from_list_with_call() {
2253 let expr = HirExpr::List(vec![HirExpr::Call {
2254 func: "get_item".to_string(),
2255 args: vec![],
2256 kwargs: vec![],
2257 }]);
2258 let mut calls = std::collections::HashSet::new();
2259 extract_calls_from_expr_inner(&expr, &mut calls);
2260 assert!(calls.contains("get_item"));
2261 }
2262
2263 #[test]
2265 fn test_stmt_size_assign() {
2266 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2267 let stmt = HirStmt::Assign {
2268 target: AssignTarget::Symbol("x".to_string()),
2269 value: HirExpr::Literal(Literal::Int(1)),
2270 type_annotation: None,
2271 };
2272 let size = analyzer.calculate_stmt_size(&stmt);
2273 assert!(size > 0);
2274 }
2275
2276 #[test]
2277 fn test_stmt_size_if() {
2278 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2279 let stmt = HirStmt::If {
2280 condition: HirExpr::Literal(Literal::Bool(true)),
2281 then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
2282 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
2283 2,
2284 ))))]),
2285 };
2286 let size = analyzer.calculate_stmt_size(&stmt);
2287 assert!(size >= 3);
2288 }
2289
2290 #[test]
2291 fn test_stmt_size_while() {
2292 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2293 let stmt = HirStmt::While {
2294 condition: HirExpr::Literal(Literal::Bool(true)),
2295 body: vec![HirStmt::Break { label: None }],
2296 };
2297 let size = analyzer.calculate_stmt_size(&stmt);
2298 assert!(size >= 2);
2299 }
2300
2301 #[test]
2302 fn test_stmt_size_for() {
2303 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2304 let stmt = HirStmt::For {
2305 target: AssignTarget::Symbol("i".to_string()),
2306 iter: HirExpr::List(vec![]),
2307 body: vec![],
2308 };
2309 let size = analyzer.calculate_stmt_size(&stmt);
2310 assert!(size >= 1);
2311 }
2312
2313 #[test]
2314 fn test_stmt_size_try() {
2315 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2316 let stmt = HirStmt::Try {
2317 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
2318 handlers: vec![],
2319 orelse: None,
2320 finalbody: None,
2321 };
2322 let size = analyzer.calculate_stmt_size(&stmt);
2323 assert!(size >= 1);
2324 }
2325
2326 #[test]
2328 fn test_side_effect_in_assign() {
2329 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2330 let stmt = HirStmt::Assign {
2331 target: AssignTarget::Symbol("x".to_string()),
2332 value: HirExpr::Call {
2333 func: "print".to_string(),
2334 args: vec![],
2335 kwargs: vec![],
2336 },
2337 type_annotation: None,
2338 };
2339 assert!(analyzer.stmt_has_side_effects(&stmt));
2340 }
2341
2342 #[test]
2343 fn test_side_effect_in_raise() {
2344 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2345 let stmt = HirStmt::Raise {
2346 exception: Some(HirExpr::Call {
2347 func: "ValueError".to_string(),
2348 args: vec![HirExpr::Literal(Literal::String("error".to_string()))],
2349 kwargs: vec![],
2350 }),
2351 cause: None,
2352 };
2353 assert!(analyzer.stmt_has_side_effects(&stmt));
2354 }
2355
2356 #[test]
2358 fn test_config_no_inline_single_use() {
2359 let config = InliningConfig {
2360 inline_single_use: false,
2361 ..Default::default()
2362 };
2363 assert!(!config.inline_single_use);
2364 }
2365
2366 #[test]
2367 fn test_config_no_inline_trivial() {
2368 let config = InliningConfig {
2369 inline_trivial: false,
2370 ..Default::default()
2371 };
2372 assert!(!config.inline_trivial);
2373 }
2374
2375 #[test]
2376 fn test_config_inline_loops() {
2377 let config = InliningConfig {
2378 inline_loops: true,
2379 ..Default::default()
2380 };
2381 assert!(config.inline_loops);
2382 }
2383
2384 #[test]
2385 fn test_config_high_cost_threshold() {
2386 let config = InliningConfig {
2387 cost_threshold: 10.0,
2388 ..Default::default()
2389 };
2390 assert_eq!(config.cost_threshold, 10.0);
2391 }
2392
2393 #[test]
2395 fn test_function_metrics_new() {
2396 let metrics = FunctionMetrics {
2397 size: 10,
2398 _param_count: 2,
2399 _return_count: 1,
2400 has_loops: false,
2401 has_side_effects: false,
2402 is_trivial: false,
2403 call_count: 5,
2404 cost: 2.5,
2405 };
2406 assert_eq!(metrics.size, 10);
2407 assert_eq!(metrics.call_count, 5);
2408 assert_eq!(metrics.cost, 2.5);
2409 assert!(!metrics.has_loops);
2410 }
2411
2412 #[test]
2414 fn test_empty_function_body() {
2415 let func = HirFunction {
2416 name: "empty".to_string(),
2417 params: smallvec![],
2418 ret_type: Type::None,
2419 body: vec![],
2420 properties: FunctionProperties::default(),
2421 annotations: Default::default(),
2422 docstring: None,
2423 };
2424 let analyzer = InliningAnalyzer::new(InliningConfig::default());
2425 let size = analyzer.calculate_function_size(&func);
2426 assert_eq!(size, 0);
2427 }
2428
2429 #[test]
2431 fn test_mutual_recursion() {
2432 let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
2433 let program = HirProgram {
2434 functions: vec![
2435 HirFunction {
2436 name: "even".to_string(),
2437 params: smallvec![HirParam::new("n".to_string(), Type::Int)],
2438 ret_type: Type::Bool,
2439 body: vec![HirStmt::Return(Some(HirExpr::Call {
2440 func: "odd".to_string(),
2441 args: vec![HirExpr::Var("n".to_string())],
2442 kwargs: vec![],
2443 }))],
2444 properties: FunctionProperties::default(),
2445 annotations: Default::default(),
2446 docstring: None,
2447 },
2448 HirFunction {
2449 name: "odd".to_string(),
2450 params: smallvec![HirParam::new("n".to_string(), Type::Int)],
2451 ret_type: Type::Bool,
2452 body: vec![HirStmt::Return(Some(HirExpr::Call {
2453 func: "even".to_string(),
2454 args: vec![HirExpr::Var("n".to_string())],
2455 kwargs: vec![],
2456 }))],
2457 properties: FunctionProperties::default(),
2458 annotations: Default::default(),
2459 docstring: None,
2460 },
2461 ],
2462 classes: vec![],
2463 imports: vec![],
2464 };
2465 let decisions = analyzer.analyze_program(&program);
2466 assert!(!decisions.is_empty());
2468 }
2469}