Skip to main content

logicaffeine_compile/optimize/
partial_eval.rs

1use std::collections::HashMap;
2
3use crate::arena::Arena;
4use crate::ast::stmt::{Expr, Literal, Stmt, TypeExpr, Block, StringPart};
5use crate::intern::{Interner, Symbol};
6use std::collections::HashSet;
7
8use super::bta::{self, BindingTime, Division};
9use super::effects::EffectEnv;
10
11type SpecKey = (Symbol, Vec<Option<Literal>>);
12
13struct FuncInfo<'a> {
14    name: Symbol,
15    params: Vec<(Symbol, &'a TypeExpr<'a>)>,
16    body: Block<'a>,
17    generics: Vec<Symbol>,
18    return_type: Option<&'a TypeExpr<'a>>,
19    opt_flags: crate::optimization::OptimizationConfig,
20}
21
22struct SpecRegistry<'a> {
23    cache: HashMap<SpecKey, Symbol>,
24    new_funcs: Vec<Stmt<'a>>,
25    variant_count: HashMap<Symbol, usize>,
26    history: Vec<SpecKey>,
27    bta_cache: Option<super::bta::BtaCache>,
28}
29
30impl<'a> SpecRegistry<'a> {
31    fn new() -> Self {
32        SpecRegistry {
33            cache: HashMap::new(),
34            new_funcs: Vec::new(),
35            variant_count: HashMap::new(),
36            history: Vec::new(),
37            bta_cache: None,
38        }
39    }
40}
41
42fn spec_key_embeds(earlier: &SpecKey, later: &SpecKey) -> bool {
43    if earlier.0 != later.0 {
44        return false;
45    }
46    let ea = &earlier.1;
47    let la = &later.1;
48    if ea.len() != la.len() {
49        return false;
50    }
51    if ea == la {
52        return false; // reflexive — not a proper embedding
53    }
54    let mut strict = false;
55    for (e, l) in ea.iter().zip(la.iter()) {
56        match (e, l) {
57            (None, _) => {} // dynamic embeds in anything
58            (Some(_), None) => return false, // static does not embed in dynamic
59            (Some(e_lit), Some(l_lit)) => {
60                if !literal_embeds(e_lit, l_lit) {
61                    return false;
62                }
63                if e_lit != l_lit {
64                    strict = true;
65                }
66            }
67        }
68    }
69    strict
70}
71
72fn literal_embeds(a: &Literal, b: &Literal) -> bool {
73    match (a, b) {
74        (Literal::Number(x), Literal::Number(y)) => x.abs() <= y.abs(),
75        (Literal::Float(x), Literal::Float(y)) => x.abs() <= y.abs(),
76        (Literal::Boolean(_), Literal::Boolean(_)) => true,
77        (Literal::Text(x), Literal::Text(y)) => x.index() <= y.index(),
78        (Literal::Nothing, Literal::Nothing) => true,
79        _ => a == b,
80    }
81}
82
83fn collect_func_defs<'a>(stmts: &[Stmt<'a>]) -> HashMap<Symbol, FuncInfo<'a>> {
84    let mut defs = HashMap::new();
85    for stmt in stmts {
86        if let Stmt::FunctionDef { name, params, body, generics, return_type, is_native, opt_flags, .. } = stmt {
87            if !is_native {
88                defs.insert(*name, FuncInfo {
89                    name: *name,
90                    params: params.clone(),
91                    body,
92                    generics: generics.clone(),
93                    return_type: *return_type,
94                    opt_flags: opt_flags.clone(),
95                });
96            }
97        }
98    }
99    defs
100}
101
102fn body_has_io(stmts: &[Stmt]) -> bool {
103    for stmt in stmts {
104        if stmt_has_io(stmt) {
105            return true;
106        }
107    }
108    false
109}
110
111/// Whether a statement carries an opaque effectful boundary that disqualifies its
112/// enclosing function from specialization. This is the fallback for the no-`effect_env`
113/// path; the production path uses `EffectEnv::function_is_specialization_safe`, which is
114/// strictly more precise. The name is kept for continuity, but it now covers IO *and* the
115/// concurrency / networking constructs the substitution cannot enter.
116fn stmt_has_io(stmt: &Stmt) -> bool {
117    match stmt {
118        Stmt::Show { .. }
119        | Stmt::WriteFile { .. }
120        | Stmt::SendMessage { .. }
121        | Stmt::StreamMessage { .. }
122        | Stmt::Sleep { .. }
123        | Stmt::SendPipe { .. }
124        | Stmt::TrySendPipe { .. }
125        | Stmt::ReceivePipe { .. }
126        | Stmt::TryReceivePipe { .. }
127        | Stmt::ReadFrom { .. }
128        | Stmt::Check { .. } => true,
129        // Concurrency / spawn / networking — opaque boundaries, never specialize across.
130        Stmt::LaunchTask { .. }
131        | Stmt::LaunchTaskWithHandle { .. }
132        | Stmt::CreatePipe { .. }
133        | Stmt::StopTask { .. }
134        | Stmt::Spawn { .. }
135        | Stmt::Listen { .. }
136        | Stmt::ConnectTo { .. }
137        | Stmt::LetPeerAgent { .. }
138        | Stmt::AwaitMessage { .. }
139        | Stmt::Sync { .. }
140        | Stmt::Mount { .. } => true,
141        Stmt::IncreaseCrdt { .. }
142        | Stmt::DecreaseCrdt { .. }
143        | Stmt::MergeCrdt { .. }
144        | Stmt::AppendToSequence { .. }
145        | Stmt::ResolveConflict { .. } => true,
146        // A Select is nondeterministic and a Concurrent/Parallel block is a concurrency
147        // boundary — disqualifying regardless of their body contents.
148        Stmt::Select { .. } | Stmt::Concurrent { .. } | Stmt::Parallel { .. } => true,
149        Stmt::If { then_block, else_block, .. } => {
150            body_has_io(then_block)
151                || else_block.map_or(false, |eb| body_has_io(eb))
152        }
153        Stmt::While { body, .. } | Stmt::Repeat { body, .. } => body_has_io(body),
154        Stmt::Zone { body, .. } => body_has_io(body),
155        Stmt::Inspect { arms, .. } => {
156            arms.iter().any(|arm| body_has_io(arm.body))
157        }
158        _ => false,
159    }
160}
161
162fn body_has_escape(stmts: &[Stmt]) -> bool {
163    for stmt in stmts {
164        match stmt {
165            Stmt::Escape { .. } => return true,
166            Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
167                if expr_has_escape(value) {
168                    return true;
169                }
170            }
171            Stmt::If { then_block, else_block, .. } => {
172                if body_has_escape(then_block) {
173                    return true;
174                }
175                if let Some(eb) = else_block {
176                    if body_has_escape(eb) {
177                        return true;
178                    }
179                }
180            }
181            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
182                if body_has_escape(body) {
183                    return true;
184                }
185            }
186            Stmt::Return { value } => {
187                if let Some(v) = value {
188                    if expr_has_escape(v) {
189                        return true;
190                    }
191                }
192            }
193            _ => {}
194        }
195    }
196    false
197}
198
199fn expr_has_escape(expr: &Expr) -> bool {
200    match expr {
201        Expr::Escape { .. } => true,
202        _ => false,
203    }
204}
205
206fn classify_arg<'a>(arg: &Expr<'a>, division: Option<&Division>) -> Option<Literal> {
207    match arg {
208        Expr::Literal(lit) => Some(lit.clone()),
209        _ => {
210            if let Some(div) = division {
211                match bta::analyze_expr(arg, div) {
212                    BindingTime::Static(lit) => Some(lit),
213                    BindingTime::Dynamic => None,
214                }
215            } else {
216                None
217            }
218        }
219    }
220}
221
222fn compute_spec_key<'a>(function: Symbol, args: &[&'a Expr<'a>], division: Option<&Division>) -> (SpecKey, Vec<Option<Literal>>) {
223    let arg_classifications: Vec<Option<Literal>> = args.iter()
224        .map(|a| classify_arg(a, division))
225        .collect();
226    let key = (function, arg_classifications.clone());
227    (key, arg_classifications)
228}
229
230fn is_mixed(classifications: &[Option<Literal>]) -> bool {
231    let has_static = classifications.iter().any(|c| c.is_some());
232    let has_dynamic = classifications.iter().any(|c| c.is_none());
233    has_static && has_dynamic
234}
235
236fn make_spec_name(interner: &mut Interner, func_name: Symbol, classifications: &[Option<Literal>]) -> Symbol {
237    let base = interner.resolve(func_name);
238    let mut name = base.to_string();
239    for (i, c) in classifications.iter().enumerate() {
240        if let Some(lit) = c {
241            name.push_str(&format!("_s{}_{}", i, literal_to_name_part(lit)));
242        }
243    }
244    interner.intern(&name)
245}
246
247fn literal_to_name_part(lit: &Literal) -> String {
248    match lit {
249        Literal::Number(n) => format!("{}", n),
250        Literal::Float(f) => format!("{}", f).replace('.', "d").replace('-', "n"),
251        Literal::Boolean(b) => format!("{}", b),
252        Literal::Text(s) => format!("t{:x}", s.index()),
253        Literal::Nothing => "nothing".to_string(),
254        _ => "x".to_string(),
255    }
256}
257
258fn substitute_expr<'a>(
259    expr: &'a Expr<'a>,
260    substitutions: &HashMap<Symbol, &'a Expr<'a>>,
261    expr_arena: &'a Arena<Expr<'a>>,
262) -> &'a Expr<'a> {
263    match expr {
264        Expr::Identifier(sym) => {
265            if let Some(replacement) = substitutions.get(sym) {
266                replacement
267            } else {
268                expr
269            }
270        }
271        Expr::BinaryOp { op, left, right } => {
272            let new_left = substitute_expr(left, substitutions, expr_arena);
273            let new_right = substitute_expr(right, substitutions, expr_arena);
274            if std::ptr::eq(new_left as *const _, *left as *const _)
275                && std::ptr::eq(new_right as *const _, *right as *const _) {
276                expr
277            } else {
278                expr_arena.alloc(Expr::BinaryOp {
279                    op: *op,
280                    left: new_left,
281                    right: new_right,
282                })
283            }
284        }
285        Expr::Not { operand } => {
286            let new_operand = substitute_expr(operand, substitutions, expr_arena);
287            if std::ptr::eq(new_operand as *const _, *operand as *const _) {
288                expr
289            } else {
290                expr_arena.alloc(Expr::Not { operand: new_operand })
291            }
292        }
293        Expr::Call { function, args } => {
294            let new_args: Vec<&'a Expr<'a>> = args.iter()
295                .map(|a| substitute_expr(a, substitutions, expr_arena))
296                .collect();
297            let changed = new_args.iter().zip(args.iter())
298                .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
299            if changed {
300                expr_arena.alloc(Expr::Call { function: *function, args: new_args })
301            } else {
302                expr
303            }
304        }
305        Expr::Index { collection, index } => {
306            let new_coll = substitute_expr(collection, substitutions, expr_arena);
307            let new_idx = substitute_expr(index, substitutions, expr_arena);
308            if std::ptr::eq(new_coll as *const _, *collection as *const _)
309                && std::ptr::eq(new_idx as *const _, *index as *const _) {
310                expr
311            } else {
312                expr_arena.alloc(Expr::Index { collection: new_coll, index: new_idx })
313            }
314        }
315        Expr::Length { collection } => {
316            let new_coll = substitute_expr(collection, substitutions, expr_arena);
317            if std::ptr::eq(new_coll as *const _, *collection as *const _) {
318                expr
319            } else {
320                expr_arena.alloc(Expr::Length { collection: new_coll })
321            }
322        }
323        Expr::Slice { collection, start, end } => {
324            let new_coll = substitute_expr(collection, substitutions, expr_arena);
325            let new_start = substitute_expr(start, substitutions, expr_arena);
326            let new_end = substitute_expr(end, substitutions, expr_arena);
327            if std::ptr::eq(new_coll as *const _, *collection as *const _)
328                && std::ptr::eq(new_start as *const _, *start as *const _)
329                && std::ptr::eq(new_end as *const _, *end as *const _) {
330                expr
331            } else {
332                expr_arena.alloc(Expr::Slice { collection: new_coll, start: new_start, end: new_end })
333            }
334        }
335        Expr::FieldAccess { object, field } => {
336            let new_obj = substitute_expr(object, substitutions, expr_arena);
337            if std::ptr::eq(new_obj as *const _, *object as *const _) {
338                expr
339            } else {
340                expr_arena.alloc(Expr::FieldAccess { object: new_obj, field: *field })
341            }
342        }
343        Expr::Contains { collection, value } => {
344            let new_coll = substitute_expr(collection, substitutions, expr_arena);
345            let new_val = substitute_expr(value, substitutions, expr_arena);
346            if std::ptr::eq(new_coll as *const _, *collection as *const _)
347                && std::ptr::eq(new_val as *const _, *value as *const _) {
348                expr
349            } else {
350                expr_arena.alloc(Expr::Contains { collection: new_coll, value: new_val })
351            }
352        }
353        Expr::NewVariant { enum_name, variant, fields } => {
354            let new_fields: Vec<(Symbol, &'a Expr<'a>)> = fields.iter()
355                .map(|(name, val)| (*name, substitute_expr(val, substitutions, expr_arena)))
356                .collect();
357            let changed = new_fields.iter().zip(fields.iter())
358                .any(|((_, new_v), (_, old_v))| !std::ptr::eq(*new_v as *const _, *old_v as *const _));
359            if changed {
360                expr_arena.alloc(Expr::NewVariant { enum_name: *enum_name, variant: *variant, fields: new_fields })
361            } else {
362                expr
363            }
364        }
365        Expr::New { type_name, type_args, init_fields } => {
366            let new_fields: Vec<(Symbol, &'a Expr<'a>)> = init_fields.iter()
367                .map(|(name, val)| (*name, substitute_expr(val, substitutions, expr_arena)))
368                .collect();
369            let changed = new_fields.iter().zip(init_fields.iter())
370                .any(|((_, new_v), (_, old_v))| !std::ptr::eq(*new_v as *const _, *old_v as *const _));
371            if changed {
372                expr_arena.alloc(Expr::New { type_name: *type_name, type_args: type_args.clone(), init_fields: new_fields })
373            } else {
374                expr
375            }
376        }
377        Expr::OptionSome { value } => {
378            let new_val = substitute_expr(value, substitutions, expr_arena);
379            if std::ptr::eq(new_val as *const _, *value as *const _) { expr }
380            else { expr_arena.alloc(Expr::OptionSome { value: new_val }) }
381        }
382        Expr::Copy { expr: inner } => {
383            let new_inner = substitute_expr(inner, substitutions, expr_arena);
384            if std::ptr::eq(new_inner as *const _, *inner as *const _) { expr }
385            else { expr_arena.alloc(Expr::Copy { expr: new_inner }) }
386        }
387        Expr::Give { value } => {
388            let new_val = substitute_expr(value, substitutions, expr_arena);
389            if std::ptr::eq(new_val as *const _, *value as *const _) { expr }
390            else { expr_arena.alloc(Expr::Give { value: new_val }) }
391        }
392        Expr::List(items) => {
393            let new_items: Vec<&'a Expr<'a>> = items.iter()
394                .map(|item| substitute_expr(item, substitutions, expr_arena))
395                .collect();
396            let changed = new_items.iter().zip(items.iter())
397                .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
398            if changed { expr_arena.alloc(Expr::List(new_items)) }
399            else { expr }
400        }
401        Expr::Tuple(items) => {
402            let new_items: Vec<&'a Expr<'a>> = items.iter()
403                .map(|item| substitute_expr(item, substitutions, expr_arena))
404                .collect();
405            let changed = new_items.iter().zip(items.iter())
406                .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
407            if changed { expr_arena.alloc(Expr::Tuple(new_items)) }
408            else { expr }
409        }
410        Expr::Range { start, end } => {
411            let new_start = substitute_expr(start, substitutions, expr_arena);
412            let new_end = substitute_expr(end, substitutions, expr_arena);
413            if std::ptr::eq(new_start as *const _, *start as *const _)
414                && std::ptr::eq(new_end as *const _, *end as *const _) { expr }
415            else { expr_arena.alloc(Expr::Range { start: new_start, end: new_end }) }
416        }
417        Expr::Union { left, right } => {
418            let new_left = substitute_expr(left, substitutions, expr_arena);
419            let new_right = substitute_expr(right, substitutions, expr_arena);
420            if std::ptr::eq(new_left as *const _, *left as *const _)
421                && std::ptr::eq(new_right as *const _, *right as *const _) { expr }
422            else { expr_arena.alloc(Expr::Union { left: new_left, right: new_right }) }
423        }
424        Expr::Intersection { left, right } => {
425            let new_left = substitute_expr(left, substitutions, expr_arena);
426            let new_right = substitute_expr(right, substitutions, expr_arena);
427            if std::ptr::eq(new_left as *const _, *left as *const _)
428                && std::ptr::eq(new_right as *const _, *right as *const _) { expr }
429            else { expr_arena.alloc(Expr::Intersection { left: new_left, right: new_right }) }
430        }
431        Expr::CallExpr { callee, args } => {
432            let new_callee = substitute_expr(callee, substitutions, expr_arena);
433            let new_args: Vec<&'a Expr<'a>> = args.iter()
434                .map(|a| substitute_expr(a, substitutions, expr_arena))
435                .collect();
436            let changed = !std::ptr::eq(new_callee as *const _, *callee as *const _)
437                || new_args.iter().zip(args.iter())
438                    .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
439            if changed {
440                expr_arena.alloc(Expr::CallExpr { callee: new_callee, args: new_args })
441            } else {
442                expr
443            }
444        }
445        Expr::WithCapacity { value, capacity } => {
446            let new_val = substitute_expr(value, substitutions, expr_arena);
447            let new_cap = substitute_expr(capacity, substitutions, expr_arena);
448            if std::ptr::eq(new_val as *const _, *value as *const _)
449                && std::ptr::eq(new_cap as *const _, *capacity as *const _) { expr }
450            else { expr_arena.alloc(Expr::WithCapacity { value: new_val, capacity: new_cap }) }
451        }
452        Expr::InterpolatedString(parts) => {
453            let new_parts: Vec<StringPart<'a>> = parts.iter()
454                .map(|part| match part {
455                    StringPart::Literal(_) => part.clone(),
456                    StringPart::Expr { value, format_spec, debug } => {
457                        let new_val = substitute_expr(value, substitutions, expr_arena);
458                        if std::ptr::eq(new_val as *const _, *value as *const _) {
459                            part.clone()
460                        } else {
461                            StringPart::Expr { value: new_val, format_spec: *format_spec, debug: *debug }
462                        }
463                    }
464                })
465                .collect();
466            expr_arena.alloc(Expr::InterpolatedString(new_parts))
467        }
468        _ => expr,
469    }
470}
471
472fn substitute_stmt<'a>(
473    stmt: &Stmt<'a>,
474    substitutions: &HashMap<Symbol, &'a Expr<'a>>,
475    expr_arena: &'a Arena<Expr<'a>>,
476    stmt_arena: &'a Arena<Stmt<'a>>,
477) -> Stmt<'a> {
478    match stmt {
479        Stmt::Let { var, value, mutable, ty } => Stmt::Let {
480            var: *var,
481            value: substitute_expr(value, substitutions, expr_arena),
482            mutable: *mutable,
483            ty: *ty,
484        },
485        Stmt::Set { target, value } => Stmt::Set {
486            target: *target,
487            value: substitute_expr(value, substitutions, expr_arena),
488        },
489        Stmt::Return { value } => Stmt::Return {
490            value: value.map(|v| substitute_expr(v, substitutions, expr_arena)),
491        },
492        Stmt::Show { object, recipient } => Stmt::Show {
493            object: substitute_expr(object, substitutions, expr_arena),
494            recipient: *recipient,
495        },
496        Stmt::Call { function, args } => Stmt::Call {
497            function: *function,
498            args: args.iter().map(|a| substitute_expr(a, substitutions, expr_arena)).collect(),
499        },
500        Stmt::If { cond, then_block, else_block } => {
501            let new_then: Vec<Stmt<'a>> = then_block.iter()
502                .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
503                .collect();
504            let new_else = else_block.map(|eb| {
505                let stmts: Vec<Stmt<'a>> = eb.iter()
506                    .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
507                    .collect();
508                stmt_arena.alloc_slice(stmts) as &[Stmt<'a>]
509            });
510            Stmt::If {
511                cond: substitute_expr(cond, substitutions, expr_arena),
512                then_block: stmt_arena.alloc_slice(new_then),
513                else_block: new_else,
514            }
515        }
516        Stmt::While { cond, body, decreasing } => {
517            let new_body: Vec<Stmt<'a>> = body.iter()
518                .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
519                .collect();
520            Stmt::While {
521                cond: substitute_expr(cond, substitutions, expr_arena),
522                body: stmt_arena.alloc_slice(new_body),
523                decreasing: *decreasing,
524            }
525        }
526        Stmt::Repeat { pattern, iterable, body } => {
527            let new_body: Vec<Stmt<'a>> = body.iter()
528                .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
529                .collect();
530            Stmt::Repeat {
531                pattern: pattern.clone(),
532                iterable: substitute_expr(iterable, substitutions, expr_arena),
533                body: stmt_arena.alloc_slice(new_body),
534            }
535        }
536        Stmt::SetIndex { collection, index, value } => Stmt::SetIndex {
537            collection: substitute_expr(collection, substitutions, expr_arena),
538            index: substitute_expr(index, substitutions, expr_arena),
539            value: substitute_expr(value, substitutions, expr_arena),
540        },
541        Stmt::Push { value, collection } => Stmt::Push {
542            value: substitute_expr(value, substitutions, expr_arena),
543            collection: substitute_expr(collection, substitutions, expr_arena),
544        },
545        Stmt::SetField { object, field, value } => Stmt::SetField {
546            object: substitute_expr(object, substitutions, expr_arena),
547            field: *field,
548            value: substitute_expr(value, substitutions, expr_arena),
549        },
550        Stmt::Give { object, recipient } => Stmt::Give {
551            object: substitute_expr(object, substitutions, expr_arena),
552            recipient: substitute_expr(recipient, substitutions, expr_arena),
553        },
554        Stmt::Add { value, collection } => Stmt::Add {
555            value: substitute_expr(value, substitutions, expr_arena),
556            collection: substitute_expr(collection, substitutions, expr_arena),
557        },
558        Stmt::Remove { value, collection } => Stmt::Remove {
559            value: substitute_expr(value, substitutions, expr_arena),
560            collection: substitute_expr(collection, substitutions, expr_arena),
561        },
562        Stmt::Inspect { target, arms, has_otherwise } => {
563            let new_arms: Vec<_> = arms.iter().map(|arm| {
564                let new_body: Vec<Stmt<'a>> = arm.body.iter()
565                    .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
566                    .collect();
567                crate::ast::stmt::MatchArm {
568                    enum_name: arm.enum_name,
569                    variant: arm.variant,
570                    bindings: arm.bindings.clone(),
571                    body: stmt_arena.alloc_slice(new_body),
572                }
573            }).collect();
574            Stmt::Inspect {
575                target: substitute_expr(target, substitutions, expr_arena),
576                arms: new_arms,
577                has_otherwise: *has_otherwise,
578            }
579        }
580        Stmt::RuntimeAssert { condition, hard } => Stmt::RuntimeAssert {
581            condition: substitute_expr(condition, substitutions, expr_arena),
582            hard: *hard,
583        },
584        other => other.clone(),
585    }
586}
587
588pub(crate) fn substitute_block<'a>(
589    block: Block<'a>,
590    substitutions: &HashMap<Symbol, &'a Expr<'a>>,
591    expr_arena: &'a Arena<Expr<'a>>,
592    stmt_arena: &'a Arena<Stmt<'a>>,
593) -> Vec<Stmt<'a>> {
594    block.iter()
595        .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
596        .collect()
597}
598
599fn count_stmts(stmts: &[Stmt]) -> usize {
600    let mut count = 0;
601    for stmt in stmts {
602        count += 1;
603        match stmt {
604            Stmt::If { then_block, else_block, .. } => {
605                count += count_stmts(then_block);
606                if let Some(eb) = else_block {
607                    count += count_stmts(eb);
608                }
609            }
610            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
611                count += count_stmts(body);
612            }
613            Stmt::FunctionDef { body, .. } => {
614                count += count_stmts(body);
615            }
616            _ => {}
617        }
618    }
619    count
620}
621
622fn try_specialize_call<'a>(
623    function: Symbol,
624    args: &[&'a Expr<'a>],
625    func_defs: &HashMap<Symbol, FuncInfo<'a>>,
626    registry: &mut SpecRegistry<'a>,
627    expr_arena: &'a Arena<Expr<'a>>,
628    stmt_arena: &'a Arena<Stmt<'a>>,
629    interner: &mut Interner,
630    effect_env: Option<&EffectEnv>,
631) -> Option<(Symbol, Vec<&'a Expr<'a>>)> {
632    // Build a BTA division from known literal arguments for enhanced classification
633    let mut division = Division::new();
634    if let Some(func_info) = func_defs.get(&function) {
635        for (i, (param_sym, _)) in func_info.params.iter().enumerate() {
636            if let Some(arg) = args.get(i) {
637                if let Expr::Literal(lit) = arg {
638                    division.insert(*param_sym, BindingTime::Static(lit.clone()));
639                }
640            }
641        }
642    }
643    let (key, classifications) = compute_spec_key(function, args, Some(&division));
644
645    if !is_mixed(&classifications) {
646        return None;
647    }
648
649    if let Some(&cached_name) = registry.cache.get(&key) {
650        let dynamic_args: Vec<&'a Expr<'a>> = args.iter().zip(classifications.iter())
651            .filter(|(_, c)| c.is_none())
652            .map(|(a, _)| *a)
653            .collect();
654        return Some((cached_name, dynamic_args));
655    }
656
657    // Check if this specialization embeds in a previous one (termination guard)
658    if registry.history.iter().any(|prev| spec_key_embeds(prev, &key)) {
659        return None;
660    }
661
662    let count = registry.variant_count.get(&function).copied().unwrap_or(0);
663    if count >= 8 {
664        return None;
665    }
666
667    let func_info = func_defs.get(&function)?;
668
669    // The specialization folds a static argument into the body and drops the parameter.
670    // That is only sound when the callee has no opaque effectful boundary: IO, escape,
671    // a security check, nondeterminism, or concurrency. Folding across a concurrency /
672    // networking statement would drop a parameter the substitution cannot reach (its
673    // `substitute_stmt` arm is the verbatim catch-all), leaving a dangling free variable.
674    let safe_to_specialize = if let Some(env) = effect_env {
675        let fn_name = interner.resolve(function);
676        env.function_is_specialization_safe(fn_name)
677    } else {
678        !body_has_io(func_info.body)
679    };
680    if !safe_to_specialize {
681        return None;
682    }
683
684    if body_has_escape(func_info.body) {
685        return None;
686    }
687
688    let spec_name = make_spec_name(interner, func_info.name, &classifications);
689
690    let mut substitutions: HashMap<Symbol, &'a Expr<'a>> = HashMap::new();
691    let mut new_params: Vec<(Symbol, &'a TypeExpr<'a>)> = Vec::new();
692
693    for (i, (param_sym, param_type)) in func_info.params.iter().enumerate() {
694        if let Some(Some(lit)) = classifications.get(i) {
695            let lit_expr = expr_arena.alloc(Expr::Literal(lit.clone()));
696            substitutions.insert(*param_sym, lit_expr);
697        } else {
698            new_params.push((*param_sym, *param_type));
699        }
700    }
701
702    let specialized_body = substitute_block(func_info.body, &substitutions, expr_arena, stmt_arena);
703
704    let folded = super::fold::fold_stmts(specialized_body, expr_arena, stmt_arena, interner);
705    let optimized = super::dce::eliminate_dead_code(folded, stmt_arena, expr_arena);
706
707    let original_cost = count_stmts(func_info.body) + func_info.params.len();
708    let specialized_cost = count_stmts(&optimized) + new_params.len();
709
710    if specialized_cost as f64 > original_cost as f64 * 0.8 {
711        return None;
712    }
713
714    // Register the specialized function in cache BEFORE cascading to prevent infinite loops
715    registry.history.push(key.clone());
716    registry.cache.insert(key, spec_name);
717    *registry.variant_count.entry(function).or_insert(0) += 1;
718
719    // Cascade: walk specialized body for further specialization candidates
720    let cascaded: Vec<Stmt<'a>> = optimized.into_iter()
721        .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
722        .collect();
723
724    let new_body = stmt_arena.alloc_slice(cascaded);
725
726    let new_func = Stmt::FunctionDef {
727        name: spec_name,
728        generics: func_info.generics.clone(),
729        params: new_params,
730        body: new_body,
731        return_type: func_info.return_type,
732        is_native: false,
733        native_path: None,
734        is_exported: false,
735        export_target: None,
736        opt_flags: func_info.opt_flags.clone(),
737    };
738
739    registry.new_funcs.push(new_func);
740
741    let dynamic_args: Vec<&'a Expr<'a>> = args.iter().zip(classifications.iter())
742        .filter(|(_, c)| c.is_none())
743        .map(|(a, _)| *a)
744        .collect();
745
746    Some((spec_name, dynamic_args))
747}
748
749fn specialize_in_expr<'a>(
750    expr: &'a Expr<'a>,
751    func_defs: &HashMap<Symbol, FuncInfo<'a>>,
752    registry: &mut SpecRegistry<'a>,
753    expr_arena: &'a Arena<Expr<'a>>,
754    stmt_arena: &'a Arena<Stmt<'a>>,
755    interner: &mut Interner,
756    effect_env: Option<&EffectEnv>,
757) -> &'a Expr<'a> {
758    match expr {
759        Expr::Call { function, args } => {
760            let new_args: Vec<&'a Expr<'a>> = args.iter()
761                .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
762                .collect();
763
764            if let Some((spec_name, dynamic_args)) = try_specialize_call(
765                *function, &new_args, func_defs, registry, expr_arena, stmt_arena, interner, effect_env,
766            ) {
767                expr_arena.alloc(Expr::Call {
768                    function: spec_name,
769                    args: dynamic_args,
770                })
771            } else {
772                let changed = new_args.iter().zip(args.iter())
773                    .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
774                if changed {
775                    expr_arena.alloc(Expr::Call { function: *function, args: new_args })
776                } else {
777                    expr
778                }
779            }
780        }
781        Expr::BinaryOp { op, left, right } => {
782            let new_left = specialize_in_expr(left, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
783            let new_right = specialize_in_expr(right, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
784            if std::ptr::eq(new_left as *const _, *left as *const _)
785                && std::ptr::eq(new_right as *const _, *right as *const _) {
786                expr
787            } else {
788                expr_arena.alloc(Expr::BinaryOp { op: *op, left: new_left, right: new_right })
789            }
790        }
791        Expr::Not { operand } => {
792            let new_op = specialize_in_expr(operand, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
793            if std::ptr::eq(new_op as *const _, *operand as *const _) {
794                expr
795            } else {
796                expr_arena.alloc(Expr::Not { operand: new_op })
797            }
798        }
799        _ => expr,
800    }
801}
802
803fn specialize_in_stmt<'a>(
804    stmt: Stmt<'a>,
805    func_defs: &HashMap<Symbol, FuncInfo<'a>>,
806    registry: &mut SpecRegistry<'a>,
807    expr_arena: &'a Arena<Expr<'a>>,
808    stmt_arena: &'a Arena<Stmt<'a>>,
809    interner: &mut Interner,
810    effect_env: Option<&EffectEnv>,
811) -> Stmt<'a> {
812    match stmt {
813        Stmt::Let { var, value, mutable, ty } => Stmt::Let {
814            var,
815            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
816            mutable,
817            ty,
818        },
819        Stmt::Set { target, value } => Stmt::Set {
820            target,
821            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
822        },
823        Stmt::Return { value } => Stmt::Return {
824            value: value.map(|v| specialize_in_expr(v, func_defs, registry, expr_arena, stmt_arena, interner, effect_env)),
825        },
826        Stmt::Show { object, recipient } => Stmt::Show {
827            object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
828            recipient,
829        },
830        Stmt::Call { function, args } => {
831            let new_args: Vec<&'a Expr<'a>> = args.iter()
832                .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
833                .collect();
834
835            if let Some((spec_name, dynamic_args)) = try_specialize_call(
836                function, &new_args, func_defs, registry, expr_arena, stmt_arena, interner, effect_env,
837            ) {
838                Stmt::Call { function: spec_name, args: dynamic_args }
839            } else {
840                Stmt::Call { function, args: new_args }
841            }
842        }
843        Stmt::If { cond, then_block, else_block } => {
844            let new_cond = specialize_in_expr(cond, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
845            let new_then: Vec<Stmt<'a>> = then_block.iter().cloned()
846                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
847                .collect();
848            let new_else = else_block.map(|eb| {
849                let stmts: Vec<Stmt<'a>> = eb.iter().cloned()
850                    .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
851                    .collect();
852                stmt_arena.alloc_slice(stmts) as &[Stmt<'a>]
853            });
854            Stmt::If {
855                cond: new_cond,
856                then_block: stmt_arena.alloc_slice(new_then),
857                else_block: new_else,
858            }
859        }
860        Stmt::While { cond, body, decreasing } => {
861            let new_cond = specialize_in_expr(cond, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
862            let new_body: Vec<Stmt<'a>> = body.iter().cloned()
863                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
864                .collect();
865            Stmt::While {
866                cond: new_cond,
867                body: stmt_arena.alloc_slice(new_body),
868                decreasing,
869            }
870        }
871        Stmt::Repeat { pattern, iterable, body } => {
872            let new_iter = specialize_in_expr(iterable, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
873            let new_body: Vec<Stmt<'a>> = body.iter().cloned()
874                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
875                .collect();
876            Stmt::Repeat {
877                pattern,
878                iterable: new_iter,
879                body: stmt_arena.alloc_slice(new_body),
880            }
881        }
882        Stmt::SetIndex { collection, index, value } => Stmt::SetIndex {
883            collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
884            index: specialize_in_expr(index, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
885            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
886        },
887        Stmt::Push { value, collection } => Stmt::Push {
888            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
889            collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
890        },
891        Stmt::SetField { object, field, value } => Stmt::SetField {
892            object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
893            field,
894            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
895        },
896        Stmt::Give { object, recipient } => Stmt::Give {
897            object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
898            recipient: specialize_in_expr(recipient, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
899        },
900        Stmt::Add { value, collection } => Stmt::Add {
901            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
902            collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
903        },
904        Stmt::Remove { value, collection } => Stmt::Remove {
905            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
906            collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
907        },
908        Stmt::Inspect { target, arms, has_otherwise } => {
909            let new_target = specialize_in_expr(target, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
910            let new_arms: Vec<_> = arms.into_iter().map(|arm| {
911                let new_body: Vec<Stmt<'a>> = arm.body.iter().cloned()
912                    .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
913                    .collect();
914                crate::ast::stmt::MatchArm {
915                    enum_name: arm.enum_name,
916                    variant: arm.variant,
917                    bindings: arm.bindings,
918                    body: stmt_arena.alloc_slice(new_body),
919                }
920            }).collect();
921            Stmt::Inspect {
922                target: new_target,
923                arms: new_arms,
924                has_otherwise,
925            }
926        }
927        Stmt::RuntimeAssert { condition, hard } => Stmt::RuntimeAssert {
928            condition: specialize_in_expr(condition, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
929            hard,
930        },
931        Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
932            if is_native {
933                return Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags };
934            }
935            let new_body: Vec<Stmt<'a>> = body.iter().cloned()
936                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
937                .collect();
938            Stmt::FunctionDef {
939                name,
940                params,
941                generics,
942                body: stmt_arena.alloc_slice(new_body),
943                return_type,
944                is_native,
945                native_path,
946                is_exported,
947                export_target,
948                opt_flags,
949            }
950        }
951        // ---- Deep specialization across concurrency boundaries ----
952        // Descend into Concurrent/Parallel/Zone/Select bodies and the expression
953        // arguments of the Go-like concurrency statements to specialize the pure calls
954        // within. Each statement is rebuilt as the SAME variant in the SAME order — the
955        // boundary is never folded across, and no effect is reordered or duplicated.
956        Stmt::Concurrent { tasks } => {
957            let new_tasks: Vec<Stmt<'a>> = tasks.iter().cloned()
958                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
959                .collect();
960            Stmt::Concurrent { tasks: stmt_arena.alloc_slice(new_tasks) }
961        }
962        Stmt::Parallel { tasks } => {
963            let new_tasks: Vec<Stmt<'a>> = tasks.iter().cloned()
964                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
965                .collect();
966            Stmt::Parallel { tasks: stmt_arena.alloc_slice(new_tasks) }
967        }
968        Stmt::Zone { name, capacity, source_file, body } => {
969            let new_body: Vec<Stmt<'a>> = body.iter().cloned()
970                .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
971                .collect();
972            Stmt::Zone { name, capacity, source_file, body: stmt_arena.alloc_slice(new_body) }
973        }
974        Stmt::Select { branches } => {
975            let new_branches: Vec<crate::ast::stmt::SelectBranch<'a>> = branches.into_iter().map(|b| match b {
976                crate::ast::stmt::SelectBranch::Receive { var, pipe, body } => {
977                    let new_body: Vec<Stmt<'a>> = body.iter().cloned()
978                        .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
979                        .collect();
980                    crate::ast::stmt::SelectBranch::Receive {
981                        var,
982                        pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
983                        body: stmt_arena.alloc_slice(new_body),
984                    }
985                }
986                crate::ast::stmt::SelectBranch::Timeout { milliseconds, body } => {
987                    let new_body: Vec<Stmt<'a>> = body.iter().cloned()
988                        .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
989                        .collect();
990                    crate::ast::stmt::SelectBranch::Timeout {
991                        milliseconds: specialize_in_expr(milliseconds, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
992                        body: stmt_arena.alloc_slice(new_body),
993                    }
994                }
995            }).collect();
996            Stmt::Select { branches: new_branches }
997        }
998        Stmt::SendPipe { value, pipe } => Stmt::SendPipe {
999            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1000            pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1001        },
1002        Stmt::TrySendPipe { value, pipe, result } => Stmt::TrySendPipe {
1003            value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1004            pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1005            result,
1006        },
1007        Stmt::ReceivePipe { var, pipe } => Stmt::ReceivePipe {
1008            var,
1009            pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1010        },
1011        Stmt::TryReceivePipe { var, pipe } => Stmt::TryReceivePipe {
1012            var,
1013            pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1014        },
1015        Stmt::LaunchTask { function, args } => Stmt::LaunchTask {
1016            function,
1017            args: args.iter()
1018                .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
1019                .collect(),
1020        },
1021        Stmt::LaunchTaskWithHandle { handle, function, args } => Stmt::LaunchTaskWithHandle {
1022            handle,
1023            function,
1024            args: args.iter()
1025                .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
1026                .collect(),
1027        },
1028        Stmt::StopTask { handle } => Stmt::StopTask {
1029            handle: specialize_in_expr(handle, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1030        },
1031        other => other,
1032    }
1033}
1034
1035pub fn specialize_stmts<'a>(
1036    stmts: Vec<Stmt<'a>>,
1037    expr_arena: &'a Arena<Expr<'a>>,
1038    stmt_arena: &'a Arena<Stmt<'a>>,
1039    interner: &mut Interner,
1040) -> (Vec<Stmt<'a>>, usize) {
1041    let mut variant_count = HashMap::new();
1042    specialize_stmts_with_state(stmts, expr_arena, stmt_arena, interner, &mut variant_count, None)
1043}
1044
1045pub fn specialize_stmts_with_state<'a>(
1046    stmts: Vec<Stmt<'a>>,
1047    expr_arena: &'a Arena<Expr<'a>>,
1048    stmt_arena: &'a Arena<Stmt<'a>>,
1049    interner: &mut Interner,
1050    persistent_variant_count: &mut HashMap<Symbol, usize>,
1051    bta_cache: Option<&super::bta::BtaCache>,
1052) -> (Vec<Stmt<'a>>, usize) {
1053    let effect_env = EffectEnv::from_stmts(&stmts, interner);
1054    let func_defs = collect_func_defs(&stmts);
1055    let mut registry = SpecRegistry::new();
1056    registry.variant_count = persistent_variant_count.clone();
1057    if let Some(cache) = bta_cache {
1058        registry.bta_cache = Some(cache.clone());
1059    }
1060
1061    let specialized: Vec<Stmt<'a>> = stmts.into_iter()
1062        .map(|stmt| specialize_in_stmt(stmt, &func_defs, &mut registry, expr_arena, stmt_arena, interner, Some(&effect_env)))
1063        .collect();
1064
1065    let changes = registry.new_funcs.len();
1066    *persistent_variant_count = registry.variant_count;
1067    let mut result = registry.new_funcs;
1068    result.extend(specialized);
1069    (result, changes)
1070}
1071
1072/// Remove identity bindings and trivial patterns from residual code.
1073///
1074/// - `Let x = x` → removed
1075/// - `Let x = lit; Return x` → `Return lit`
1076/// - Single-arm `Inspect` with Otherwise only → inline the body
1077/// - Iterates to fixpoint (max 4 passes)
1078pub fn cleanup_identities<'a>(
1079    stmts: Vec<Stmt<'a>>,
1080    expr_arena: &'a Arena<Expr<'a>>,
1081    stmt_arena: &'a Arena<Stmt<'a>>,
1082) -> Vec<Stmt<'a>> {
1083    let mut current = stmts;
1084    for _ in 0..4 {
1085        let next = cleanup_pass(&current, expr_arena, stmt_arena);
1086        if next.len() == current.len() {
1087            // Quick structural check for fixpoint
1088            let mut same = true;
1089            for (a, b) in next.iter().zip(current.iter()) {
1090                if !stmt_structurally_equal(a, b) {
1091                    same = false;
1092                    break;
1093                }
1094            }
1095            if same {
1096                return next;
1097            }
1098        }
1099        current = next;
1100    }
1101    current
1102}
1103
1104fn cleanup_pass<'a>(
1105    stmts: &[Stmt<'a>],
1106    expr_arena: &'a Arena<Expr<'a>>,
1107    stmt_arena: &'a Arena<Stmt<'a>>,
1108) -> Vec<Stmt<'a>> {
1109    let mut result = Vec::with_capacity(stmts.len());
1110
1111    for (i, stmt) in stmts.iter().enumerate() {
1112        match stmt {
1113            // Remove Let x = x (identity binding)
1114            Stmt::Let { var, value: Expr::Identifier(src), .. } if var == src => {
1115                continue;
1116            }
1117            // Collapse Let x = lit; Return x → Return lit
1118            Stmt::Let { var, value, mutable: false, .. } => {
1119                if let Some(Stmt::Return { value: Some(Expr::Identifier(ret_var)) }) = stmts.get(i + 1) {
1120                    if var == ret_var {
1121                        if let Expr::Literal(_) = value {
1122                            result.push(Stmt::Return { value: Some(value) });
1123                            // Skip the next Return statement too — handled below
1124                            continue;
1125                        }
1126                    }
1127                }
1128                // Recurse into sub-blocks
1129                result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1130            }
1131            // Skip Return that was already folded into a Let
1132            Stmt::Return { value: Some(Expr::Identifier(ret_var)) } => {
1133                if i > 0 {
1134                    if let Stmt::Let { var, value: Expr::Literal(_), mutable: false, .. } = &stmts[i - 1] {
1135                        if var == ret_var {
1136                            continue;
1137                        }
1138                    }
1139                }
1140                result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1141            }
1142            // Single-arm Inspect with only Otherwise → inline body
1143            Stmt::Inspect { arms, has_otherwise: true, .. } if arms.len() == 1 => {
1144                let arm = &arms[0];
1145                if arm.variant.is_none() {
1146                    // Otherwise-only: inline body
1147                    for s in arm.body {
1148                        result.push(cleanup_stmt(s, expr_arena, stmt_arena));
1149                    }
1150                    continue;
1151                }
1152                result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1153            }
1154            _ => {
1155                result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1156            }
1157        }
1158    }
1159
1160    result
1161}
1162
1163fn cleanup_stmt<'a>(
1164    stmt: &Stmt<'a>,
1165    expr_arena: &'a Arena<Expr<'a>>,
1166    stmt_arena: &'a Arena<Stmt<'a>>,
1167) -> Stmt<'a> {
1168    match stmt {
1169        Stmt::If { cond, then_block, else_block } => {
1170            let new_then = cleanup_pass(then_block, expr_arena, stmt_arena);
1171            let new_else = else_block.map(|eb| {
1172                let cleaned = cleanup_pass(eb, expr_arena, stmt_arena);
1173                stmt_arena.alloc_slice(cleaned) as &[Stmt<'a>]
1174            });
1175            Stmt::If {
1176                cond,
1177                then_block: stmt_arena.alloc_slice(new_then),
1178                else_block: new_else,
1179            }
1180        }
1181        Stmt::While { cond, body, decreasing } => {
1182            let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1183            Stmt::While {
1184                cond,
1185                body: stmt_arena.alloc_slice(new_body),
1186                decreasing: *decreasing,
1187            }
1188        }
1189        Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
1190            if *is_native {
1191                return stmt.clone();
1192            }
1193            let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1194            Stmt::FunctionDef {
1195                name: *name,
1196                params: params.clone(),
1197                generics: generics.clone(),
1198                body: stmt_arena.alloc_slice(new_body),
1199                return_type: *return_type,
1200                is_native: *is_native,
1201                native_path: *native_path,
1202                is_exported: *is_exported,
1203                export_target: *export_target,
1204                opt_flags: opt_flags.clone(),
1205            }
1206        }
1207        Stmt::Repeat { pattern, iterable, body } => {
1208            let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1209            Stmt::Repeat {
1210                pattern: pattern.clone(),
1211                iterable,
1212                body: stmt_arena.alloc_slice(new_body),
1213            }
1214        }
1215        other => other.clone(),
1216    }
1217}
1218
1219fn stmt_structurally_equal(a: &Stmt, b: &Stmt) -> bool {
1220    std::mem::discriminant(a) == std::mem::discriminant(b)
1221}