1use crate::ast::{Expr, FnBody, FnDef, Stmt};
2
3use super::{
4 CallLowerCtx, CallPlan, ForwardCallPlan, LeafOp, classify_call_plan,
5 classify_forward_call_plan, classify_leaf_op, classify_match_dispatch_plan,
6};
7
8#[derive(Debug, Clone, PartialEq)]
15pub enum BodyExprPlan<'a> {
16 Expr(&'a Expr),
17 Leaf(LeafOp<'a>),
18 Call { target: CallPlan, args: &'a [Expr] },
19 ForwardCall(ForwardCallPlan),
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct BodyBindingPlan<'a> {
24 pub name: &'a str,
25 pub expr: BodyExprPlan<'a>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum BodyPlan<'a> {
30 SingleExpr(BodyExprPlan<'a>),
31 Block {
32 stmts: &'a [Stmt],
33 bindings: Vec<BodyBindingPlan<'a>>,
34 tail: BodyExprPlan<'a>,
35 },
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ThinKind {
40 Leaf,
41 Direct,
42 Forward,
43 Dispatch,
44 Tail,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct ThinBodyPlan<'a> {
49 pub params: &'a [(String, String)],
50 pub body: BodyPlan<'a>,
51 pub kind: ThinKind,
52}
53
54pub trait ThinBodyCtx: CallLowerCtx {
55 fn find_fn_def<'a>(&'a self, name: &str) -> Option<&'a FnDef>;
56}
57
58pub fn classify_body_expr_plan<'a>(expr: &'a Expr, ctx: &impl CallLowerCtx) -> BodyExprPlan<'a> {
59 if let Some(leaf) = classify_leaf_op(expr, ctx) {
60 return BodyExprPlan::Leaf(leaf);
61 }
62
63 if let Some(plan) = classify_forward_call_plan(expr, ctx) {
64 return BodyExprPlan::ForwardCall(plan);
65 }
66
67 if let Expr::FnCall(fn_expr, args) = expr {
68 let target = classify_call_plan(fn_expr, ctx);
69 if !matches!(target, CallPlan::Dynamic) {
70 return BodyExprPlan::Call { target, args };
71 }
72 }
73
74 BodyExprPlan::Expr(expr)
75}
76
77pub fn classify_body_plan<'a>(body: &'a FnBody, ctx: &impl CallLowerCtx) -> Option<BodyPlan<'a>> {
78 let stmts = body.stmts();
79 let (tail_stmt, prefix) = stmts.split_last()?;
80
81 let Stmt::Expr(tail_expr) = tail_stmt else {
82 return None;
83 };
84
85 if prefix.is_empty() {
86 return Some(BodyPlan::SingleExpr(classify_body_expr_plan(
87 tail_expr, ctx,
88 )));
89 }
90
91 let mut bindings = Vec::with_capacity(prefix.len());
92 for stmt in prefix {
93 let Stmt::Binding(name, _type_ann, expr) = stmt else {
94 return None;
95 };
96 bindings.push(BodyBindingPlan {
97 name,
98 expr: classify_body_expr_plan(expr, ctx),
99 });
100 }
101
102 Some(BodyPlan::Block {
103 stmts,
104 bindings,
105 tail: classify_body_expr_plan(tail_expr, ctx),
106 })
107}
108
109pub fn classify_thin_body_plan<'a>(
110 name: &str,
111 ctx: &'a impl ThinBodyCtx,
112) -> Option<ThinBodyPlan<'a>> {
113 let fd = ctx.find_fn_def(name)?;
114 classify_thin_fn_def(fd, ctx)
115}
116
117pub fn classify_thin_fn_def<'a>(
118 fd: &'a FnDef,
119 ctx: &impl CallLowerCtx,
120) -> Option<ThinBodyPlan<'a>> {
121 let body = classify_body_plan(&fd.body, ctx)?;
122 Some(ThinBodyPlan {
123 params: &fd.params,
124 kind: classify_thin_kind(&body, ctx)?,
125 body,
126 })
127}
128
129fn classify_thin_kind(plan: &BodyPlan<'_>, ctx: &impl CallLowerCtx) -> Option<ThinKind> {
130 match plan {
131 BodyPlan::SingleExpr(expr) => classify_thin_expr_kind(expr, ctx),
132 BodyPlan::Block { bindings, tail, .. } => {
133 if bindings
134 .iter()
135 .all(|binding| body_expr_is_thin_binding(&binding.expr))
136 {
137 classify_thin_expr_kind(tail, ctx)
138 } else {
139 None
140 }
141 }
142 }
143}
144
145fn classify_thin_expr_kind(plan: &BodyExprPlan<'_>, ctx: &impl CallLowerCtx) -> Option<ThinKind> {
146 match plan {
147 BodyExprPlan::Leaf(_) => Some(ThinKind::Leaf),
148 BodyExprPlan::Call { .. } => Some(ThinKind::Direct),
149 BodyExprPlan::ForwardCall(_) => Some(ThinKind::Forward),
150 BodyExprPlan::Expr(expr) => match expr {
151 Expr::Match { arms, .. } if classify_match_dispatch_plan(arms, ctx).is_some() => {
152 Some(ThinKind::Dispatch)
153 }
154 Expr::TailCall(_) => Some(ThinKind::Tail),
155 _ => None,
156 },
157 }
158}
159
160fn body_expr_is_thin_binding(plan: &BodyExprPlan<'_>) -> bool {
161 match plan {
162 BodyExprPlan::Leaf(_) | BodyExprPlan::Call { .. } | BodyExprPlan::ForwardCall(_) => true,
163 BodyExprPlan::Expr(expr) => matches!(
164 expr,
165 Expr::Literal(_) | Expr::Ident(_) | Expr::Constructor(_, _)
166 ),
167 }
168}