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 { method, .. } => method_has_side_effects(method),
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"];
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(),
},
_ => 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(format!("x{}", i)),
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))],
};
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);
}
}