use crate::hir::{HirExpr, HirFunction, HirProgram, HirStmt};
use std::collections::{HashMap, HashSet};
pub struct InliningAnalyzer {
config: InliningConfig,
call_graph: CallGraph,
function_metrics: HashMap<String, FunctionMetrics>,
}
#[derive(Debug, Clone)]
pub struct InliningConfig {
pub max_inline_size: usize,
pub max_inline_depth: usize,
pub inline_single_use: bool,
pub inline_trivial: bool,
pub cost_threshold: f64,
pub inline_loops: bool,
}
impl Default for InliningConfig {
fn default() -> Self {
Self {
max_inline_size: 20,
max_inline_depth: 3,
inline_single_use: true,
inline_trivial: true,
cost_threshold: 1.5,
inline_loops: false,
}
}
}
#[derive(Debug, Default)]
struct CallGraph {
calls: HashMap<String, HashSet<String>>,
called_by: HashMap<String, HashSet<String>>,
recursive: HashSet<String>,
}
#[derive(Debug, Clone)]
struct FunctionMetrics {
size: usize,
_param_count: usize,
_return_count: usize,
has_loops: bool,
has_side_effects: bool,
is_trivial: bool,
call_count: usize,
cost: f64,
}
#[derive(Debug, Clone)]
pub struct InliningDecision {
pub should_inline: bool,
pub reason: InliningReason,
pub cost_benefit: f64,
}
#[derive(Debug, Clone)]
pub enum InliningReason {
Trivial,
SingleUse,
SmallHotFunction,
EnablesOptimization,
TooLarge,
Recursive,
HasSideEffects,
ContainsLoops,
CostTooHigh,
}
impl InliningAnalyzer {
pub fn new(config: InliningConfig) -> Self {
Self {
config,
call_graph: CallGraph::default(),
function_metrics: HashMap::new(),
}
}
pub fn analyze_program(&mut self, program: &HirProgram) -> HashMap<String, InliningDecision> {
self.build_call_graph(program);
self.detect_recursion();
self.calculate_metrics(program);
self.make_decisions()
}
pub fn apply_inlining(
&self,
mut program: HirProgram,
decisions: &HashMap<String, InliningDecision>,
) -> HirProgram {
let function_map: HashMap<String, HirFunction> = program
.functions
.iter()
.map(|f| (f.name.clone(), f.clone()))
.collect();
let mut inlined_functions = HashSet::new();
for func_idx in 0..program.functions.len() {
let func = &mut program.functions[func_idx];
let mut modified_body = Vec::new();
for stmt in &func.body {
match self.try_inline_stmt(stmt, &function_map, decisions, 0) {
Some(inlined_stmts) => {
modified_body.extend(inlined_stmts);
if let HirStmt::Expr(HirExpr::Call { func: callee, .. }) = stmt {
if decisions
.get(callee)
.map(|d| d.should_inline)
.unwrap_or(false)
{
inlined_functions.insert(callee.clone());
}
}
}
None => modified_body.push(stmt.clone()),
}
}
func.body = modified_body;
}
if self.config.inline_single_use {
program.functions.retain(|f| {
!inlined_functions.contains(&f.name)
|| self
.function_metrics
.get(&f.name)
.map(|m| m.call_count > 1)
.unwrap_or(true)
});
}
program
}
fn build_call_graph(&mut self, program: &HirProgram) {
for func in &program.functions {
let calls = self.extract_calls_from_function(func);
self.call_graph
.calls
.insert(func.name.clone(), calls.clone());
for callee in calls {
self.call_graph
.called_by
.entry(callee)
.or_default()
.insert(func.name.clone());
}
}
}
fn extract_calls_from_function(&self, func: &HirFunction) -> HashSet<String> {
let mut calls = HashSet::new();
for stmt in &func.body {
self.extract_calls_from_stmt(stmt, &mut calls);
}
calls
}
fn extract_calls_from_stmt(&self, stmt: &HirStmt, calls: &mut HashSet<String>) {
match stmt {
HirStmt::Expr(expr) => self.extract_calls_from_expr(expr, calls),
HirStmt::Assign { value, .. } => self.extract_calls_from_expr(value, calls),
HirStmt::Return(Some(expr)) => self.extract_calls_from_expr(expr, calls),
HirStmt::If {
condition,
then_body,
else_body,
} => self.extract_calls_from_if(condition, then_body, else_body, calls),
HirStmt::While { condition, body }
| HirStmt::For {
iter: condition,
body,
..
} => self.extract_calls_from_loop(condition, body, calls),
_ => {}
}
}
fn extract_calls_from_if(
&self,
condition: &HirExpr,
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
calls: &mut HashSet<String>,
) {
self.extract_calls_from_expr(condition, calls);
self.extract_calls_from_body(then_body, calls);
if let Some(else_stmts) = else_body {
self.extract_calls_from_body(else_stmts, calls);
}
}
fn extract_calls_from_loop(
&self,
condition: &HirExpr,
body: &[HirStmt],
calls: &mut HashSet<String>,
) {
self.extract_calls_from_expr(condition, calls);
self.extract_calls_from_body(body, calls);
}
fn extract_calls_from_body(&self, body: &[HirStmt], calls: &mut HashSet<String>) {
for s in body {
self.extract_calls_from_stmt(s, calls);
}
}
fn extract_calls_from_expr(&self, expr: &HirExpr, calls: &mut HashSet<String>) {
extract_calls_from_expr_inner(expr, calls);
}
}
fn extract_calls_from_expr_inner(expr: &HirExpr, calls: &mut HashSet<String>) {
match expr {
HirExpr::Call { func, args, .. } => extract_from_call(func, args, calls),
HirExpr::Binary { left, right, .. } => extract_from_binary(left, right, calls),
HirExpr::Unary { operand, .. } => extract_calls_from_expr_inner(operand, calls),
HirExpr::List(items) | HirExpr::Tuple(items) => extract_from_items(items, calls),
HirExpr::Dict(pairs) => extract_from_dict(pairs, calls),
HirExpr::MethodCall { object, args, .. } => extract_from_method_call(object, args, calls),
HirExpr::Lambda { body, .. } => extract_calls_from_expr_inner(body, calls),
_ => {}
}
}
fn extract_from_call(func: &str, args: &[HirExpr], calls: &mut HashSet<String>) {
calls.insert(func.to_string());
for arg in args {
extract_calls_from_expr_inner(arg, calls);
}
}
fn extract_from_binary(left: &HirExpr, right: &HirExpr, calls: &mut HashSet<String>) {
extract_calls_from_expr_inner(left, calls);
extract_calls_from_expr_inner(right, calls);
}
fn extract_from_items(items: &[HirExpr], calls: &mut HashSet<String>) {
for item in items {
extract_calls_from_expr_inner(item, calls);
}
}
fn extract_from_dict(pairs: &[(HirExpr, HirExpr)], calls: &mut HashSet<String>) {
for (k, v) in pairs {
extract_calls_from_expr_inner(k, calls);
extract_calls_from_expr_inner(v, calls);
}
}
fn extract_from_method_call(object: &HirExpr, args: &[HirExpr], calls: &mut HashSet<String>) {
extract_calls_from_expr_inner(object, calls);
for arg in args {
extract_calls_from_expr_inner(arg, calls);
}
}
impl InliningAnalyzer {
fn detect_recursion(&mut self) {
for func_name in self.call_graph.calls.keys() {
let mut visited = HashSet::new();
let mut stack = HashSet::new();
if self.is_recursive_dfs(func_name, &mut visited, &mut stack) {
self.call_graph.recursive.insert(func_name.clone());
}
}
}
fn is_recursive_dfs(
&self,
func: &str,
visited: &mut HashSet<String>,
stack: &mut HashSet<String>,
) -> bool {
visited.insert(func.to_string());
stack.insert(func.to_string());
if let Some(callees) = self.call_graph.calls.get(func) {
for callee in callees {
if stack.contains(callee) {
return true; }
if !visited.contains(callee) && self.is_recursive_dfs(callee, visited, stack) {
return true;
}
}
}
stack.remove(func);
false
}
fn calculate_metrics(&mut self, program: &HirProgram) {
for func in &program.functions {
let size = self.calculate_function_size(func);
let has_loops = self.contains_loops(&func.body);
let has_side_effects = self.has_side_effects(func);
let is_trivial = self.is_trivial_function(func);
let return_count = self.count_returns(&func.body);
let call_count = self
.call_graph
.called_by
.get(&func.name)
.map(|callers| callers.len())
.unwrap_or(0);
let cost = self.estimate_cost(func, size, has_loops, has_side_effects);
let metrics = FunctionMetrics {
size,
_param_count: func.params.len(),
_return_count: return_count,
has_loops,
has_side_effects,
is_trivial,
call_count,
cost,
};
self.function_metrics.insert(func.name.clone(), metrics);
}
}
fn calculate_function_size(&self, func: &HirFunction) -> usize {
let mut size = 0;
for stmt in &func.body {
size += self.calculate_stmt_size(stmt);
}
size
}
fn calculate_stmt_size(&self, stmt: &HirStmt) -> usize {
match stmt {
HirStmt::Expr(expr) => self.calculate_expr_size(expr),
HirStmt::Assign { value, .. } => 1 + self.calculate_expr_size(value),
HirStmt::Return(Some(expr)) => 1 + self.calculate_expr_size(expr),
HirStmt::Return(None) => 1,
HirStmt::If {
condition,
then_body,
else_body,
} => self.calculate_if_size(condition, then_body, else_body),
HirStmt::While { condition, body }
| HirStmt::For {
iter: condition,
body,
..
} => self.calculate_loop_size(condition, body),
_ => 1,
}
}
fn calculate_if_size(
&self,
condition: &HirExpr,
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
) -> usize {
let mut size = 1 + self.calculate_expr_size(condition);
size += self.calculate_body_size(then_body);
if let Some(else_stmts) = else_body {
size += self.calculate_body_size(else_stmts);
}
size
}
fn calculate_loop_size(&self, condition: &HirExpr, body: &[HirStmt]) -> usize {
let mut size = 1 + self.calculate_expr_size(condition);
size += self.calculate_body_size(body);
size
}
fn calculate_body_size(&self, body: &[HirStmt]) -> usize {
body.iter().map(|s| self.calculate_stmt_size(s)).sum()
}
fn calculate_expr_size(&self, expr: &HirExpr) -> usize {
calculate_expr_size_inner(expr)
}
fn contains_loops(&self, body: &[HirStmt]) -> bool {
contains_loops_inner(body)
}
fn has_side_effects(&self, func: &HirFunction) -> bool {
if !func.properties.is_pure {
return true;
}
for stmt in &func.body {
if self.stmt_has_side_effects(stmt) {
return true;
}
}
false
}
fn stmt_has_side_effects(&self, stmt: &HirStmt) -> bool {
match stmt {
HirStmt::Expr(expr) => self.expr_has_side_effects(expr),
HirStmt::Assign { value, .. } => self.expr_has_side_effects(value),
HirStmt::Return(Some(expr)) => self.expr_has_side_effects(expr),
HirStmt::If {
condition,
then_body,
else_body,
} => self.if_has_side_effects(condition, then_body, else_body),
HirStmt::While { condition, body }
| HirStmt::For {
iter: condition,
body,
..
} => self.loop_has_side_effects(condition, body),
HirStmt::Raise { .. } => true,
_ => false,
}
}
fn if_has_side_effects(
&self,
condition: &HirExpr,
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
) -> bool {
self.expr_has_side_effects(condition)
|| self.body_has_side_effects(then_body)
|| else_body
.as_ref()
.map(|stmts| self.body_has_side_effects(stmts))
.unwrap_or(false)
}
fn loop_has_side_effects(&self, condition: &HirExpr, body: &[HirStmt]) -> bool {
self.expr_has_side_effects(condition) || self.body_has_side_effects(body)
}
fn body_has_side_effects(&self, body: &[HirStmt]) -> bool {
body.iter().any(|s| self.stmt_has_side_effects(s))
}
fn expr_has_side_effects(&self, expr: &HirExpr) -> bool {
expr_has_side_effects_inner(expr)
}
fn is_trivial_function(&self, func: &HirFunction) -> bool {
if func.body.len() == 1 {
matches!(func.body[0], HirStmt::Return(_))
} else {
false
}
}
fn count_returns(&self, body: &[HirStmt]) -> usize {
count_returns_inner(body)
}
fn estimate_cost(
&self,
func: &HirFunction,
size: usize,
has_loops: bool,
has_side_effects: bool,
) -> f64 {
let mut cost = size as f64;
if has_loops {
cost *= 10.0;
}
if has_side_effects {
cost *= 2.0;
}
let return_count = count_returns_inner(&func.body);
if return_count > 1 {
cost *= 1.0 + (return_count as f64 * 0.2);
}
cost += func.params.len() as f64 * 0.5;
cost
}
fn make_decisions(&self) -> HashMap<String, InliningDecision> {
let mut decisions = HashMap::new();
for (func_name, metrics) in &self.function_metrics {
let decision = self.decide_inlining(func_name, metrics);
decisions.insert(func_name.clone(), decision);
}
decisions
}
fn decide_inlining(&self, func_name: &str, metrics: &FunctionMetrics) -> InliningDecision {
if let Some(rejection) = self.check_inlining_rejections(func_name, metrics) {
return rejection;
}
if let Some(approval) = self.check_inlining_approvals(metrics) {
return approval;
}
self.decide_by_cost_benefit(metrics)
}
fn check_inlining_rejections(
&self,
func_name: &str,
metrics: &FunctionMetrics,
) -> Option<InliningDecision> {
if self.call_graph.recursive.contains(func_name) {
return Some(InliningDecision {
should_inline: false,
reason: InliningReason::Recursive,
cost_benefit: 0.0,
});
}
if metrics.size > self.config.max_inline_size {
return Some(InliningDecision {
should_inline: false,
reason: InliningReason::TooLarge,
cost_benefit: 0.0,
});
}
if metrics.has_loops && !self.config.inline_loops {
return Some(InliningDecision {
should_inline: false,
reason: InliningReason::ContainsLoops,
cost_benefit: 0.0,
});
}
if metrics.has_side_effects {
return Some(InliningDecision {
should_inline: false,
reason: InliningReason::HasSideEffects,
cost_benefit: 0.0,
});
}
None
}
fn check_inlining_approvals(&self, metrics: &FunctionMetrics) -> Option<InliningDecision> {
if self.config.inline_trivial && metrics.is_trivial {
return Some(InliningDecision {
should_inline: true,
reason: InliningReason::Trivial,
cost_benefit: 10.0,
});
}
if self.config.inline_single_use && metrics.call_count == 1 && !metrics.has_side_effects {
return Some(InliningDecision {
should_inline: true,
reason: InliningReason::SingleUse,
cost_benefit: 5.0,
});
}
None
}
fn decide_by_cost_benefit(&self, metrics: &FunctionMetrics) -> InliningDecision {
let call_overhead = 1.0;
let benefit = (call_overhead * metrics.call_count as f64) - metrics.cost;
let cost_benefit = benefit / metrics.cost;
if cost_benefit >= self.config.cost_threshold {
InliningDecision {
should_inline: true,
reason: InliningReason::SmallHotFunction,
cost_benefit,
}
} else {
InliningDecision {
should_inline: false,
reason: InliningReason::CostTooHigh,
cost_benefit,
}
}
}
fn try_inline_stmt(
&self,
stmt: &HirStmt,
function_map: &HashMap<String, HirFunction>,
decisions: &HashMap<String, InliningDecision>,
depth: usize,
) -> Option<Vec<HirStmt>> {
if depth >= self.config.max_inline_depth {
return None;
}
match stmt {
HirStmt::Expr(HirExpr::Call { func, args, .. }) => {
self.try_inline_expr_call(func, args, function_map, decisions, depth)
}
HirStmt::Assign { target, value, .. } => {
self.try_inline_assign_call(target, value, function_map, decisions, depth)
}
_ => None,
}
}
fn try_inline_expr_call(
&self,
func: &str,
args: &[HirExpr],
function_map: &HashMap<String, HirFunction>,
decisions: &HashMap<String, InliningDecision>,
depth: usize,
) -> Option<Vec<HirStmt>> {
let decision = decisions.get(func)?;
if !decision.should_inline {
return None;
}
let target_func = function_map.get(func)?;
Some(self.inline_function_call(target_func, args, function_map, decisions, depth))
}
fn try_inline_assign_call(
&self,
target: &crate::hir::AssignTarget,
value: &HirExpr,
function_map: &HashMap<String, HirFunction>,
decisions: &HashMap<String, InliningDecision>,
depth: usize,
) -> Option<Vec<HirStmt>> {
if let HirExpr::Call { func, args, .. } = value {
let decision = decisions.get(func)?;
if !decision.should_inline {
return None;
}
let target_func = function_map.get(func)?;
let inlined =
self.inline_function_call(target_func, args, function_map, decisions, depth);
if inlined.is_empty() {
return None;
}
let mut result = inlined;
self.replace_return_with_assign(&mut result, target);
Some(result)
} else {
None
}
}
fn replace_return_with_assign(
&self,
statements: &mut [HirStmt],
target: &crate::hir::AssignTarget,
) {
if let Some(last) = statements.last_mut() {
if let HirStmt::Return(Some(expr)) = last {
*last = HirStmt::Assign {
target: target.clone(),
value: expr.clone(),
type_annotation: None,
};
}
}
}
fn inline_function_call(
&self,
func: &HirFunction,
args: &[HirExpr],
function_map: &HashMap<String, HirFunction>,
decisions: &HashMap<String, InliningDecision>,
depth: usize,
) -> Vec<HirStmt> {
let mut inlined_body = Vec::new();
for (i, param) in func.params.iter().enumerate() {
if let Some(arg) = args.get(i) {
inlined_body.push(HirStmt::Assign {
target: crate::hir::AssignTarget::Symbol(format!("_inline_{}", param.name)),
value: arg.clone(),
type_annotation: None,
});
}
}
for stmt in &func.body {
let transformed = self.transform_stmt_for_inlining(stmt, &func.params, depth + 1);
if let Some(inlined) =
self.try_inline_stmt(&transformed, function_map, decisions, depth + 1)
{
inlined_body.extend(inlined);
} else {
inlined_body.push(transformed);
}
}
inlined_body
}
fn transform_stmt_for_inlining(
&self,
stmt: &HirStmt,
params: &[crate::hir::HirParam],
_depth: usize,
) -> HirStmt {
match stmt {
HirStmt::Expr(expr) => HirStmt::Expr(transform_expr_for_inlining_inner(expr, params)),
HirStmt::Assign {
target,
value,
type_annotation,
} => HirStmt::Assign {
target: self.transform_assign_target_for_inlining(target, params),
value: transform_expr_for_inlining_inner(value, params),
type_annotation: type_annotation.clone(),
},
HirStmt::Return(Some(expr)) => {
HirStmt::Return(Some(transform_expr_for_inlining_inner(expr, params)))
}
HirStmt::If {
condition,
then_body,
else_body,
} => HirStmt::If {
condition: transform_expr_for_inlining_inner(condition, params),
then_body: then_body
.iter()
.map(|s| self.transform_stmt_for_inlining(s, params, _depth))
.collect(),
else_body: else_body.as_ref().map(|stmts| {
stmts
.iter()
.map(|s| self.transform_stmt_for_inlining(s, params, _depth))
.collect()
}),
},
_ => stmt.clone(),
}
}
#[allow(dead_code)]
fn transform_expr_for_inlining(
&self,
expr: &HirExpr,
params: &[crate::hir::HirParam],
) -> HirExpr {
transform_expr_for_inlining_inner(expr, params)
}
fn transform_assign_target_for_inlining(
&self,
target: &crate::hir::AssignTarget,
params: &[crate::hir::HirParam],
) -> crate::hir::AssignTarget {
match target {
crate::hir::AssignTarget::Symbol(name) => {
if params.iter().any(|p| &p.name == name) {
crate::hir::AssignTarget::Symbol(format!("_inline_{}", name))
} else {
target.clone()
}
}
_ => target.clone(),
}
}
}
fn calculate_expr_size_inner(expr: &HirExpr) -> usize {
match expr {
HirExpr::Literal(_) | HirExpr::Var(_) => 1,
HirExpr::Binary { left, right, .. } => {
1 + calculate_expr_size_inner(left) + calculate_expr_size_inner(right)
}
HirExpr::Unary { operand, .. } => 1 + calculate_expr_size_inner(operand),
HirExpr::Call { args, .. } => 1 + args.iter().map(calculate_expr_size_inner).sum::<usize>(),
HirExpr::List(items) | HirExpr::Tuple(items) => {
1 + items.iter().map(calculate_expr_size_inner).sum::<usize>()
}
HirExpr::Dict(pairs) => {
1 + pairs
.iter()
.map(|(k, v)| calculate_expr_size_inner(k) + calculate_expr_size_inner(v))
.sum::<usize>()
}
_ => 1,
}
}
fn contains_loops_inner(body: &[HirStmt]) -> bool {
for stmt in body {
match stmt {
HirStmt::While { .. } | HirStmt::For { .. } => return true,
HirStmt::If {
then_body,
else_body,
..
} => {
if contains_loops_inner(then_body) {
return true;
}
if let Some(else_stmts) = else_body {
if contains_loops_inner(else_stmts) {
return true;
}
}
}
_ => {}
}
}
false
}
fn expr_has_side_effects_inner(expr: &HirExpr) -> bool {
match expr {
HirExpr::Call { func, args, .. } => call_has_side_effects(func, args),
HirExpr::MethodCall { object, method, .. } => {
method_has_side_effects(method) || expr_has_side_effects_inner(object)
}
HirExpr::Binary { left, right, .. } => binary_has_side_effects(left, right),
HirExpr::Unary { operand, .. } => expr_has_side_effects_inner(operand),
HirExpr::List(items) | HirExpr::Tuple(items) => collection_has_side_effects(items),
HirExpr::Dict(pairs) => dict_has_side_effects(pairs),
_ => false,
}
}
fn call_has_side_effects(func: &str, args: &[HirExpr]) -> bool {
let pure_functions = ["len", "abs", "min", "max", "sum", "str", "int", "float"];
!pure_functions.contains(&func) || args.iter().any(expr_has_side_effects_inner)
}
fn method_has_side_effects(method: &str) -> bool {
let mutating_methods = [
"append", "extend", "remove", "pop", "clear", "sort", "reverse", "insert", "update",
"write",
];
mutating_methods.contains(&method)
}
fn binary_has_side_effects(left: &HirExpr, right: &HirExpr) -> bool {
expr_has_side_effects_inner(left) || expr_has_side_effects_inner(right)
}
fn collection_has_side_effects(items: &[HirExpr]) -> bool {
items.iter().any(expr_has_side_effects_inner)
}
fn dict_has_side_effects(pairs: &[(HirExpr, HirExpr)]) -> bool {
pairs
.iter()
.any(|(k, v)| expr_has_side_effects_inner(k) || expr_has_side_effects_inner(v))
}
fn count_returns_inner(body: &[HirStmt]) -> usize {
let mut count = 0;
for stmt in body {
match stmt {
HirStmt::Return(_) => count += 1,
HirStmt::If {
then_body,
else_body,
..
} => {
count += count_returns_inner(then_body);
if let Some(else_stmts) = else_body {
count += count_returns_inner(else_stmts);
}
}
HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
count += count_returns_inner(body);
}
_ => {}
}
}
count
}
fn transform_expr_for_inlining_inner(expr: &HirExpr, params: &[crate::hir::HirParam]) -> HirExpr {
match expr {
HirExpr::Var(name) => {
if params.iter().any(|p| &p.name == name) {
HirExpr::Var(format!("_inline_{}", name))
} else {
expr.clone()
}
}
HirExpr::Binary { left, right, op } => HirExpr::Binary {
left: Box::new(transform_expr_for_inlining_inner(left, params)),
right: Box::new(transform_expr_for_inlining_inner(right, params)),
op: *op,
},
HirExpr::Unary { operand, op } => HirExpr::Unary {
operand: Box::new(transform_expr_for_inlining_inner(operand, params)),
op: *op,
},
HirExpr::Call { func, args, .. } => HirExpr::Call {
func: func.clone(),
args: args
.iter()
.map(|a| transform_expr_for_inlining_inner(a, params))
.collect(),
kwargs: vec![],
},
_ => expr.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::*;
use smallvec::smallvec;
fn create_simple_function(name: &str, size: usize) -> HirFunction {
let mut body = Vec::new();
for i in 0..size {
body.push(HirStmt::Assign {
target: AssignTarget::Symbol(Symbol::from(format!("x{}", i).as_str())),
value: HirExpr::Literal(Literal::Int(i as i64)),
type_annotation: None,
});
}
body.push(HirStmt::Return(Some(HirExpr::Var("x0".to_string()))));
HirFunction {
name: name.to_string(),
params: smallvec![],
ret_type: Type::Int,
body,
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
}
}
#[test]
fn test_trivial_function_detection() {
let func = HirFunction {
name: "identity".to_string(),
params: smallvec![HirParam::new("x".to_string(), Type::Int)],
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let analyzer = InliningAnalyzer::new(InliningConfig::default());
assert!(analyzer.is_trivial_function(&func));
}
#[test]
fn test_function_size_calculation() {
let func = create_simple_function("test", 5);
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let size = analyzer.calculate_function_size(&func);
assert_eq!(size, 12); }
#[test]
fn test_loop_detection() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Break { label: None }],
}];
let _analyzer = InliningAnalyzer::new(InliningConfig::default());
assert!(contains_loops_inner(&body));
}
#[test]
fn test_side_effect_detection() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "append".to_string(),
args: vec![HirExpr::Literal(Literal::Int(42))],
kwargs: vec![],
};
let _analyzer = InliningAnalyzer::new(InliningConfig::default());
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_inlining_config_default() {
let config = InliningConfig::default();
assert_eq!(config.max_inline_size, 20);
assert_eq!(config.max_inline_depth, 3);
assert!(config.inline_single_use);
assert!(config.inline_trivial);
}
#[test]
fn test_inlining_config_custom() {
let config = InliningConfig {
max_inline_size: 50,
max_inline_depth: 5,
inline_single_use: false,
inline_trivial: false,
cost_threshold: 2.0,
inline_loops: true,
};
assert_eq!(config.max_inline_size, 50);
assert_eq!(config.max_inline_depth, 5);
assert!(!config.inline_single_use);
assert!(!config.inline_trivial);
assert_eq!(config.cost_threshold, 2.0);
assert!(config.inline_loops);
}
#[test]
fn test_inlining_reason_variants() {
let reasons = [
InliningReason::Trivial,
InliningReason::SingleUse,
InliningReason::SmallHotFunction,
InliningReason::EnablesOptimization,
InliningReason::TooLarge,
InliningReason::Recursive,
InliningReason::HasSideEffects,
InliningReason::ContainsLoops,
];
for reason in &reasons {
let _ = format!("{:?}", reason);
}
}
#[test]
fn test_inlining_decision_creation() {
let decision = InliningDecision {
should_inline: true,
reason: InliningReason::Trivial,
cost_benefit: 1.5,
};
assert!(decision.should_inline);
assert_eq!(decision.cost_benefit, 1.5);
}
#[test]
fn test_analyzer_new() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
assert!(std::mem::size_of_val(&analyzer) > 0);
}
#[test]
fn test_non_trivial_function() {
let func = HirFunction {
name: "complex".to_string(),
params: smallvec![HirParam::new("x".to_string(), Type::Int)],
ret_type: Type::Int,
body: vec![
HirStmt::Assign {
target: AssignTarget::Symbol("y".to_string()),
value: HirExpr::Var("x".to_string()),
type_annotation: None,
},
HirStmt::Return(Some(HirExpr::Var("y".to_string()))),
],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let analyzer = InliningAnalyzer::new(InliningConfig::default());
assert!(!analyzer.is_trivial_function(&func));
}
#[test]
fn test_for_loop_detection() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Call {
func: "range".to_string(),
args: vec![HirExpr::Literal(Literal::Int(10))],
kwargs: vec![],
},
body: vec![],
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_if_no_loop() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: None,
}];
assert!(!contains_loops_inner(&body));
}
#[test]
fn test_print_side_effect() {
let expr = HirExpr::Call {
func: "print".to_string(),
args: vec![HirExpr::Literal(Literal::String("hello".to_string()))],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_append_side_effect() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "append".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_pure_function_no_side_effect() {
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Literal(Literal::Int(1))),
op: BinOp::Add,
right: Box::new(HirExpr::Literal(Literal::Int(2))),
};
assert!(!expr_has_side_effects_inner(&expr));
}
#[test]
fn test_function_size_empty() {
let func = HirFunction {
name: "empty".to_string(),
params: smallvec![],
ret_type: Type::None,
body: vec![],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let analyzer = InliningAnalyzer::new(InliningConfig::default());
assert_eq!(analyzer.calculate_function_size(&func), 0);
}
#[test]
fn test_call_graph_default() {
let cg = CallGraph::default();
assert!(cg.calls.is_empty());
assert!(cg.called_by.is_empty());
assert!(cg.recursive.is_empty());
}
#[test]
fn test_expr_size_literal() {
let expr = HirExpr::Literal(Literal::Int(42));
assert_eq!(calculate_expr_size_inner(&expr), 1);
}
#[test]
fn test_expr_size_var() {
let expr = HirExpr::Var("x".to_string());
assert_eq!(calculate_expr_size_inner(&expr), 1);
}
#[test]
fn test_expr_size_binary() {
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Literal(Literal::Int(1))),
op: BinOp::Add,
right: Box::new(HirExpr::Literal(Literal::Int(2))),
};
assert_eq!(calculate_expr_size_inner(&expr), 3);
}
#[test]
fn test_expr_size_list() {
let expr = HirExpr::List(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
]);
assert_eq!(calculate_expr_size_inner(&expr), 3);
}
#[test]
fn test_stmt_size_return_none() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Return(None);
assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
}
#[test]
fn test_stmt_size_return_some() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))));
assert_eq!(analyzer.calculate_stmt_size(&stmt), 2);
}
#[test]
fn test_stmt_size_expr() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Expr(HirExpr::Literal(Literal::Int(42)));
assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
}
#[test]
fn test_stmt_size_pass() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Pass;
assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
}
#[test]
fn test_stmt_size_break() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Break { label: None };
assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
}
#[test]
fn test_stmt_size_continue() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Continue { label: None };
assert_eq!(analyzer.calculate_stmt_size(&stmt), 1);
}
#[test]
fn test_extract_calls_from_call_expr() {
let mut calls = HashSet::new();
let expr = HirExpr::Call {
func: "foo".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("foo"));
}
#[test]
fn test_extract_calls_from_binary_expr() {
let mut calls = HashSet::new();
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Call {
func: "left_fn".to_string(),
args: vec![],
kwargs: vec![],
}),
op: BinOp::Add,
right: Box::new(HirExpr::Call {
func: "right_fn".to_string(),
args: vec![],
kwargs: vec![],
}),
};
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("left_fn"));
assert!(calls.contains("right_fn"));
}
#[test]
fn test_extract_calls_from_unary_expr() {
let mut calls = HashSet::new();
let expr = HirExpr::Unary {
op: crate::hir::UnaryOp::Neg,
operand: Box::new(HirExpr::Call {
func: "inner".to_string(),
args: vec![],
kwargs: vec![],
}),
};
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("inner"));
}
#[test]
fn test_extract_calls_from_list() {
let mut calls = HashSet::new();
let expr = HirExpr::List(vec![
HirExpr::Call {
func: "fn1".to_string(),
args: vec![],
kwargs: vec![],
},
HirExpr::Call {
func: "fn2".to_string(),
args: vec![],
kwargs: vec![],
},
]);
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("fn1"));
assert!(calls.contains("fn2"));
}
#[test]
fn test_extract_calls_from_tuple() {
let mut calls = HashSet::new();
let expr = HirExpr::Tuple(vec![HirExpr::Call {
func: "tuple_fn".to_string(),
args: vec![],
kwargs: vec![],
}]);
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("tuple_fn"));
}
#[test]
fn test_extract_calls_from_dict() {
let mut calls = HashSet::new();
let expr = HirExpr::Dict(vec![(
HirExpr::Call {
func: "key_fn".to_string(),
args: vec![],
kwargs: vec![],
},
HirExpr::Call {
func: "val_fn".to_string(),
args: vec![],
kwargs: vec![],
},
)]);
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("key_fn"));
assert!(calls.contains("val_fn"));
}
#[test]
fn test_extract_calls_from_method_call() {
let mut calls = HashSet::new();
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Call {
func: "get_obj".to_string(),
args: vec![],
kwargs: vec![],
}),
method: "method".to_string(),
args: vec![HirExpr::Call {
func: "get_arg".to_string(),
args: vec![],
kwargs: vec![],
}],
kwargs: vec![],
};
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("get_obj"));
assert!(calls.contains("get_arg"));
}
#[test]
fn test_extract_calls_from_lambda() {
let mut calls = HashSet::new();
let expr = HirExpr::Lambda {
params: vec!["x".to_string()],
body: Box::new(HirExpr::Call {
func: "lambda_call".to_string(),
args: vec![],
kwargs: vec![],
}),
};
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("lambda_call"));
}
#[test]
fn test_extract_calls_from_var() {
let mut calls = HashSet::new();
let expr = HirExpr::Var("x".to_string());
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.is_empty());
}
#[test]
fn test_extract_calls_from_literal() {
let mut calls = HashSet::new();
let expr = HirExpr::Literal(Literal::Int(42));
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.is_empty());
}
#[test]
fn test_analyzer_extract_calls_from_function() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let func = HirFunction {
name: "caller".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![
HirStmt::Expr(HirExpr::Call {
func: "callee1".to_string(),
args: vec![],
kwargs: vec![],
}),
HirStmt::Assign {
target: AssignTarget::Symbol("x".to_string()),
value: HirExpr::Call {
func: "callee2".to_string(),
args: vec![],
kwargs: vec![],
},
type_annotation: None,
},
HirStmt::Return(Some(HirExpr::Call {
func: "callee3".to_string(),
args: vec![],
kwargs: vec![],
})),
],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let calls = analyzer.extract_calls_from_function(&func);
assert!(calls.contains("callee1"));
assert!(calls.contains("callee2"));
assert!(calls.contains("callee3"));
}
#[test]
fn test_analyzer_extract_calls_from_if() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let func = HirFunction {
name: "if_caller".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::If {
condition: HirExpr::Call {
func: "cond_fn".to_string(),
args: vec![],
kwargs: vec![],
},
then_body: vec![HirStmt::Expr(HirExpr::Call {
func: "then_fn".to_string(),
args: vec![],
kwargs: vec![],
})],
else_body: Some(vec![HirStmt::Expr(HirExpr::Call {
func: "else_fn".to_string(),
args: vec![],
kwargs: vec![],
})]),
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let calls = analyzer.extract_calls_from_function(&func);
assert!(calls.contains("cond_fn"));
assert!(calls.contains("then_fn"));
assert!(calls.contains("else_fn"));
}
#[test]
fn test_analyzer_extract_calls_from_while() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let func = HirFunction {
name: "while_caller".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::While {
condition: HirExpr::Call {
func: "while_cond".to_string(),
args: vec![],
kwargs: vec![],
},
body: vec![HirStmt::Expr(HirExpr::Call {
func: "while_body".to_string(),
args: vec![],
kwargs: vec![],
})],
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let calls = analyzer.extract_calls_from_function(&func);
assert!(calls.contains("while_cond"));
assert!(calls.contains("while_body"));
}
#[test]
fn test_analyzer_extract_calls_from_for() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let func = HirFunction {
name: "for_caller".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Call {
func: "for_iter".to_string(),
args: vec![],
kwargs: vec![],
},
body: vec![HirStmt::Expr(HirExpr::Call {
func: "for_body".to_string(),
args: vec![],
kwargs: vec![],
})],
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let calls = analyzer.extract_calls_from_function(&func);
assert!(calls.contains("for_iter"));
assert!(calls.contains("for_body"));
}
#[test]
fn test_function_metrics_default() {
let metrics = FunctionMetrics {
size: 10,
_param_count: 2,
_return_count: 1,
has_loops: false,
has_side_effects: false,
is_trivial: true,
call_count: 3,
cost: 1.5,
};
assert_eq!(metrics.size, 10);
assert!(!metrics.has_loops);
assert!(!metrics.has_side_effects);
assert!(metrics.is_trivial);
assert_eq!(metrics.call_count, 3);
}
#[test]
fn test_expr_size_call() {
let expr = HirExpr::Call {
func: "fn".to_string(),
args: vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
],
kwargs: vec![],
};
let size = calculate_expr_size_inner(&expr);
assert_eq!(size, 3); }
#[test]
fn test_expr_size_method_call() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("obj".to_string())),
method: "method".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
let size = calculate_expr_size_inner(&expr);
assert_eq!(size, 1); }
#[test]
fn test_expr_size_dict() {
let expr = HirExpr::Dict(vec![
(
HirExpr::Literal(Literal::String("a".to_string())),
HirExpr::Literal(Literal::Int(1)),
),
(
HirExpr::Literal(Literal::String("b".to_string())),
HirExpr::Literal(Literal::Int(2)),
),
]);
let size = calculate_expr_size_inner(&expr);
assert!(size >= 5); }
#[test]
fn test_expr_size_lambda() {
let expr = HirExpr::Lambda {
params: vec!["x".to_string()],
body: Box::new(HirExpr::Binary {
left: Box::new(HirExpr::Var("x".to_string())),
op: BinOp::Add,
right: Box::new(HirExpr::Literal(Literal::Int(1))),
}),
};
let size = calculate_expr_size_inner(&expr);
assert_eq!(size, 1); }
#[test]
fn test_expr_size_unary() {
let expr = HirExpr::Unary {
op: crate::hir::UnaryOp::Neg,
operand: Box::new(HirExpr::Literal(Literal::Int(5))),
};
let size = calculate_expr_size_inner(&expr);
assert_eq!(size, 2); }
#[test]
fn test_contains_loops_with_nested_while() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![],
}],
else_body: None,
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_contains_loops_with_nested_for() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![],
}],
else_body: None,
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_side_effect_write() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("file".to_string())),
method: "write".to_string(),
args: vec![HirExpr::Literal(Literal::String("data".to_string()))],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_extend() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "extend".to_string(),
args: vec![HirExpr::List(vec![])],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_insert() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "insert".to_string(),
args: vec![
HirExpr::Literal(Literal::Int(0)),
HirExpr::Literal(Literal::Int(42)),
],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_pop() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "pop".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_remove() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "remove".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_clear() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "clear".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_update() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("dict".to_string())),
method: "update".to_string(),
args: vec![HirExpr::Dict(vec![])],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_pure_method_call() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "copy".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(!expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_in_args() {
let expr = HirExpr::Call {
func: "pure_fn".to_string(),
args: vec![HirExpr::Call {
func: "print".to_string(),
args: vec![],
kwargs: vec![],
}],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_in_method_object() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "append".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
}),
method: "copy".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_call_graph_with_function() {
let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
let program = HirProgram {
functions: vec![
HirFunction {
name: "caller".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Call {
func: "callee".to_string(),
args: vec![],
kwargs: vec![],
}))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
},
HirFunction {
name: "callee".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
},
],
classes: vec![],
imports: vec![],
};
let decisions = analyzer.analyze_program(&program);
assert!(!decisions.is_empty());
}
#[test]
fn test_recursive_function_detection() {
let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
let program = HirProgram {
functions: vec![HirFunction {
name: "recursive".to_string(),
params: smallvec![HirParam::new("n".to_string(), Type::Int)],
ret_type: Type::Int,
body: vec![HirStmt::If {
condition: HirExpr::Binary {
left: Box::new(HirExpr::Var("n".to_string())),
op: BinOp::Eq,
right: Box::new(HirExpr::Literal(Literal::Int(0))),
},
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: Some(vec![HirStmt::Return(Some(HirExpr::Binary {
left: Box::new(HirExpr::Var("n".to_string())),
op: BinOp::Mul,
right: Box::new(HirExpr::Call {
func: "recursive".to_string(),
args: vec![HirExpr::Binary {
left: Box::new(HirExpr::Var("n".to_string())),
op: BinOp::Sub,
right: Box::new(HirExpr::Literal(Literal::Int(1))),
}],
kwargs: vec![],
}),
}))]),
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
}],
classes: vec![],
imports: vec![],
};
let decisions = analyzer.analyze_program(&program);
if let Some(decision) = decisions.get("recursive") {
assert!(
!decision.should_inline || matches!(decision.reason, InliningReason::Recursive)
);
}
}
#[test]
fn test_side_effect_sort() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "sort".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_reverse() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("list".to_string())),
method: "reverse".to_string(),
args: vec![],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_side_effect_dict_update() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("dict".to_string())),
method: "update".to_string(),
args: vec![HirExpr::Dict(vec![])],
kwargs: vec![],
};
assert!(expr_has_side_effects_inner(&expr));
}
#[test]
fn test_pure_expr_get() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("dict".to_string())),
method: "get".to_string(),
args: vec![HirExpr::Literal(Literal::String("key".to_string()))],
kwargs: vec![],
};
assert!(!expr_has_side_effects_inner(&expr));
}
#[test]
fn test_expr_size_set() {
let expr = HirExpr::Set(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
]);
let size = calculate_expr_size_inner(&expr);
assert!(size > 0);
}
#[test]
fn test_expr_size_tuple() {
let expr = HirExpr::Tuple(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::String("a".to_string())),
]);
let size = calculate_expr_size_inner(&expr);
assert!(size >= 2);
}
#[test]
fn test_expr_size_binary_complex() {
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Binary {
left: Box::new(HirExpr::Var("a".to_string())),
op: BinOp::Add,
right: Box::new(HirExpr::Var("b".to_string())),
}),
op: BinOp::Mul,
right: Box::new(HirExpr::Var("c".to_string())),
};
let size = calculate_expr_size_inner(&expr);
assert!(size >= 3);
}
#[test]
fn test_expr_size_call_with_args() {
let expr = HirExpr::Call {
func: "func".to_string(),
args: vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
],
kwargs: vec![],
};
let size = calculate_expr_size_inner(&expr);
assert!(size >= 2);
}
#[test]
fn test_expr_size_method_with_args() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("obj".to_string())),
method: "method".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
let size = calculate_expr_size_inner(&expr);
assert!(size >= 1);
}
#[test]
fn test_contains_loops_nested_for_in_while() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Call {
func: "range".to_string(),
args: vec![HirExpr::Literal(Literal::Int(10))],
kwargs: vec![],
},
body: vec![],
}],
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_contains_loops_in_while_body() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(false)),
body: vec![],
}],
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_contains_loops_in_if_then() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Break { label: None }],
}],
else_body: None,
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_contains_loops_in_if_else() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![],
else_body: Some(vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::List(vec![]),
body: vec![],
}]),
}];
assert!(contains_loops_inner(&body));
}
#[test]
fn test_count_returns_multiple() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
2,
))))]),
}];
let count = count_returns_inner(&body);
assert_eq!(count, 2);
}
#[test]
fn test_count_returns_in_while() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
}];
let count = count_returns_inner(&body);
assert_eq!(count, 1);
}
#[test]
fn test_count_returns_in_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::List(vec![]),
body: vec![HirStmt::Return(Some(HirExpr::Var("i".to_string())))],
}];
let count = count_returns_inner(&body);
assert_eq!(count, 1);
}
#[test]
fn test_inlining_decision_too_large() {
let decision = InliningDecision {
should_inline: false,
reason: InliningReason::TooLarge,
cost_benefit: 0.0,
};
assert!(!decision.should_inline);
assert!(matches!(decision.reason, InliningReason::TooLarge));
}
#[test]
fn test_inlining_decision_contains_loops() {
let decision = InliningDecision {
should_inline: false,
reason: InliningReason::ContainsLoops,
cost_benefit: 0.0,
};
assert!(!decision.should_inline);
assert!(matches!(decision.reason, InliningReason::ContainsLoops));
}
#[test]
fn test_inlining_decision_cost_too_high() {
let decision = InliningDecision {
should_inline: false,
reason: InliningReason::CostTooHigh,
cost_benefit: -0.5,
};
assert!(!decision.should_inline);
assert!(decision.cost_benefit < 0.0);
}
#[test]
fn test_apply_inlining_no_changes() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let program = HirProgram {
functions: vec![create_simple_function("main", 5)],
classes: vec![],
imports: vec![],
};
let decisions = std::collections::HashMap::new();
let result = analyzer.apply_inlining(program.clone(), &decisions);
assert_eq!(result.functions.len(), program.functions.len());
}
#[test]
fn test_transform_expr_literal() {
let expr = HirExpr::Literal(Literal::Int(42));
let result = transform_expr_for_inlining_inner(&expr, &[]);
assert!(matches!(result, HirExpr::Literal(Literal::Int(42))));
}
#[test]
fn test_transform_expr_var_no_param_match() {
let expr = HirExpr::Var("x".to_string());
let params = vec![HirParam::new("y".to_string(), Type::Int)];
let result = transform_expr_for_inlining_inner(&expr, ¶ms);
assert!(matches!(result, HirExpr::Var(ref name) if name == "x"));
}
#[test]
fn test_transform_expr_binary() {
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Literal(Literal::Int(1))),
op: BinOp::Add,
right: Box::new(HirExpr::Literal(Literal::Int(2))),
};
let result = transform_expr_for_inlining_inner(&expr, &[]);
assert!(matches!(result, HirExpr::Binary { .. }));
}
#[test]
fn test_transform_expr_call() {
let expr = HirExpr::Call {
func: "foo".to_string(),
args: vec![HirExpr::Literal(Literal::Int(1))],
kwargs: vec![],
};
let result = transform_expr_for_inlining_inner(&expr, &[]);
assert!(matches!(result, HirExpr::Call { .. }));
}
#[test]
fn test_extract_calls_from_nested_call() {
let expr = HirExpr::Call {
func: "outer".to_string(),
args: vec![HirExpr::Call {
func: "inner".to_string(),
args: vec![],
kwargs: vec![],
}],
kwargs: vec![],
};
let mut calls = std::collections::HashSet::new();
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("outer"));
assert!(calls.contains("inner"));
}
#[test]
fn test_extract_calls_from_method_with_call_arg() {
let expr = HirExpr::MethodCall {
object: Box::new(HirExpr::Var("obj".to_string())),
method: "process".to_string(),
args: vec![HirExpr::Call {
func: "get_value".to_string(),
args: vec![],
kwargs: vec![],
}],
kwargs: vec![],
};
let mut calls = std::collections::HashSet::new();
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("get_value"));
}
#[test]
fn test_extract_calls_from_binary_with_calls() {
let expr = HirExpr::Binary {
left: Box::new(HirExpr::Call {
func: "left_fn".to_string(),
args: vec![],
kwargs: vec![],
}),
op: BinOp::Add,
right: Box::new(HirExpr::Call {
func: "right_fn".to_string(),
args: vec![],
kwargs: vec![],
}),
};
let mut calls = std::collections::HashSet::new();
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("left_fn"));
assert!(calls.contains("right_fn"));
}
#[test]
fn test_extract_calls_from_list_with_call() {
let expr = HirExpr::List(vec![HirExpr::Call {
func: "get_item".to_string(),
args: vec![],
kwargs: vec![],
}]);
let mut calls = std::collections::HashSet::new();
extract_calls_from_expr_inner(&expr, &mut calls);
assert!(calls.contains("get_item"));
}
#[test]
fn test_stmt_size_assign() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Assign {
target: AssignTarget::Symbol("x".to_string()),
value: HirExpr::Literal(Literal::Int(1)),
type_annotation: None,
};
let size = analyzer.calculate_stmt_size(&stmt);
assert!(size > 0);
}
#[test]
fn test_stmt_size_if() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
2,
))))]),
};
let size = analyzer.calculate_stmt_size(&stmt);
assert!(size >= 3);
}
#[test]
fn test_stmt_size_while() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Break { label: None }],
};
let size = analyzer.calculate_stmt_size(&stmt);
assert!(size >= 2);
}
#[test]
fn test_stmt_size_for() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::List(vec![]),
body: vec![],
};
let size = analyzer.calculate_stmt_size(&stmt);
assert!(size >= 1);
}
#[test]
fn test_stmt_size_try() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Try {
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
handlers: vec![],
orelse: None,
finalbody: None,
};
let size = analyzer.calculate_stmt_size(&stmt);
assert!(size >= 1);
}
#[test]
fn test_side_effect_in_assign() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Assign {
target: AssignTarget::Symbol("x".to_string()),
value: HirExpr::Call {
func: "print".to_string(),
args: vec![],
kwargs: vec![],
},
type_annotation: None,
};
assert!(analyzer.stmt_has_side_effects(&stmt));
}
#[test]
fn test_side_effect_in_raise() {
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let stmt = HirStmt::Raise {
exception: Some(HirExpr::Call {
func: "ValueError".to_string(),
args: vec![HirExpr::Literal(Literal::String("error".to_string()))],
kwargs: vec![],
}),
cause: None,
};
assert!(analyzer.stmt_has_side_effects(&stmt));
}
#[test]
fn test_config_no_inline_single_use() {
let config = InliningConfig {
inline_single_use: false,
..Default::default()
};
assert!(!config.inline_single_use);
}
#[test]
fn test_config_no_inline_trivial() {
let config = InliningConfig {
inline_trivial: false,
..Default::default()
};
assert!(!config.inline_trivial);
}
#[test]
fn test_config_inline_loops() {
let config = InliningConfig {
inline_loops: true,
..Default::default()
};
assert!(config.inline_loops);
}
#[test]
fn test_config_high_cost_threshold() {
let config = InliningConfig {
cost_threshold: 10.0,
..Default::default()
};
assert_eq!(config.cost_threshold, 10.0);
}
#[test]
fn test_function_metrics_new() {
let metrics = FunctionMetrics {
size: 10,
_param_count: 2,
_return_count: 1,
has_loops: false,
has_side_effects: false,
is_trivial: false,
call_count: 5,
cost: 2.5,
};
assert_eq!(metrics.size, 10);
assert_eq!(metrics.call_count, 5);
assert_eq!(metrics.cost, 2.5);
assert!(!metrics.has_loops);
}
#[test]
fn test_empty_function_body() {
let func = HirFunction {
name: "empty".to_string(),
params: smallvec![],
ret_type: Type::None,
body: vec![],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
let analyzer = InliningAnalyzer::new(InliningConfig::default());
let size = analyzer.calculate_function_size(&func);
assert_eq!(size, 0);
}
#[test]
fn test_mutual_recursion() {
let mut analyzer = InliningAnalyzer::new(InliningConfig::default());
let program = HirProgram {
functions: vec![
HirFunction {
name: "even".to_string(),
params: smallvec![HirParam::new("n".to_string(), Type::Int)],
ret_type: Type::Bool,
body: vec![HirStmt::Return(Some(HirExpr::Call {
func: "odd".to_string(),
args: vec![HirExpr::Var("n".to_string())],
kwargs: vec![],
}))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
},
HirFunction {
name: "odd".to_string(),
params: smallvec![HirParam::new("n".to_string(), Type::Int)],
ret_type: Type::Bool,
body: vec![HirStmt::Return(Some(HirExpr::Call {
func: "even".to_string(),
args: vec![HirExpr::Var("n".to_string())],
kwargs: vec![],
}))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
},
],
classes: vec![],
imports: vec![],
};
let decisions = analyzer.analyze_program(&program);
assert!(!decisions.is_empty());
}
}