Skip to main content

yulang_native/
cps_lower.rs

1use std::cell::Cell;
2use std::collections::{HashMap, HashSet};
3use std::fmt;
4use std::rc::Rc;
5
6use yulang_runtime as runtime;
7use yulang_typed_ir as typed_ir;
8
9use crate::cps_capture::infer_cps_captures;
10use crate::cps_effectful_calls::reify_effectful_direct_calls;
11use crate::cps_ir::{
12    CpsContinuation, CpsContinuationId, CpsFunction, CpsHandler, CpsHandlerArm, CpsHandlerEnv,
13    CpsHandlerId, CpsLiteral, CpsModule, CpsRecordField, CpsShotKind, CpsStmt, CpsTerminator,
14    CpsValueId,
15};
16
17pub type CpsLowerResult<T> = Result<T, CpsLowerError>;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum CpsLowerError {
21    UnsupportedRoot {
22        root: runtime::Root,
23    },
24    MissingRootExpr {
25        index: usize,
26    },
27    UnsupportedExpr {
28        kind: &'static str,
29    },
30    UnsupportedFreeVar {
31        path: typed_ir::Path,
32    },
33    UnsupportedBareEffectOp {
34        path: typed_ir::Path,
35    },
36    UnsupportedPattern {
37        kind: &'static str,
38    },
39    UnsupportedBinding {
40        path: typed_ir::Path,
41        reason: &'static str,
42    },
43    PrimitiveArityMismatch {
44        op: typed_ir::PrimitiveOp,
45        expected: usize,
46        actual: usize,
47    },
48    CallArityMismatch {
49        target: String,
50        expected: usize,
51        actual: usize,
52    },
53}
54
55impl fmt::Display for CpsLowerError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            CpsLowerError::UnsupportedRoot { root } => {
59                write!(f, "CPS lowering does not support root {root:?} yet")
60            }
61            CpsLowerError::MissingRootExpr { index } => {
62                write!(f, "runtime module has no root expression at index {index}")
63            }
64            CpsLowerError::UnsupportedExpr { kind } => {
65                write!(f, "CPS lowering does not support {kind} expressions yet")
66            }
67            CpsLowerError::UnsupportedFreeVar { path } => {
68                write!(
69                    f,
70                    "CPS lowering does not support free variable `{}` yet",
71                    path_name(path)
72                )
73            }
74            CpsLowerError::UnsupportedBareEffectOp { path } => {
75                write!(
76                    f,
77                    "CPS lowering does not support bare effect operation `{}` yet",
78                    path_name(path)
79                )
80            }
81            CpsLowerError::UnsupportedPattern { kind } => {
82                write!(f, "CPS lowering does not support {kind} patterns yet")
83            }
84            CpsLowerError::UnsupportedBinding { path, reason } => {
85                write!(
86                    f,
87                    "CPS lowering does not support binding {} yet: {reason}",
88                    path_name(path)
89                )
90            }
91            CpsLowerError::PrimitiveArityMismatch {
92                op,
93                expected,
94                actual,
95            } => write!(
96                f,
97                "CPS lowering expected {expected} arguments for primitive {op:?}, got {actual}"
98            ),
99            CpsLowerError::CallArityMismatch {
100                target,
101                expected,
102                actual,
103            } => write!(
104                f,
105                "CPS lowering expected {expected} arguments for call to {target}, got {actual}"
106            ),
107        }
108    }
109}
110
111impl std::error::Error for CpsLowerError {}
112
113pub fn lower_cps_module(module: &runtime::Module) -> CpsLowerResult<CpsModule> {
114    let binding_table = module
115        .bindings
116        .iter()
117        .map(|binding| (binding.name.clone(), binding))
118        .collect::<HashMap<_, _>>();
119    let function_table = module
120        .bindings
121        .iter()
122        .filter_map(binding_function_info)
123        .collect::<HashMap<_, _>>();
124    let reachable = reachable_binding_paths(module, &function_table, &binding_table);
125    let functions = module
126        .bindings
127        .iter()
128        .filter(|binding| reachable.contains(&binding.name))
129        .map(|binding| lower_binding(binding, &function_table, &binding_table))
130        .collect::<CpsLowerResult<Vec<_>>>()?;
131
132    let mut roots = Vec::new();
133    for root in &module.roots {
134        match root {
135            runtime::Root::Expr(index) => {
136                let expr = module
137                    .root_exprs
138                    .get(*index)
139                    .ok_or(CpsLowerError::MissingRootExpr { index: *index })?;
140                roots.push(
141                    FunctionLowerer::new(
142                        format!("root_{index}"),
143                        &function_table,
144                        &binding_table,
145                        Vec::new(),
146                        FunctionLoweringTraits::default(),
147                    )
148                    .lower_root(expr)?,
149                );
150            }
151            runtime::Root::Binding(_) => {
152                return Err(CpsLowerError::UnsupportedRoot { root: root.clone() });
153            }
154        }
155    }
156    let mut module = CpsModule { functions, roots };
157    reify_effectful_direct_calls(&mut module);
158    infer_cps_captures(&mut module);
159    make_handler_ids_global(&mut module);
160    if std::env::var_os("YULANG_DUMP_CPS").is_some() {
161        eprintln!("{module:#?}");
162    }
163    Ok(module)
164}
165
166fn make_handler_ids_global(module: &mut CpsModule) {
167    let mut next_handler = 0;
168    for function in module.functions.iter_mut().chain(&mut module.roots) {
169        let offset = next_handler;
170        next_handler += function.handlers.len();
171        if offset == 0 {
172            continue;
173        }
174        for handler in &mut function.handlers {
175            handler.id.0 += offset;
176        }
177        for continuation in &mut function.continuations {
178            for stmt in &mut continuation.stmts {
179                match stmt {
180                    CpsStmt::ResumeWithHandler { handler, .. }
181                    | CpsStmt::InstallHandler { handler, .. }
182                    | CpsStmt::UninstallHandler { handler } => {
183                        remap_handler_id(handler, offset);
184                    }
185                    _ => {}
186                }
187            }
188            if let CpsTerminator::Perform { handler, .. } = &mut continuation.terminator {
189                remap_handler_id(handler, offset);
190            }
191        }
192    }
193}
194
195fn remap_handler_id(handler: &mut CpsHandlerId, offset: usize) {
196    if *handler != dynamic_handler_id() {
197        handler.0 += offset;
198    }
199}
200
201fn reachable_binding_paths(
202    module: &runtime::Module,
203    functions: &HashMap<typed_ir::Path, FunctionInfo>,
204    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
205) -> HashSet<typed_ir::Path> {
206    let binding_bodies = module
207        .bindings
208        .iter()
209        .map(|binding| (binding.name.clone(), &binding.body))
210        .collect::<HashMap<_, _>>();
211    let mut reachable = HashSet::new();
212    let mut stack = Vec::new();
213    for root in &module.roots {
214        match root {
215            runtime::Root::Expr(index) => {
216                if let Some(expr) = module.root_exprs.get(*index) {
217                    let mut visiting_values = HashSet::new();
218                    collect_expr_direct_calls_inner(
219                        expr,
220                        functions,
221                        bindings,
222                        &mut stack,
223                        &mut visiting_values,
224                    );
225                }
226            }
227            runtime::Root::Binding(path) => stack.push(path.clone()),
228        }
229    }
230
231    while let Some(path) = stack.pop() {
232        if !reachable.insert(path.clone()) {
233            continue;
234        }
235        let Some(body) = binding_bodies.get(&path) else {
236            continue;
237        };
238        let mut visiting_values = HashSet::new();
239        collect_expr_direct_calls_inner(
240            body,
241            functions,
242            bindings,
243            &mut stack,
244            &mut visiting_values,
245        );
246    }
247    reachable
248}
249
250fn collect_expr_direct_calls_inner(
251    expr: &runtime::Expr,
252    functions: &HashMap<typed_ir::Path, FunctionInfo>,
253    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
254    out: &mut Vec<typed_ir::Path>,
255    visiting_values: &mut HashSet<typed_ir::Path>,
256) {
257    if runtime_type_is_bool_value(&expr.ty)
258        && let Ok(Some((target, _info, args))) = direct_apply_path(expr, functions)
259        && handler_wrapper_args_have_unconsumed_effects(target, &args, bindings)
260    {
261        out.push(target.clone());
262    }
263    if let Some((body, arms, _consumes)) = inline_thunk_handler_apply(expr, functions, bindings) {
264        collect_expr_direct_calls_inner(&body, functions, bindings, out, visiting_values);
265        for arm in &arms {
266            collect_pattern_direct_calls(&arm.payload, functions, bindings, out, visiting_values);
267            if let Some(guard) = &arm.guard {
268                collect_expr_direct_calls_inner(guard, functions, bindings, out, visiting_values);
269            }
270            collect_expr_direct_calls_inner(&arm.body, functions, bindings, out, visiting_values);
271        }
272        return;
273    }
274    if let Ok(Some((target, _info, args))) = direct_apply_path(expr, functions) {
275        if fail_prefix_path(target)
276            || bindings
277                .get(target)
278                .is_some_and(|binding| binding_is_throw_forwarder(binding))
279        {
280            for arg in args {
281                collect_expr_direct_calls_inner(arg, functions, bindings, out, visiting_values);
282            }
283            return;
284        }
285        if !matches!(expr.ty, runtime::Type::Thunk { .. })
286            && args.iter().any(|arg| is_inline_argument(arg))
287        {
288            if let Some(binding) = bindings.get(&target) {
289                let mut visiting = HashSet::new();
290                let mut memo = HashMap::new();
291                if binding_may_perform_when_called(
292                    &target,
293                    functions,
294                    bindings,
295                    &mut visiting,
296                    &mut memo,
297                ) || (runtime_type_is_bool_value(&expr.ty)
298                    && handler_wrapper_args_have_unconsumed_effects(&target, &args, bindings))
299                {
300                    out.push(target.clone());
301                }
302                if binding_has_self_direct_call(&target, &binding.body, functions) {
303                    out.push(target.clone());
304                } else {
305                    collect_expr_direct_calls_inner(
306                        &binding.body,
307                        functions,
308                        bindings,
309                        out,
310                        visiting_values,
311                    );
312                }
313            }
314        } else {
315            out.push(target.clone());
316        }
317    }
318    match &expr.kind {
319        runtime::ExprKind::Lambda { body, .. } => {
320            collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
321        }
322        runtime::ExprKind::Apply { callee, arg, .. } => {
323            collect_expr_direct_calls_inner(callee, functions, bindings, out, visiting_values);
324            collect_expr_direct_calls_inner(arg, functions, bindings, out, visiting_values);
325        }
326        runtime::ExprKind::If {
327            cond,
328            then_branch,
329            else_branch,
330            ..
331        } => {
332            collect_expr_direct_calls_inner(cond, functions, bindings, out, visiting_values);
333            collect_expr_direct_calls_inner(then_branch, functions, bindings, out, visiting_values);
334            collect_expr_direct_calls_inner(else_branch, functions, bindings, out, visiting_values);
335        }
336        runtime::ExprKind::Tuple(items) => {
337            for item in items {
338                collect_expr_direct_calls_inner(item, functions, bindings, out, visiting_values);
339            }
340        }
341        runtime::ExprKind::Record { fields, spread } => {
342            for field in fields {
343                collect_expr_direct_calls_inner(
344                    &field.value,
345                    functions,
346                    bindings,
347                    out,
348                    visiting_values,
349                );
350            }
351            if let Some(spread) = spread {
352                match spread {
353                    runtime::RecordSpreadExpr::Head(expr)
354                    | runtime::RecordSpreadExpr::Tail(expr) => {
355                        collect_expr_direct_calls_inner(
356                            expr,
357                            functions,
358                            bindings,
359                            out,
360                            visiting_values,
361                        );
362                    }
363                }
364            }
365        }
366        runtime::ExprKind::Variant {
367            value: Some(value), ..
368        } => collect_expr_direct_calls_inner(value, functions, bindings, out, visiting_values),
369        runtime::ExprKind::Select { base, .. } => {
370            collect_expr_direct_calls_inner(base, functions, bindings, out, visiting_values);
371        }
372        runtime::ExprKind::Match {
373            scrutinee, arms, ..
374        } => {
375            collect_expr_direct_calls_inner(scrutinee, functions, bindings, out, visiting_values);
376            for arm in arms {
377                if let Some(guard) = &arm.guard {
378                    collect_expr_direct_calls_inner(
379                        guard,
380                        functions,
381                        bindings,
382                        out,
383                        visiting_values,
384                    );
385                }
386                collect_expr_direct_calls_inner(
387                    &arm.body,
388                    functions,
389                    bindings,
390                    out,
391                    visiting_values,
392                );
393                collect_pattern_direct_calls(
394                    &arm.pattern,
395                    functions,
396                    bindings,
397                    out,
398                    visiting_values,
399                );
400            }
401        }
402        runtime::ExprKind::Block { stmts, tail } => {
403            for (index, stmt) in stmts.iter().enumerate() {
404                match stmt {
405                    runtime::Stmt::Let { pattern, value } => {
406                        if unused_pure_let(
407                            pattern,
408                            value,
409                            &stmts[index + 1..],
410                            tail.as_deref(),
411                            functions,
412                            bindings,
413                        ) {
414                            continue;
415                        }
416                        collect_pattern_direct_calls(
417                            pattern,
418                            functions,
419                            bindings,
420                            out,
421                            visiting_values,
422                        );
423                        collect_expr_direct_calls_inner(
424                            value,
425                            functions,
426                            bindings,
427                            out,
428                            visiting_values,
429                        );
430                    }
431                    runtime::Stmt::Expr(expr) => {
432                        collect_expr_direct_calls_inner(
433                            expr,
434                            functions,
435                            bindings,
436                            out,
437                            visiting_values,
438                        );
439                    }
440                    runtime::Stmt::Module { body, .. } => {
441                        collect_expr_direct_calls_inner(
442                            body,
443                            functions,
444                            bindings,
445                            out,
446                            visiting_values,
447                        );
448                    }
449                }
450            }
451            if let Some(tail) = tail {
452                collect_expr_direct_calls_inner(tail, functions, bindings, out, visiting_values);
453            }
454        }
455        runtime::ExprKind::Handle { body, arms, .. } => {
456            collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
457            for arm in arms {
458                collect_pattern_direct_calls(
459                    &arm.payload,
460                    functions,
461                    bindings,
462                    out,
463                    visiting_values,
464                );
465                if let Some(guard) = &arm.guard {
466                    collect_expr_direct_calls_inner(
467                        guard,
468                        functions,
469                        bindings,
470                        out,
471                        visiting_values,
472                    );
473                }
474                collect_expr_direct_calls_inner(
475                    &arm.body,
476                    functions,
477                    bindings,
478                    out,
479                    visiting_values,
480                );
481            }
482        }
483        runtime::ExprKind::BindHere { expr }
484        | runtime::ExprKind::Thunk { expr, .. }
485        | runtime::ExprKind::LocalPushId { body: expr, .. }
486        | runtime::ExprKind::AddId { thunk: expr, .. }
487        | runtime::ExprKind::Coerce { expr, .. }
488        | runtime::ExprKind::Pack { expr, .. } => {
489            collect_expr_direct_calls_inner(expr, functions, bindings, out, visiting_values);
490        }
491        runtime::ExprKind::Var(path) => {
492            if functions.contains_key(path) {
493                out.push(path.clone());
494            }
495            if let Some(binding) = bindings.get(path)
496                && let Some(body) = binding_value_body(binding)
497                && !matches!(&body.kind, runtime::ExprKind::Var(inner) if inner == path)
498                && visiting_values.insert(path.clone())
499            {
500                collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
501                visiting_values.remove(path);
502            }
503        }
504        runtime::ExprKind::EffectOp(_)
505        | runtime::ExprKind::PrimitiveOp(_)
506        | runtime::ExprKind::Lit(_)
507        | runtime::ExprKind::Variant { value: None, .. }
508        | runtime::ExprKind::PeekId
509        | runtime::ExprKind::FindId { .. } => {}
510    }
511}
512
513fn binding_has_self_direct_call(
514    target: &typed_ir::Path,
515    expr: &runtime::Expr,
516    functions: &HashMap<typed_ir::Path, FunctionInfo>,
517) -> bool {
518    if let Some((called, _)) = direct_apply_target(expr, functions)
519        && &called == target
520    {
521        return true;
522    }
523    match &expr.kind {
524        runtime::ExprKind::Lambda { body, .. }
525        | runtime::ExprKind::Thunk { expr: body, .. }
526        | runtime::ExprKind::BindHere { expr: body }
527        | runtime::ExprKind::LocalPushId { body, .. }
528        | runtime::ExprKind::AddId { thunk: body, .. }
529        | runtime::ExprKind::Coerce { expr: body, .. }
530        | runtime::ExprKind::Pack { expr: body, .. } => {
531            binding_has_self_direct_call(target, body, functions)
532        }
533        runtime::ExprKind::Apply { callee, arg, .. } => {
534            binding_has_self_direct_call(target, callee, functions)
535                || binding_has_self_direct_call(target, arg, functions)
536        }
537        runtime::ExprKind::If {
538            cond,
539            then_branch,
540            else_branch,
541            ..
542        } => {
543            binding_has_self_direct_call(target, cond, functions)
544                || binding_has_self_direct_call(target, then_branch, functions)
545                || binding_has_self_direct_call(target, else_branch, functions)
546        }
547        runtime::ExprKind::Tuple(items) => items
548            .iter()
549            .any(|item| binding_has_self_direct_call(target, item, functions)),
550        runtime::ExprKind::Record { fields, spread } => {
551            fields
552                .iter()
553                .any(|field| binding_has_self_direct_call(target, &field.value, functions))
554                || spread.as_ref().is_some_and(|spread| match spread {
555                    runtime::RecordSpreadExpr::Head(expr)
556                    | runtime::RecordSpreadExpr::Tail(expr) => {
557                        binding_has_self_direct_call(target, expr, functions)
558                    }
559                })
560        }
561        runtime::ExprKind::Variant {
562            value: Some(value), ..
563        }
564        | runtime::ExprKind::Select { base: value, .. } => {
565            binding_has_self_direct_call(target, value, functions)
566        }
567        runtime::ExprKind::Match {
568            scrutinee, arms, ..
569        } => {
570            binding_has_self_direct_call(target, scrutinee, functions)
571                || arms.iter().any(|arm| {
572                    arm.guard
573                        .as_ref()
574                        .is_some_and(|guard| binding_has_self_direct_call(target, guard, functions))
575                        || binding_has_self_direct_call(target, &arm.body, functions)
576                })
577        }
578        runtime::ExprKind::Handle { body, arms, .. } => {
579            binding_has_self_direct_call(target, body, functions)
580                || arms.iter().any(|arm| {
581                    arm.guard
582                        .as_ref()
583                        .is_some_and(|guard| binding_has_self_direct_call(target, guard, functions))
584                        || binding_has_self_direct_call(target, &arm.body, functions)
585                })
586        }
587        runtime::ExprKind::Block { stmts, tail } => {
588            stmts.iter().any(|stmt| match stmt {
589                runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
590                    binding_has_self_direct_call(target, value, functions)
591                }
592                runtime::Stmt::Module { body, .. } => {
593                    binding_has_self_direct_call(target, body, functions)
594                }
595            }) || tail
596                .as_ref()
597                .is_some_and(|tail| binding_has_self_direct_call(target, tail, functions))
598        }
599        runtime::ExprKind::Var(_)
600        | runtime::ExprKind::EffectOp(_)
601        | runtime::ExprKind::PrimitiveOp(_)
602        | runtime::ExprKind::Lit(_)
603        | runtime::ExprKind::Variant { value: None, .. }
604        | runtime::ExprKind::PeekId
605        | runtime::ExprKind::FindId { .. } => false,
606    }
607}
608
609fn collect_expr_performed_effects(expr: &runtime::Expr) -> Vec<typed_ir::Path> {
610    let mut effects = Vec::new();
611    collect_expr_performed_effects_into(expr, &mut effects);
612    effects
613}
614
615fn collect_expr_performed_effects_into(expr: &runtime::Expr, out: &mut Vec<typed_ir::Path>) {
616    if let Some(request) = effect_apply_nested(expr)
617        && !out.iter().any(|seen| seen == &request.effect)
618    {
619        out.push(request.effect);
620    }
621    match &expr.kind {
622        runtime::ExprKind::Lambda { body, .. }
623        | runtime::ExprKind::Thunk { expr: body, .. }
624        | runtime::ExprKind::LocalPushId { body, .. }
625        | runtime::ExprKind::BindHere { expr: body }
626        | runtime::ExprKind::AddId { thunk: body, .. }
627        | runtime::ExprKind::Coerce { expr: body, .. }
628        | runtime::ExprKind::Pack { expr: body, .. } => {
629            collect_expr_performed_effects_into(body, out);
630        }
631        runtime::ExprKind::Apply { callee, arg, .. } => {
632            collect_expr_performed_effects_into(callee, out);
633            collect_expr_performed_effects_into(arg, out);
634        }
635        runtime::ExprKind::If {
636            cond,
637            then_branch,
638            else_branch,
639            ..
640        } => {
641            collect_expr_performed_effects_into(cond, out);
642            collect_expr_performed_effects_into(then_branch, out);
643            collect_expr_performed_effects_into(else_branch, out);
644        }
645        runtime::ExprKind::Match {
646            scrutinee, arms, ..
647        } => {
648            collect_expr_performed_effects_into(scrutinee, out);
649            for arm in arms {
650                if let Some(guard) = &arm.guard {
651                    collect_expr_performed_effects_into(guard, out);
652                }
653                collect_expr_performed_effects_into(&arm.body, out);
654            }
655        }
656        runtime::ExprKind::Handle { body, arms, .. } => {
657            collect_expr_performed_effects_into(body, out);
658            for arm in arms {
659                if let Some(guard) = &arm.guard {
660                    collect_expr_performed_effects_into(guard, out);
661                }
662                collect_expr_performed_effects_into(&arm.body, out);
663            }
664        }
665        runtime::ExprKind::Block { stmts, tail } => {
666            for stmt in stmts {
667                match stmt {
668                    runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
669                        collect_expr_performed_effects_into(value, out);
670                    }
671                    runtime::Stmt::Module { body, .. } => {
672                        collect_expr_performed_effects_into(body, out);
673                    }
674                }
675            }
676            if let Some(tail) = tail {
677                collect_expr_performed_effects_into(tail, out);
678            }
679        }
680        runtime::ExprKind::Tuple(items) => {
681            for item in items {
682                collect_expr_performed_effects_into(item, out);
683            }
684        }
685        runtime::ExprKind::Record { fields, spread } => {
686            for field in fields {
687                collect_expr_performed_effects_into(&field.value, out);
688            }
689            if let Some(spread) = spread {
690                match spread {
691                    runtime::RecordSpreadExpr::Head(expr)
692                    | runtime::RecordSpreadExpr::Tail(expr) => {
693                        collect_expr_performed_effects_into(expr, out);
694                    }
695                }
696            }
697        }
698        runtime::ExprKind::Variant {
699            value: Some(value), ..
700        }
701        | runtime::ExprKind::Select { base: value, .. } => {
702            collect_expr_performed_effects_into(value, out);
703        }
704        runtime::ExprKind::Var(_)
705        | runtime::ExprKind::EffectOp(_)
706        | runtime::ExprKind::PrimitiveOp(_)
707        | runtime::ExprKind::Lit(_)
708        | runtime::ExprKind::Variant { value: None, .. }
709        | runtime::ExprKind::PeekId
710        | runtime::ExprKind::FindId { .. } => {}
711    }
712}
713
714fn collect_pattern_direct_calls(
715    pattern: &runtime::Pattern,
716    functions: &HashMap<typed_ir::Path, FunctionInfo>,
717    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
718    out: &mut Vec<typed_ir::Path>,
719    visiting_values: &mut HashSet<typed_ir::Path>,
720) {
721    match pattern {
722        runtime::Pattern::Tuple { items, .. } => {
723            for item in items {
724                collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
725            }
726        }
727        runtime::Pattern::List {
728            prefix,
729            spread,
730            suffix,
731            ..
732        } => {
733            for item in prefix {
734                collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
735            }
736            if let Some(spread) = spread {
737                collect_pattern_direct_calls(spread, functions, bindings, out, visiting_values);
738            }
739            for item in suffix {
740                collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
741            }
742        }
743        runtime::Pattern::Record { fields, spread, .. } => {
744            for field in fields {
745                collect_pattern_direct_calls(
746                    &field.pattern,
747                    functions,
748                    bindings,
749                    out,
750                    visiting_values,
751                );
752                if let Some(default) = &field.default {
753                    collect_expr_direct_calls_inner(
754                        default,
755                        functions,
756                        bindings,
757                        out,
758                        visiting_values,
759                    );
760                }
761            }
762            if let Some(spread) = spread {
763                match spread {
764                    runtime::RecordSpreadPattern::Head(pattern)
765                    | runtime::RecordSpreadPattern::Tail(pattern) => {
766                        collect_pattern_direct_calls(
767                            pattern,
768                            functions,
769                            bindings,
770                            out,
771                            visiting_values,
772                        );
773                    }
774                }
775            }
776        }
777        runtime::Pattern::Variant {
778            value: Some(value), ..
779        }
780        | runtime::Pattern::As { pattern: value, .. } => {
781            collect_pattern_direct_calls(value, functions, bindings, out, visiting_values);
782        }
783        runtime::Pattern::Or { left, right, .. } => {
784            collect_pattern_direct_calls(left, functions, bindings, out, visiting_values);
785            collect_pattern_direct_calls(right, functions, bindings, out, visiting_values);
786        }
787        runtime::Pattern::Wildcard { .. }
788        | runtime::Pattern::Bind { .. }
789        | runtime::Pattern::Lit { .. }
790        | runtime::Pattern::Variant { value: None, .. } => {}
791    }
792}
793
794fn unused_pure_let(
795    pattern: &runtime::Pattern,
796    value: &runtime::Expr,
797    rest: &[runtime::Stmt],
798    tail: Option<&runtime::Expr>,
799    functions: &HashMap<typed_ir::Path, FunctionInfo>,
800    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
801) -> bool {
802    let bound = pattern_bound_paths(pattern);
803    !bound.is_empty()
804        && pure_unused_expr(value, functions, bindings, &mut HashSet::new())
805        && !stmts_or_tail_use_any_path(rest, tail, &bound)
806}
807
808fn pattern_bound_paths(pattern: &runtime::Pattern) -> HashSet<typed_ir::Path> {
809    let mut paths = HashSet::new();
810    collect_pattern_bound_paths(pattern, &mut paths);
811    paths
812}
813
814fn collect_pattern_bound_paths(pattern: &runtime::Pattern, out: &mut HashSet<typed_ir::Path>) {
815    match pattern {
816        runtime::Pattern::Bind { name, .. } => {
817            out.insert(typed_ir::Path::from_name(name.clone()));
818        }
819        runtime::Pattern::Tuple { items, .. } => {
820            for item in items {
821                collect_pattern_bound_paths(item, out);
822            }
823        }
824        runtime::Pattern::List {
825            prefix,
826            spread,
827            suffix,
828            ..
829        } => {
830            for item in prefix {
831                collect_pattern_bound_paths(item, out);
832            }
833            if let Some(spread) = spread {
834                collect_pattern_bound_paths(spread, out);
835            }
836            for item in suffix {
837                collect_pattern_bound_paths(item, out);
838            }
839        }
840        runtime::Pattern::Record { fields, spread, .. } => {
841            for field in fields {
842                collect_pattern_bound_paths(&field.pattern, out);
843            }
844            if let Some(spread) = spread {
845                match spread {
846                    runtime::RecordSpreadPattern::Head(pattern)
847                    | runtime::RecordSpreadPattern::Tail(pattern) => {
848                        collect_pattern_bound_paths(pattern, out);
849                    }
850                }
851            }
852        }
853        runtime::Pattern::Variant {
854            value: Some(value), ..
855        }
856        | runtime::Pattern::As { pattern: value, .. } => {
857            collect_pattern_bound_paths(value, out);
858        }
859        runtime::Pattern::Or { left, right, .. } => {
860            collect_pattern_bound_paths(left, out);
861            collect_pattern_bound_paths(right, out);
862        }
863        runtime::Pattern::Wildcard { .. }
864        | runtime::Pattern::Lit { .. }
865        | runtime::Pattern::Variant { value: None, .. } => {}
866    }
867}
868
869fn pure_unused_expr(
870    expr: &runtime::Expr,
871    functions: &HashMap<typed_ir::Path, FunctionInfo>,
872    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
873    stack: &mut HashSet<typed_ir::Path>,
874) -> bool {
875    if let Some((op, args)) = primitive_apply(expr) {
876        return args.len() == primitive_arity(op)
877            && args
878                .into_iter()
879                .all(|arg| pure_unused_expr(arg, functions, bindings, stack));
880    }
881    if let Ok(Some((target, _, args))) = direct_apply_path(expr, functions) {
882        if !args
883            .iter()
884            .all(|arg| pure_unused_expr(arg, functions, bindings, stack))
885        {
886            return false;
887        }
888        let Some(binding) = bindings.get(target) else {
889            return false;
890        };
891        let (params, body) = collect_lambda_params(&binding.body);
892        if params.len() != args.len() || !stack.insert(target.clone()) {
893            return false;
894        }
895        let pure = pure_unused_expr(body, functions, bindings, stack);
896        stack.remove(target);
897        return pure;
898    }
899
900    match &expr.kind {
901        runtime::ExprKind::Var(_)
902        | runtime::ExprKind::EffectOp(_)
903        | runtime::ExprKind::PrimitiveOp(_)
904        | runtime::ExprKind::Lit(_)
905        | runtime::ExprKind::Lambda { .. }
906        | runtime::ExprKind::PeekId
907        | runtime::ExprKind::FindId { .. } => true,
908        runtime::ExprKind::Tuple(items) => items
909            .iter()
910            .all(|item| pure_unused_expr(item, functions, bindings, stack)),
911        runtime::ExprKind::Record { fields, spread } => {
912            spread.is_none()
913                && fields
914                    .iter()
915                    .all(|field| pure_unused_expr(&field.value, functions, bindings, stack))
916        }
917        runtime::ExprKind::Variant { value, .. } => value
918            .as_deref()
919            .is_none_or(|value| pure_unused_expr(value, functions, bindings, stack)),
920        runtime::ExprKind::BindHere { expr }
921        | runtime::ExprKind::Thunk { expr, .. }
922        | runtime::ExprKind::LocalPushId { body: expr, .. }
923        | runtime::ExprKind::AddId { thunk: expr, .. }
924        | runtime::ExprKind::Coerce { expr, .. }
925        | runtime::ExprKind::Pack { expr, .. } => {
926            pure_unused_expr(expr, functions, bindings, stack)
927        }
928        runtime::ExprKind::Apply { .. }
929        | runtime::ExprKind::If { .. }
930        | runtime::ExprKind::Select { .. }
931        | runtime::ExprKind::Match { .. }
932        | runtime::ExprKind::Block { .. }
933        | runtime::ExprKind::Handle { .. } => false,
934    }
935}
936
937fn stmts_or_tail_use_any_path(
938    stmts: &[runtime::Stmt],
939    tail: Option<&runtime::Expr>,
940    paths: &HashSet<typed_ir::Path>,
941) -> bool {
942    stmts.iter().any(|stmt| stmt_uses_any_path(stmt, paths))
943        || tail.is_some_and(|tail| expr_uses_any_path(tail, paths))
944}
945
946fn stmt_uses_any_path(stmt: &runtime::Stmt, paths: &HashSet<typed_ir::Path>) -> bool {
947    match stmt {
948        runtime::Stmt::Let { pattern, value } => {
949            pattern_default_uses_any_path(pattern, paths) || expr_uses_any_path(value, paths)
950        }
951        runtime::Stmt::Expr(expr) | runtime::Stmt::Module { body: expr, .. } => {
952            expr_uses_any_path(expr, paths)
953        }
954    }
955}
956
957fn pattern_default_uses_any_path(
958    pattern: &runtime::Pattern,
959    paths: &HashSet<typed_ir::Path>,
960) -> bool {
961    match pattern {
962        runtime::Pattern::Tuple { items, .. } => items
963            .iter()
964            .any(|item| pattern_default_uses_any_path(item, paths)),
965        runtime::Pattern::List {
966            prefix,
967            spread,
968            suffix,
969            ..
970        } => {
971            prefix
972                .iter()
973                .any(|item| pattern_default_uses_any_path(item, paths))
974                || spread
975                    .as_deref()
976                    .is_some_and(|spread| pattern_default_uses_any_path(spread, paths))
977                || suffix
978                    .iter()
979                    .any(|item| pattern_default_uses_any_path(item, paths))
980        }
981        runtime::Pattern::Record { fields, spread, .. } => {
982            fields.iter().any(|field| {
983                pattern_default_uses_any_path(&field.pattern, paths)
984                    || field
985                        .default
986                        .as_ref()
987                        .is_some_and(|default| expr_uses_any_path(default, paths))
988            }) || spread.as_ref().is_some_and(|spread| match spread {
989                runtime::RecordSpreadPattern::Head(pattern)
990                | runtime::RecordSpreadPattern::Tail(pattern) => {
991                    pattern_default_uses_any_path(pattern, paths)
992                }
993            })
994        }
995        runtime::Pattern::Variant {
996            value: Some(value), ..
997        }
998        | runtime::Pattern::As { pattern: value, .. } => {
999            pattern_default_uses_any_path(value, paths)
1000        }
1001        runtime::Pattern::Or { left, right, .. } => {
1002            pattern_default_uses_any_path(left, paths)
1003                || pattern_default_uses_any_path(right, paths)
1004        }
1005        runtime::Pattern::Wildcard { .. }
1006        | runtime::Pattern::Bind { .. }
1007        | runtime::Pattern::Lit { .. }
1008        | runtime::Pattern::Variant { value: None, .. } => false,
1009    }
1010}
1011
1012fn expr_uses_any_path(expr: &runtime::Expr, paths: &HashSet<typed_ir::Path>) -> bool {
1013    match &expr.kind {
1014        runtime::ExprKind::Var(path) => paths.contains(path),
1015        runtime::ExprKind::Lambda { body, .. }
1016        | runtime::ExprKind::Thunk { expr: body, .. }
1017        | runtime::ExprKind::LocalPushId { body, .. }
1018        | runtime::ExprKind::BindHere { expr: body }
1019        | runtime::ExprKind::AddId { thunk: body, .. }
1020        | runtime::ExprKind::Coerce { expr: body, .. }
1021        | runtime::ExprKind::Pack { expr: body, .. } => expr_uses_any_path(body, paths),
1022        runtime::ExprKind::Apply { callee, arg, .. } => {
1023            expr_uses_any_path(callee, paths) || expr_uses_any_path(arg, paths)
1024        }
1025        runtime::ExprKind::If {
1026            cond,
1027            then_branch,
1028            else_branch,
1029            ..
1030        } => {
1031            expr_uses_any_path(cond, paths)
1032                || expr_uses_any_path(then_branch, paths)
1033                || expr_uses_any_path(else_branch, paths)
1034        }
1035        runtime::ExprKind::Tuple(items) => items.iter().any(|item| expr_uses_any_path(item, paths)),
1036        runtime::ExprKind::Record { fields, spread } => {
1037            fields
1038                .iter()
1039                .any(|field| expr_uses_any_path(&field.value, paths))
1040                || spread.as_ref().is_some_and(|spread| match spread {
1041                    runtime::RecordSpreadExpr::Head(expr)
1042                    | runtime::RecordSpreadExpr::Tail(expr) => expr_uses_any_path(expr, paths),
1043                })
1044        }
1045        runtime::ExprKind::Variant {
1046            value: Some(value), ..
1047        }
1048        | runtime::ExprKind::Select { base: value, .. } => expr_uses_any_path(value, paths),
1049        runtime::ExprKind::Match {
1050            scrutinee, arms, ..
1051        } => {
1052            expr_uses_any_path(scrutinee, paths)
1053                || arms.iter().any(|arm| {
1054                    pattern_default_uses_any_path(&arm.pattern, paths)
1055                        || arm
1056                            .guard
1057                            .as_ref()
1058                            .is_some_and(|guard| expr_uses_any_path(guard, paths))
1059                        || expr_uses_any_path(&arm.body, paths)
1060                })
1061        }
1062        runtime::ExprKind::Block { stmts, tail } => {
1063            stmts_or_tail_use_any_path(stmts, tail.as_deref(), paths)
1064        }
1065        runtime::ExprKind::Handle { body, arms, .. } => {
1066            expr_uses_any_path(body, paths)
1067                || arms.iter().any(|arm| {
1068                    pattern_default_uses_any_path(&arm.payload, paths)
1069                        || arm
1070                            .guard
1071                            .as_ref()
1072                            .is_some_and(|guard| expr_uses_any_path(guard, paths))
1073                        || expr_uses_any_path(&arm.body, paths)
1074                })
1075        }
1076        runtime::ExprKind::EffectOp(_)
1077        | runtime::ExprKind::PrimitiveOp(_)
1078        | runtime::ExprKind::Lit(_)
1079        | runtime::ExprKind::Variant { value: None, .. }
1080        | runtime::ExprKind::PeekId
1081        | runtime::ExprKind::FindId { .. } => false,
1082    }
1083}
1084
1085fn direct_apply_target<'expr>(
1086    expr: &'expr runtime::Expr,
1087    functions: &HashMap<typed_ir::Path, FunctionInfo>,
1088) -> Option<(typed_ir::Path, Vec<&'expr runtime::Expr>)> {
1089    direct_apply_path(expr, functions)
1090        .ok()
1091        .flatten()
1092        .map(|(path, _, args)| (path.clone(), args))
1093}
1094
1095fn inline_thunk_handler_apply(
1096    expr: &runtime::Expr,
1097    functions: &HashMap<typed_ir::Path, FunctionInfo>,
1098    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
1099) -> Option<(runtime::Expr, Vec<runtime::HandleArm>, Vec<typed_ir::Path>)> {
1100    let (target, _, args) = direct_apply_path(expr, functions).ok()??;
1101    if args.len() != 1 {
1102        return None;
1103    }
1104    let binding = bindings.get(target)?;
1105    if binding_has_self_direct_call(target, &binding.body, functions) {
1106        return None;
1107    }
1108    let (params, body) = collect_lambda_params(&binding.body);
1109    if params.len() != 1 {
1110        return None;
1111    }
1112    let (handled_body, arms, handler) = handler_wrapper_handle(body)?;
1113    let handled_body = handle_body_execution_inner(handled_body).unwrap_or(handled_body);
1114    let handled_body = transparent_expr(handled_body);
1115    let runtime::ExprKind::Var(body_var) = &handled_body.kind else {
1116        return None;
1117    };
1118    if body_var != &typed_ir::Path::from_name(params[0].clone()) {
1119        return None;
1120    }
1121    Some((args[0].clone(), arms.to_vec(), handler.consumes.clone()))
1122}
1123
1124fn transparent_expr(expr: &runtime::Expr) -> &runtime::Expr {
1125    let mut current = expr;
1126    loop {
1127        match &current.kind {
1128            runtime::ExprKind::Coerce { expr, .. }
1129            | runtime::ExprKind::Pack { expr, .. }
1130            | runtime::ExprKind::AddId { thunk: expr, .. } => current = expr,
1131            _ => return current,
1132        }
1133    }
1134}
1135
1136fn lower_binding(
1137    binding: &runtime::Binding,
1138    functions: &HashMap<typed_ir::Path, FunctionInfo>,
1139    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
1140) -> CpsLowerResult<CpsFunction> {
1141    if !binding.type_params.is_empty() {
1142        return Err(CpsLowerError::UnsupportedBinding {
1143            path: binding.name.clone(),
1144            reason: "residual type parameters",
1145        });
1146    }
1147    if let runtime::ExprKind::PrimitiveOp(op) = binding.body.kind {
1148        return Ok(lower_primitive_binding(&binding.name, op));
1149    }
1150    let (params, body) = collect_callable_params(&binding.body);
1151    if params.is_empty() {
1152        return Err(CpsLowerError::UnsupportedBinding {
1153            path: binding.name.clone(),
1154            reason: "non-function body",
1155        });
1156    }
1157    let traits = FunctionLoweringTraits::for_body(&binding.body, functions);
1158    FunctionLowerer::new(
1159        path_name(&binding.name),
1160        functions,
1161        bindings,
1162        params,
1163        traits,
1164    )
1165    .lower_root(&body)
1166    .map_err(|error| match error {
1167        CpsLowerError::UnsupportedExpr { kind } => CpsLowerError::UnsupportedBinding {
1168            path: binding.name.clone(),
1169            reason: kind,
1170        },
1171        error => error,
1172    })
1173}
1174
1175fn binding_function_info(binding: &runtime::Binding) -> Option<(typed_ir::Path, FunctionInfo)> {
1176    if let runtime::ExprKind::PrimitiveOp(op) = binding.body.kind {
1177        let arity = primitive_arity(op);
1178        return Some((
1179            binding.name.clone(),
1180            FunctionInfo {
1181                path: binding.name.clone(),
1182                name: path_name(&binding.name),
1183                arity,
1184                params: vec![runtime::Type::unknown(); arity],
1185                ret: runtime::Type::unknown(),
1186            },
1187        ));
1188    }
1189    let (params, body) = collect_callable_params(&binding.body);
1190    if params.is_empty() {
1191        return None;
1192    }
1193    let param_types = collect_fun_param_types(&binding.body, params.len());
1194    Some((
1195        binding.name.clone(),
1196        FunctionInfo {
1197            path: binding.name.clone(),
1198            name: path_name(&binding.name),
1199            arity: params.len(),
1200            params: param_types,
1201            ret: body.ty.clone(),
1202        },
1203    ))
1204}
1205
1206fn binding_value_body(binding: &runtime::Binding) -> Option<&runtime::Expr> {
1207    if matches!(binding.body.kind, runtime::ExprKind::PrimitiveOp(_)) {
1208        return None;
1209    }
1210    collect_callable_params(&binding.body)
1211        .0
1212        .is_empty()
1213        .then_some(&binding.body)
1214}
1215
1216fn binding_is_throw_forwarder(binding: &runtime::Binding) -> bool {
1217    let (params, body) = collect_callable_params(&binding.body);
1218    let [param] = params.as_slice() else {
1219        return false;
1220    };
1221    let body = transparent_effect_expr(&body);
1222    let runtime::ExprKind::Apply { callee, arg, .. } = &body.kind else {
1223        return false;
1224    };
1225    let callee = transparent_effect_expr(callee);
1226    let runtime::ExprKind::Var(callee) = &callee.kind else {
1227        return false;
1228    };
1229    if !throw_role_method_path(callee) {
1230        return false;
1231    }
1232    let arg = transparent_effect_expr(arg);
1233    matches!(&arg.kind, runtime::ExprKind::Var(path) if path == &typed_ir::Path::from_name(param.clone()))
1234}
1235
1236/// Walk the binding body's nested `Lambda { param, body }` chain, collecting
1237/// each lambda's argument type from `expr.ty = Fun { param, ret }`.
1238/// `expected` matches the count produced by `collect_callable_params`.
1239fn collect_fun_param_types(expr: &runtime::Expr, expected: usize) -> Vec<runtime::Type> {
1240    let mut params = Vec::with_capacity(expected);
1241    let mut current = expr;
1242    while params.len() < expected {
1243        match &current.kind {
1244            runtime::ExprKind::Lambda { body, .. } => {
1245                let arg_ty = match &current.ty {
1246                    runtime::Type::Fun { param, .. } => (**param).clone(),
1247                    _ => runtime::Type::unknown(),
1248                };
1249                params.push(arg_ty);
1250                current = body;
1251            }
1252            runtime::ExprKind::Block {
1253                tail: Some(tail), ..
1254            } => {
1255                current = tail;
1256            }
1257            runtime::ExprKind::Coerce { expr, .. }
1258            | runtime::ExprKind::Pack { expr, .. }
1259            | runtime::ExprKind::BindHere { expr } => {
1260                current = expr;
1261            }
1262            _ => break,
1263        }
1264    }
1265    while params.len() < expected {
1266        params.push(runtime::Type::unknown());
1267    }
1268    params
1269}
1270
1271fn lower_primitive_binding(path: &typed_ir::Path, op: typed_ir::PrimitiveOp) -> CpsFunction {
1272    let arity = primitive_arity(op);
1273    let params = (0..arity).map(CpsValueId).collect::<Vec<_>>();
1274    let dest = CpsValueId(arity);
1275    CpsFunction {
1276        name: path_name(path),
1277        params: params.clone(),
1278        entry: CpsContinuationId(0),
1279        continuations: vec![CpsContinuation {
1280            id: CpsContinuationId(0),
1281            params: params.clone(),
1282            captures: Vec::new(),
1283            shot_kind: CpsShotKind::MultiShot,
1284            stmts: vec![CpsStmt::Primitive {
1285                dest,
1286                op,
1287                args: params,
1288            }],
1289            terminator: CpsTerminator::Return(dest),
1290        }],
1291        handlers: Vec::new(),
1292    }
1293}
1294
1295#[derive(Debug, Clone, PartialEq, Eq)]
1296struct FunctionInfo {
1297    path: typed_ir::Path,
1298    name: String,
1299    arity: usize,
1300    /// Static types of the formal parameters in declaration order. Used at
1301    /// each call site to decide whether an argument expression has to be
1302    /// suspended as a Thunk before being passed (`loop(x: [_] _, queue)`
1303    /// expects `k true` to arrive as a thunk, not as the eager Resume
1304    /// result).
1305    params: Vec<runtime::Type>,
1306    ret: runtime::Type,
1307}
1308
1309#[derive(Debug, Clone, Copy, Default)]
1310struct FunctionLoweringTraits {
1311    higher_order_helper: bool,
1312}
1313
1314impl FunctionLoweringTraits {
1315    fn for_body(body: &runtime::Expr, functions: &HashMap<typed_ir::Path, FunctionInfo>) -> Self {
1316        Self {
1317            higher_order_helper: expr_contains_indirect_apply(body, functions),
1318        }
1319    }
1320}
1321
1322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1323enum DirectCallMode {
1324    SyncDirect,
1325    EffectfulWithResume,
1326}
1327
1328#[derive(Debug)]
1329struct DirectCallPlan<'expr, 'functions> {
1330    expr: &'expr runtime::Expr,
1331    target: String,
1332    info: &'functions FunctionInfo,
1333    args: Vec<&'expr runtime::Expr>,
1334    mode: DirectCallMode,
1335    target_may_perform: bool,
1336    info_returns_thunk: bool,
1337    force_handler_reentry_args: bool,
1338    should_inline: bool,
1339}
1340
1341#[derive(Debug, Clone, Default)]
1342struct DepthCounter {
1343    value: Rc<Cell<usize>>,
1344}
1345
1346impl DepthCounter {
1347    fn is_active(&self) -> bool {
1348        self.value.get() > 0
1349    }
1350
1351    fn is_inactive(&self) -> bool {
1352        self.value.get() == 0
1353    }
1354
1355    fn enter(&self) -> DepthGuard {
1356        self.value.set(self.value.get() + 1);
1357        DepthGuard {
1358            value: Rc::clone(&self.value),
1359        }
1360    }
1361}
1362
1363struct DepthGuard {
1364    value: Rc<Cell<usize>>,
1365}
1366
1367impl Drop for DepthGuard {
1368    fn drop(&mut self) {
1369        let current = self.value.get();
1370        debug_assert!(current > 0, "depth counter underflow");
1371        self.value.set(current.saturating_sub(1));
1372    }
1373}
1374
1375enum ExprLowerCase<'expr, 'functions> {
1376    InlineThunkHandler {
1377        body: runtime::Expr,
1378        arms: Vec<runtime::HandleArm>,
1379        consumes: Vec<typed_ir::Path>,
1380    },
1381    LocalExprApply {
1382        callee: runtime::Expr,
1383        arg: &'expr runtime::Expr,
1384    },
1385    EffectRequest(CpsEffectApply<'expr>),
1386    BindHere {
1387        expr: &'expr runtime::Expr,
1388    },
1389    Primitive {
1390        op: typed_ir::PrimitiveOp,
1391        args: Vec<&'expr runtime::Expr>,
1392    },
1393    PartialDirectApply {
1394        target_path: &'expr typed_ir::Path,
1395        info: &'functions FunctionInfo,
1396        args: Vec<&'expr runtime::Expr>,
1397    },
1398    DirectApply {
1399        target_path: &'expr typed_ir::Path,
1400        info: &'functions FunctionInfo,
1401        args: Vec<&'expr runtime::Expr>,
1402    },
1403    ResumeApply {
1404        resumption: CpsValueId,
1405        arg: &'expr runtime::Expr,
1406    },
1407    PlainExprKind,
1408}
1409
1410struct FunctionLowerer<'a> {
1411    name: String,
1412    functions: &'a HashMap<typed_ir::Path, FunctionInfo>,
1413    bindings: &'a HashMap<typed_ir::Path, &'a runtime::Binding>,
1414    next_value: usize,
1415    next_continuation: usize,
1416    next_handler: usize,
1417    continuations: Vec<CpsContinuation>,
1418    handlers: Vec<CpsHandler>,
1419    forced_handler_effects: Vec<(CpsHandlerId, typed_ir::Path)>,
1420    handlers_with_external_calls: HashSet<CpsHandlerId>,
1421    current: ContinuationBuilder,
1422    locals: HashMap<typed_ir::Path, CpsValueId>,
1423    function_param_values: HashMap<typed_ir::Path, CpsValueId>,
1424    local_exprs: HashMap<typed_ir::Path, runtime::Expr>,
1425    effect_guards: HashMap<runtime::EffectIdVar, CpsValueId>,
1426    resumptions: HashSet<typed_ir::Path>,
1427    inline_stack: HashSet<typed_ir::Path>,
1428    active_handler: Option<ActiveHandlerContext>,
1429    params: Vec<CpsValueId>,
1430    handler_value_conts: Vec<CpsContinuationId>,
1431    force_effectful_apply_depth: DepthCounter,
1432    sync_apply_for_immediate_force_depth: DepthCounter,
1433    sync_direct_call_for_ignored_force_depth: DepthCounter,
1434    /// True for helpers whose callback applies / handler bodies may cross
1435    /// effectful boundaries and therefore need to capture local rest as a
1436    /// return frame.
1437    higher_order_helper: bool,
1438}
1439
1440#[derive(Clone)]
1441struct ActiveHandlerContext {
1442    handler: CpsHandlerId,
1443    expected_effects: Vec<typed_ir::Path>,
1444    parent: Option<Box<ActiveHandlerContext>>,
1445}
1446
1447#[derive(Clone, Copy)]
1448struct EffectHandlerArmChain<'a> {
1449    effect: &'a typed_ir::Path,
1450    payload: CpsValueId,
1451    resume: CpsValueId,
1452    handler: CpsHandlerId,
1453    saved_locals: &'a HashMap<typed_ir::Path, CpsValueId>,
1454    saved_local_exprs: &'a HashMap<typed_ir::Path, runtime::Expr>,
1455    saved_resumptions: &'a HashSet<typed_ir::Path>,
1456}
1457
1458impl<'a> FunctionLowerer<'a> {
1459    fn mark_active_handlers_external_call(&mut self) {
1460        let mut current = self.active_handler.clone();
1461        while let Some(context) = current {
1462            self.handlers_with_external_calls.insert(context.handler);
1463            current = context.parent.as_deref().cloned();
1464        }
1465    }
1466}
1467
1468impl<'a> FunctionLowerer<'a> {
1469    fn new(
1470        name: String,
1471        functions: &'a HashMap<typed_ir::Path, FunctionInfo>,
1472        bindings: &'a HashMap<typed_ir::Path, &'a runtime::Binding>,
1473        params: Vec<typed_ir::Name>,
1474        traits: FunctionLoweringTraits,
1475    ) -> Self {
1476        let mut next_value = 0;
1477        let mut param_values = Vec::with_capacity(params.len());
1478        let mut locals = HashMap::new();
1479        let mut function_param_values = HashMap::new();
1480        for param in params {
1481            let value = CpsValueId(next_value);
1482            next_value += 1;
1483            let path = typed_ir::Path::from_name(param);
1484            locals.insert(path.clone(), value);
1485            function_param_values.insert(path, value);
1486            param_values.push(value);
1487        }
1488        Self {
1489            name,
1490            functions,
1491            bindings,
1492            next_value,
1493            next_continuation: 1,
1494            next_handler: 0,
1495            continuations: Vec::new(),
1496            handlers: Vec::new(),
1497            forced_handler_effects: Vec::new(),
1498            handlers_with_external_calls: HashSet::new(),
1499            current: ContinuationBuilder::new(CpsContinuationId(0), param_values.clone()),
1500            locals,
1501            function_param_values,
1502            local_exprs: HashMap::new(),
1503            effect_guards: HashMap::new(),
1504            resumptions: HashSet::new(),
1505            inline_stack: HashSet::new(),
1506            active_handler: None,
1507            params: param_values,
1508            handler_value_conts: Vec::new(),
1509            force_effectful_apply_depth: DepthCounter::default(),
1510            sync_apply_for_immediate_force_depth: DepthCounter::default(),
1511            sync_direct_call_for_ignored_force_depth: DepthCounter::default(),
1512            higher_order_helper: traits.higher_order_helper,
1513        }
1514    }
1515
1516    fn lower_root(mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsFunction> {
1517        let value = self.lower_expr(expr)?;
1518        // Force the return value when the static return type demands a
1519        // plain value. Without this, a body that ends in `MakeThunk` (e.g.
1520        // a helper fn whose body branches over an effect like `guard`)
1521        // would leak a Thunk all the way to the root and make CPS eval
1522        // explode with ExpectedPlainValue.
1523        let value = self.force_if_non_thunk_demand(value, &expr.ty);
1524        self.terminate(CpsTerminator::Return(value));
1525        self.finish_current();
1526        Ok(CpsFunction {
1527            name: self.name,
1528            params: self.params,
1529            entry: CpsContinuationId(0),
1530            continuations: self.continuations,
1531            handlers: self.handlers,
1532        })
1533    }
1534
1535    fn lower_expr(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1536        let lower_case = self.classify_expr(expr)?;
1537        self.lower_classified_expr(expr, lower_case)
1538    }
1539
1540    fn lower_classified_expr(
1541        &mut self,
1542        expr: &runtime::Expr,
1543        lower_case: ExprLowerCase<'_, '_>,
1544    ) -> CpsLowerResult<CpsValueId> {
1545        match lower_case {
1546            ExprLowerCase::InlineThunkHandler {
1547                body,
1548                arms,
1549                consumes,
1550            } => self.lower_handle(&body, &arms, &consumes),
1551            ExprLowerCase::LocalExprApply { callee, arg } => {
1552                self.lower_local_expr_apply_case(&callee, arg, &expr.ty)
1553            }
1554            ExprLowerCase::EffectRequest(request) => {
1555                let (expected_effects, handler) =
1556                    self.effect_context_for_request(&request, &[], dynamic_handler_id());
1557                let (_, value) =
1558                    self.begin_resume_after_perform(request, &expected_effects, handler)?;
1559                Ok(value)
1560            }
1561            ExprLowerCase::BindHere { expr } => self.lower_bind_here(expr),
1562            ExprLowerCase::Primitive { op, args } => self.lower_primitive_apply_case(op, args),
1563            ExprLowerCase::PartialDirectApply {
1564                target_path,
1565                info,
1566                args,
1567            } => self.lower_partial_direct_apply(target_path, info, args),
1568            ExprLowerCase::DirectApply {
1569                target_path,
1570                info,
1571                args,
1572            } => {
1573                let plan = self.plan_direct_call(expr, target_path, info, args);
1574                self.lower_direct_call_plan(plan)
1575            }
1576            ExprLowerCase::ResumeApply { resumption, arg } => {
1577                let arg = self.lower_expr(arg)?;
1578                let dest = self.fresh_value();
1579                self.current.stmts.push(CpsStmt::Resume {
1580                    dest,
1581                    resumption,
1582                    arg,
1583                });
1584                Ok(dest)
1585            }
1586            ExprLowerCase::PlainExprKind => self.lower_expr_kind(expr),
1587        }
1588    }
1589
1590    fn lower_expr_kind(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1591        match &expr.kind {
1592            runtime::ExprKind::Lit(lit) => {
1593                let dest = self.fresh_value();
1594                self.current.stmts.push(CpsStmt::Literal {
1595                    dest,
1596                    literal: lower_literal(lit),
1597                });
1598                Ok(dest)
1599            }
1600            runtime::ExprKind::PrimitiveOp(_) => Err(CpsLowerError::UnsupportedExpr {
1601                kind: "bare primitive",
1602            }),
1603            runtime::ExprKind::Var(path) => {
1604                if let Some(expr) = self.local_exprs.get(path).cloned() {
1605                    return self.lower_expr(&inline_callable_expr(&expr));
1606                }
1607                if let Some(value) = self.locals.get(path).copied() {
1608                    return Ok(value);
1609                }
1610                if let Some(info) = self.functions.get(path) {
1611                    return self.lower_function_value(path, info);
1612                }
1613                if let Some(expr) = self.bindings.get(path).and_then(|binding| {
1614                    binding_value_body(binding).filter(
1615                        |body| !matches!(&body.kind, runtime::ExprKind::Var(inner) if inner == path),
1616                    )
1617                }) {
1618                    let expr = expr.clone();
1619                    if !self.inline_stack.insert(path.clone()) {
1620                        return Err(CpsLowerError::UnsupportedFreeVar { path: path.clone() });
1621                    }
1622                    let value = self.lower_expr(&expr);
1623                    self.inline_stack.remove(path);
1624                    return value;
1625                }
1626                Err(CpsLowerError::UnsupportedFreeVar { path: path.clone() })
1627            }
1628            runtime::ExprKind::If {
1629                cond,
1630                then_branch,
1631                else_branch,
1632                ..
1633            } => self.lower_if(cond, then_branch, else_branch),
1634            runtime::ExprKind::Block { stmts, tail } => self.lower_block(stmts, tail.as_deref()),
1635            runtime::ExprKind::EffectOp(path) => {
1636                Err(CpsLowerError::UnsupportedBareEffectOp { path: path.clone() })
1637            }
1638            runtime::ExprKind::Lambda { param, body, .. } => self.lower_lambda(param, body),
1639            runtime::ExprKind::Apply { callee, arg, .. } => self.lower_apply(expr, callee, arg),
1640            runtime::ExprKind::Tuple(items) => self.lower_tuple(items),
1641            runtime::ExprKind::Record { fields, spread } => self.lower_record(fields, spread),
1642            runtime::ExprKind::Variant { tag, value } => self.lower_variant(tag, value.as_deref()),
1643            runtime::ExprKind::Select { base, field } => self.lower_select(base, field),
1644            runtime::ExprKind::Match { .. } => {
1645                if let Some((cond, then_branch, else_branch)) = bool_match(expr) {
1646                    self.lower_if(cond, then_branch, else_branch)
1647                } else {
1648                    self.lower_match(expr)
1649                }
1650            }
1651            runtime::ExprKind::Handle {
1652                body,
1653                arms,
1654                handler,
1655                ..
1656            } => self.lower_handle(body, arms, &handler.consumes),
1657            runtime::ExprKind::BindHere { expr } => self.lower_bind_here(expr),
1658            runtime::ExprKind::Thunk { expr, .. } => self.lower_thunk(expr),
1659            runtime::ExprKind::LocalPushId { id, body } => {
1660                let dest = self.fresh_value();
1661                self.current
1662                    .stmts
1663                    .push(CpsStmt::FreshGuard { dest, var: *id });
1664                let previous = self.effect_guards.insert(*id, dest);
1665                let result = self.lower_expr(body);
1666                restore_effect_guard(&mut self.effect_guards, *id, previous);
1667                result
1668            }
1669            runtime::ExprKind::AddId {
1670                id,
1671                allowed,
1672                active,
1673                thunk,
1674            } => {
1675                let thunk = self.lower_expr(thunk)?;
1676                let guard = self.lower_effect_id_ref(*id)?;
1677                let dest = self.fresh_value();
1678                self.current.stmts.push(CpsStmt::AddThunkBoundary {
1679                    dest,
1680                    thunk,
1681                    guard,
1682                    allowed: allowed.clone(),
1683                    active: *active,
1684                });
1685                Ok(dest)
1686            }
1687            runtime::ExprKind::Coerce { expr, .. } | runtime::ExprKind::Pack { expr, .. } => {
1688                self.lower_expr(expr)
1689            }
1690            runtime::ExprKind::PeekId => {
1691                let dest = self.fresh_value();
1692                self.current.stmts.push(CpsStmt::PeekGuard { dest });
1693                Ok(dest)
1694            }
1695            runtime::ExprKind::FindId { id } => {
1696                let guard = self.lower_effect_id_ref(*id)?;
1697                let dest = self.fresh_value();
1698                self.current.stmts.push(CpsStmt::FindGuard { dest, guard });
1699                Ok(dest)
1700            }
1701        }
1702    }
1703
1704    fn classify_expr<'expr>(
1705        &self,
1706        expr: &'expr runtime::Expr,
1707    ) -> CpsLowerResult<ExprLowerCase<'expr, 'a>> {
1708        if let Some((body, arms, consumes)) =
1709            inline_thunk_handler_apply(expr, self.functions, self.bindings)
1710        {
1711            return Ok(ExprLowerCase::InlineThunkHandler {
1712                body,
1713                arms,
1714                consumes,
1715            });
1716        }
1717        if let Some((callee, arg)) = self.local_expr_apply_case(expr) {
1718            return Ok(ExprLowerCase::LocalExprApply { callee, arg });
1719        }
1720        if let Some(request) = effect_apply_body_request(expr) {
1721            return Ok(ExprLowerCase::EffectRequest(request));
1722        }
1723        if let runtime::ExprKind::BindHere { expr } = &expr.kind {
1724            return Ok(ExprLowerCase::BindHere { expr });
1725        }
1726        if let Some((op, args)) = primitive_apply(expr) {
1727            return Ok(ExprLowerCase::Primitive { op, args });
1728        }
1729        if let Some((target_path, info, args)) = partial_direct_apply_path(expr, self.functions)? {
1730            return Ok(ExprLowerCase::PartialDirectApply {
1731                target_path,
1732                info,
1733                args,
1734            });
1735        }
1736        if let Some((target_path, info, args)) = direct_apply_path(expr, self.functions)? {
1737            return Ok(ExprLowerCase::DirectApply {
1738                target_path,
1739                info,
1740                args,
1741            });
1742        }
1743        if let Some((resumption, arg)) = self.resume_apply(expr) {
1744            return Ok(ExprLowerCase::ResumeApply { resumption, arg });
1745        }
1746        Ok(ExprLowerCase::PlainExprKind)
1747    }
1748
1749    fn lower_primitive_apply_case(
1750        &mut self,
1751        op: typed_ir::PrimitiveOp,
1752        args: Vec<&runtime::Expr>,
1753    ) -> CpsLowerResult<CpsValueId> {
1754        let expected = primitive_arity(op);
1755        if args.len() != expected {
1756            return Err(CpsLowerError::PrimitiveArityMismatch {
1757                op,
1758                expected,
1759                actual: args.len(),
1760            });
1761        }
1762        let args = args
1763            .into_iter()
1764            .map(|arg| self.lower_expr(arg))
1765            .collect::<CpsLowerResult<Vec<_>>>()?;
1766        let dest = self.fresh_value();
1767        self.current
1768            .stmts
1769            .push(CpsStmt::Primitive { dest, op, args });
1770        Ok(dest)
1771    }
1772
1773    fn lower_effect_id_ref(&mut self, id: runtime::EffectIdRef) -> CpsLowerResult<CpsValueId> {
1774        match id {
1775            runtime::EffectIdRef::Peek => {
1776                let dest = self.fresh_value();
1777                self.current.stmts.push(CpsStmt::PeekGuard { dest });
1778                Ok(dest)
1779            }
1780            runtime::EffectIdRef::Var(var) => {
1781                self.effect_guards
1782                    .get(&var)
1783                    .copied()
1784                    .ok_or(CpsLowerError::UnsupportedExpr {
1785                        kind: "effect id variable outside local push scope",
1786                    })
1787            }
1788        }
1789    }
1790
1791    fn lower_bind_here(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1792        let thunk = self.with_sync_apply_for_immediate_force_depth(|this| this.lower_expr(expr))?;
1793        let dest = self.fresh_value();
1794        self.current.stmts.push(CpsStmt::ForceThunk { dest, thunk });
1795        Ok(dest)
1796    }
1797
1798    fn lower_thunk(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1799        let entry = self.fresh_continuation();
1800        let dest = self.fresh_value();
1801        let saved_current = std::mem::replace(
1802            &mut self.current,
1803            ContinuationBuilder::new(entry, Vec::new()),
1804        );
1805        let performed_effects = collect_expr_performed_effects(expr);
1806        let value = if !performed_effects.is_empty() {
1807            let (mut expected_effects, handler) = self.current_effect_context();
1808            if expected_effects.is_empty() {
1809                expected_effects = performed_effects;
1810            }
1811            let value_cont = self.fresh_continuation();
1812            let value = self.fresh_value();
1813            self.lower_handled_body(expr, &expected_effects, handler, Some(value_cont))?;
1814            self.current = ContinuationBuilder::new(value_cont, vec![value]);
1815            value
1816        } else {
1817            self.lower_expr(expr)?
1818        };
1819        self.terminate(CpsTerminator::Return(value));
1820        self.finish_current();
1821        self.current = saved_current;
1822        self.current.stmts.push(CpsStmt::MakeThunk { dest, entry });
1823        Ok(dest)
1824    }
1825
1826    fn lower_lambda(
1827        &mut self,
1828        param: &typed_ir::Name,
1829        body: &runtime::Expr,
1830    ) -> CpsLowerResult<CpsValueId> {
1831        let entry = self.fresh_continuation();
1832        let dest = self.fresh_value();
1833        let param_value = self.fresh_value();
1834        let saved_current = std::mem::replace(
1835            &mut self.current,
1836            ContinuationBuilder::new(entry, vec![param_value]),
1837        );
1838        let saved_locals = self.locals.clone();
1839        let saved_local_exprs = self.local_exprs.clone();
1840        let saved_resumptions = self.resumptions.clone();
1841        let param_path = typed_ir::Path::from_name(param.clone());
1842        self.local_exprs.remove(&param_path);
1843        self.locals.insert(param_path, param_value);
1844        let value = if let Some(context) = self.active_handler.clone()
1845            && !collect_expr_performed_effects(body).is_empty()
1846        {
1847            let value_cont = self.fresh_continuation();
1848            let value = self.fresh_value();
1849            self.lower_handled_body(
1850                body,
1851                &context.expected_effects,
1852                context.handler,
1853                Some(value_cont),
1854            )?;
1855            self.current = ContinuationBuilder::new(value_cont, vec![value]);
1856            value
1857        } else {
1858            self.lower_expr(body)?
1859        };
1860        let value = self.force_if_non_thunk_demand(value, &body.ty);
1861        self.terminate(CpsTerminator::Return(value));
1862        self.finish_current();
1863        self.locals = saved_locals;
1864        self.local_exprs = saved_local_exprs;
1865        self.resumptions = saved_resumptions;
1866        self.current = saved_current;
1867        self.current
1868            .stmts
1869            .push(CpsStmt::MakeClosure { dest, entry });
1870        Ok(dest)
1871    }
1872
1873    fn lower_recursive_lambda(
1874        &mut self,
1875        name: &typed_ir::Name,
1876        param: &typed_ir::Name,
1877        body: &runtime::Expr,
1878    ) -> CpsLowerResult<CpsValueId> {
1879        let entry = self.fresh_continuation();
1880        let dest = self.fresh_value();
1881        let param_value = self.fresh_value();
1882        let saved_current = std::mem::replace(
1883            &mut self.current,
1884            ContinuationBuilder::new(entry, vec![param_value]),
1885        );
1886        let saved_locals = self.locals.clone();
1887        let saved_local_exprs = self.local_exprs.clone();
1888        let saved_resumptions = self.resumptions.clone();
1889        let self_path = typed_ir::Path::from_name(name.clone());
1890        self.local_exprs.remove(&self_path);
1891        self.locals.insert(self_path, dest);
1892        let param_path = typed_ir::Path::from_name(param.clone());
1893        self.local_exprs.remove(&param_path);
1894        self.locals.insert(param_path, param_value);
1895        let value = if let Some(context) = self.active_handler.clone()
1896            && !collect_expr_performed_effects(body).is_empty()
1897        {
1898            let value_cont = self.fresh_continuation();
1899            let value = self.fresh_value();
1900            self.lower_handled_body(
1901                body,
1902                &context.expected_effects,
1903                context.handler,
1904                Some(value_cont),
1905            )?;
1906            self.current = ContinuationBuilder::new(value_cont, vec![value]);
1907            value
1908        } else {
1909            self.lower_expr(body)?
1910        };
1911        let value = self.force_if_non_thunk_demand(value, &body.ty);
1912        self.terminate(CpsTerminator::Return(value));
1913        self.finish_current();
1914        self.locals = saved_locals;
1915        self.local_exprs = saved_local_exprs;
1916        self.resumptions = saved_resumptions;
1917        self.current = saved_current;
1918        self.current
1919            .stmts
1920            .push(CpsStmt::MakeRecursiveClosure { dest, entry });
1921        Ok(dest)
1922    }
1923
1924    fn lower_function_value(
1925        &mut self,
1926        path: &typed_ir::Path,
1927        info: &FunctionInfo,
1928    ) -> CpsLowerResult<CpsValueId> {
1929        if info.arity != 1 {
1930            return Err(CpsLowerError::UnsupportedBinding {
1931                path: path.clone(),
1932                reason: "function value with arity other than 1",
1933            });
1934        }
1935        let entry = self.fresh_continuation();
1936        let dest = self.fresh_value();
1937        let param_value = self.fresh_value();
1938        let saved_current = std::mem::replace(
1939            &mut self.current,
1940            ContinuationBuilder::new(entry, vec![param_value]),
1941        );
1942        let result = self.fresh_value();
1943        self.current.stmts.push(CpsStmt::DirectCall {
1944            dest: result,
1945            target: info.name.clone(),
1946            args: vec![param_value],
1947        });
1948        if matches!(info.ret, runtime::Type::Thunk { .. })
1949            || self.target_may_perform_when_called(path)
1950        {
1951            self.mark_active_handlers_external_call();
1952        }
1953        self.terminate(CpsTerminator::Return(result));
1954        self.finish_current();
1955        self.current = saved_current;
1956        self.current
1957            .stmts
1958            .push(CpsStmt::MakeClosure { dest, entry });
1959        Ok(dest)
1960    }
1961
1962    fn lower_apply(
1963        &mut self,
1964        expr: &runtime::Expr,
1965        callee: &runtime::Expr,
1966        arg: &runtime::Expr,
1967    ) -> CpsLowerResult<CpsValueId> {
1968        let closure = self.lower_expr(callee)?;
1969        let callee_ty = callable_type_after_force(&callee.ty);
1970        let forced = self.fresh_value();
1971        self.current.stmts.push(CpsStmt::ForceThunk {
1972            dest: forced,
1973            thunk: closure,
1974        });
1975        let closure = forced;
1976        let arg = self.lower_expr_as_call_arg(callee_ty, arg)?;
1977        // Final first-class calls need a continuation boundary. Partial
1978        // application that merely returns another function stays synchronous;
1979        // otherwise multi-shot search code such as `once` can replay too much
1980        // of the queue. Once the apply returns a value, however, it may
1981        // perform before that value is produced, so the caller rest must be
1982        // captured in a resumption.
1983        if self.force_effectful_apply_depth.is_active()
1984            || (self.sync_apply_for_immediate_force_depth.is_inactive()
1985                && self.higher_order_helper
1986                && matches!(expr.ty, runtime::Type::Thunk { .. }))
1987        {
1988            let post_cont = self.fresh_continuation();
1989            let result = self.fresh_value();
1990            self.terminate(CpsTerminator::EffectfulApply {
1991                closure,
1992                arg,
1993                resume: post_cont,
1994            });
1995            self.finish_current();
1996            self.mark_active_handlers_external_call();
1997            self.current = ContinuationBuilder::new(post_cont, vec![result]);
1998            return Ok(self.force_if_non_thunk_demand(result, &expr.ty));
1999        }
2000        let dest = self.fresh_value();
2001        self.current
2002            .stmts
2003            .push(CpsStmt::ApplyClosure { dest, closure, arg });
2004        Ok(self.force_if_non_thunk_demand(dest, &expr.ty))
2005    }
2006
2007    fn lower_partial_direct_apply(
2008        &mut self,
2009        target_path: &typed_ir::Path,
2010        info: &FunctionInfo,
2011        args: Vec<&runtime::Expr>,
2012    ) -> CpsLowerResult<CpsValueId> {
2013        let info_params = info.params.clone();
2014        let lowered_args = args
2015            .into_iter()
2016            .enumerate()
2017            .map(|(idx, arg)| {
2018                let expected = info_params
2019                    .get(idx)
2020                    .cloned()
2021                    .unwrap_or_else(runtime::Type::unknown);
2022                let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
2023                    self.lower_expr_as_thunk_value(arg)?
2024                } else {
2025                    self.lower_expr(arg)?
2026                };
2027                Ok(self.force_if_non_thunk_demand(lowered, &expected))
2028            })
2029            .collect::<CpsLowerResult<Vec<_>>>()?;
2030        self.emit_partial_direct_closure(target_path, info, lowered_args)
2031    }
2032
2033    fn emit_partial_direct_closure(
2034        &mut self,
2035        target_path: &typed_ir::Path,
2036        info: &FunctionInfo,
2037        captured_args: Vec<CpsValueId>,
2038    ) -> CpsLowerResult<CpsValueId> {
2039        let entry = self.fresh_continuation();
2040        let dest = self.fresh_value();
2041        let param_value = self.fresh_value();
2042        let saved_current = std::mem::replace(
2043            &mut self.current,
2044            ContinuationBuilder::new(entry, vec![param_value]),
2045        );
2046        let mut call_args = captured_args;
2047        call_args.push(param_value);
2048        let result = if call_args.len() == info.arity {
2049            let result = self.fresh_value();
2050            self.current.stmts.push(CpsStmt::DirectCall {
2051                dest: result,
2052                target: info.name.clone(),
2053                args: call_args,
2054            });
2055            if matches!(info.ret, runtime::Type::Thunk { .. })
2056                || self.target_may_perform_when_called(target_path)
2057            {
2058                self.mark_active_handlers_external_call();
2059            }
2060            result
2061        } else {
2062            self.emit_partial_direct_closure(target_path, info, call_args)?
2063        };
2064        self.terminate(CpsTerminator::Return(result));
2065        self.finish_current();
2066        self.current = saved_current;
2067        self.current
2068            .stmts
2069            .push(CpsStmt::MakeClosure { dest, entry });
2070        Ok(dest)
2071    }
2072
2073    /// Lower a positional call argument. If the callee's first formal
2074    /// parameter has Thunk type, wrap the argument in MakeThunk so that
2075    /// `loop(k true, queue)` style sites pass `k true` as a deferred
2076    /// computation rather than the Resume's eager scalar.
2077    fn lower_expr_as_call_arg(
2078        &mut self,
2079        callee_ty: &runtime::Type,
2080        arg: &runtime::Expr,
2081    ) -> CpsLowerResult<CpsValueId> {
2082        let param_ty = match callee_ty {
2083            runtime::Type::Fun { param, .. } => Some(param.as_ref()),
2084            _ => None,
2085        };
2086        let param_is_thunk = matches!(param_ty, Some(runtime::Type::Thunk { .. }));
2087        if param_is_thunk {
2088            self.lower_expr_as_thunk_value(arg)
2089        } else {
2090            self.lower_expr(arg)
2091        }
2092    }
2093
2094    /// Suspend an arbitrary expression as a Thunk so that it is evaluated
2095    /// only when the callee forces it (e.g. `loop`'s `catch x:` forcing
2096    /// `x = each [1,2,3]`). A syntactic Thunk (`{ ... }`) already produces
2097    /// a thunk handle via `lower_expr`, so we forward it directly. Every
2098    /// other expression — including `Apply` chains whose static type is
2099    /// `[eff] T` — must be wrapped via `lower_thunk` so the underlying
2100    /// `DirectCall` does not run before the caller has installed its
2101    /// handler scope.
2102    fn lower_expr_as_thunk_value(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
2103        // A syntactic Thunk literal `{ ... }` already produces a thunk
2104        // handle; a `Var` binding to a thunk-typed local just reads the
2105        // existing handle. Lowering either eagerly is fine — they don't
2106        // run anything. Anything else (especially `Apply`) would *evaluate*
2107        // when lowered eagerly, so we wrap it in `MakeThunk` to defer.
2108        match &expr.kind {
2109            runtime::ExprKind::Thunk { .. } | runtime::ExprKind::Var(_) => self.lower_expr(expr),
2110            _ if self.expr_contains_resume_apply(expr) => {
2111                self.lower_thunk_for_handler_reentry(expr)
2112            }
2113            _ => self.lower_thunk(expr),
2114        }
2115    }
2116
2117    fn lower_thunk_for_handler_reentry(
2118        &mut self,
2119        expr: &runtime::Expr,
2120    ) -> CpsLowerResult<CpsValueId> {
2121        self.lower_expr_with_forced_effectful_applies_inner(|this| this.lower_thunk(expr))
2122    }
2123
2124    fn lower_expr_with_forced_effectful_applies(
2125        &mut self,
2126        expr: &runtime::Expr,
2127    ) -> CpsLowerResult<CpsValueId> {
2128        self.lower_expr_with_forced_effectful_applies_inner(|this| this.lower_expr(expr))
2129    }
2130
2131    fn lower_expr_with_forced_effectful_applies_inner<T>(
2132        &mut self,
2133        lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2134    ) -> CpsLowerResult<T> {
2135        self.with_force_effectful_apply_depth(lower)
2136    }
2137
2138    fn with_force_effectful_apply_depth<T>(
2139        &mut self,
2140        lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2141    ) -> CpsLowerResult<T> {
2142        let _depth = self.force_effectful_apply_depth.enter();
2143        let result = lower(self);
2144        result
2145    }
2146
2147    fn with_sync_apply_for_immediate_force_depth<T>(
2148        &mut self,
2149        lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2150    ) -> CpsLowerResult<T> {
2151        let _depth = self.sync_apply_for_immediate_force_depth.enter();
2152        let result = lower(self);
2153        result
2154    }
2155
2156    fn with_sync_direct_call_for_ignored_force_depth<T>(
2157        &mut self,
2158        lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2159    ) -> CpsLowerResult<T> {
2160        let _depth = self.sync_direct_call_for_ignored_force_depth.enter();
2161        let result = lower(self);
2162        result
2163    }
2164
2165    /// Demand-side dual of `lower_expr_as_thunk_value`. When the surrounding
2166    /// expression expects a non-thunk value but the underlying call/apply may
2167    /// have produced a Thunk (e.g. an effectful helper that lowers as
2168    /// `MakeThunk` + `Return`), insert an explicit `ForceThunk` so the value
2169    /// reaches its consumer un-suspended. `ForceThunk` is a no-op on plain /
2170    /// resumption / closure values, so over-forcing is safe; we still avoid
2171    /// it when the consumer's static type is itself a Thunk so first-class
2172    /// thunk values keep flowing.
2173    fn force_if_non_thunk_demand(
2174        &mut self,
2175        value: CpsValueId,
2176        expected_ty: &runtime::Type,
2177    ) -> CpsValueId {
2178        if matches!(expected_ty, runtime::Type::Thunk { .. }) {
2179            return value;
2180        }
2181        let forced = self.fresh_value();
2182        self.current.stmts.push(CpsStmt::ForceThunk {
2183            dest: forced,
2184            thunk: value,
2185        });
2186        forced
2187    }
2188
2189    fn lower_tuple(&mut self, items: &[runtime::Expr]) -> CpsLowerResult<CpsValueId> {
2190        let items = items
2191            .iter()
2192            .map(|item| self.lower_expr(item))
2193            .collect::<CpsLowerResult<Vec<_>>>()?;
2194        let dest = self.fresh_value();
2195        self.current.stmts.push(CpsStmt::Tuple { dest, items });
2196        Ok(dest)
2197    }
2198
2199    fn lower_record(
2200        &mut self,
2201        fields: &[runtime::RecordExprField],
2202        spread: &Option<runtime::RecordSpreadExpr>,
2203    ) -> CpsLowerResult<CpsValueId> {
2204        let base = spread
2205            .as_ref()
2206            .map(|spread| match spread {
2207                runtime::RecordSpreadExpr::Head(expr) | runtime::RecordSpreadExpr::Tail(expr) => {
2208                    self.lower_expr(expr)
2209                }
2210            })
2211            .transpose()?;
2212        let fields = fields
2213            .iter()
2214            .map(|field| {
2215                Ok(CpsRecordField {
2216                    name: field.name.clone(),
2217                    value: self.lower_expr(&field.value)?,
2218                })
2219            })
2220            .collect::<CpsLowerResult<Vec<_>>>()?;
2221        let dest = self.fresh_value();
2222        self.current
2223            .stmts
2224            .push(CpsStmt::Record { dest, base, fields });
2225        Ok(dest)
2226    }
2227
2228    fn lower_variant(
2229        &mut self,
2230        tag: &typed_ir::Name,
2231        value: Option<&runtime::Expr>,
2232    ) -> CpsLowerResult<CpsValueId> {
2233        let value = value.map(|value| self.lower_expr(value)).transpose()?;
2234        let dest = self.fresh_value();
2235        self.current.stmts.push(CpsStmt::Variant {
2236            dest,
2237            tag: tag.clone(),
2238            value,
2239        });
2240        Ok(dest)
2241    }
2242
2243    fn lower_select(
2244        &mut self,
2245        base: &runtime::Expr,
2246        field: &typed_ir::Name,
2247    ) -> CpsLowerResult<CpsValueId> {
2248        let base = self.lower_expr(base)?;
2249        let dest = self.fresh_value();
2250        self.current.stmts.push(CpsStmt::Select {
2251            dest,
2252            base,
2253            field: field.clone(),
2254        });
2255        Ok(dest)
2256    }
2257
2258    fn lower_if(
2259        &mut self,
2260        cond: &runtime::Expr,
2261        then_branch: &runtime::Expr,
2262        else_branch: &runtime::Expr,
2263    ) -> CpsLowerResult<CpsValueId> {
2264        if !collect_expr_performed_effects(cond).is_empty()
2265            || self.expr_may_perform_when_evaluated(cond)
2266        {
2267            let (expected_effects, handler) = self.current_effect_context();
2268            let value_cont = self.fresh_continuation();
2269            let result = self.fresh_value();
2270            self.lower_handled_effect_condition_if(
2271                cond,
2272                then_branch,
2273                else_branch,
2274                &expected_effects,
2275                handler,
2276                Some(value_cont),
2277            )?;
2278            self.current = ContinuationBuilder::new(value_cont, vec![result]);
2279            return Ok(result);
2280        }
2281
2282        let cond = self.lower_expr(cond)?;
2283        let saved_locals = self.locals.clone();
2284        let saved_local_exprs = self.local_exprs.clone();
2285        let then_cont = self.fresh_continuation();
2286        let else_cont = self.fresh_continuation();
2287        let merge_cont = self.fresh_continuation();
2288        let result = self.fresh_value();
2289
2290        self.terminate(CpsTerminator::Branch {
2291            cond,
2292            then_cont,
2293            else_cont,
2294        });
2295        self.finish_current();
2296
2297        self.current = ContinuationBuilder::new(then_cont, Vec::new());
2298        self.locals = saved_locals.clone();
2299        self.local_exprs = saved_local_exprs.clone();
2300        let then_value = self.lower_expr(then_branch)?;
2301        self.terminate(CpsTerminator::Continue {
2302            target: merge_cont,
2303            args: vec![then_value],
2304        });
2305        self.finish_current();
2306
2307        self.current = ContinuationBuilder::new(else_cont, Vec::new());
2308        self.locals = saved_locals.clone();
2309        self.local_exprs = saved_local_exprs.clone();
2310        let else_value = self.lower_expr(else_branch)?;
2311        self.terminate(CpsTerminator::Continue {
2312            target: merge_cont,
2313            args: vec![else_value],
2314        });
2315        self.finish_current();
2316
2317        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
2318        self.locals = saved_locals;
2319        self.local_exprs = saved_local_exprs;
2320        Ok(result)
2321    }
2322
2323    fn lower_block(
2324        &mut self,
2325        stmts: &[runtime::Stmt],
2326        tail: Option<&runtime::Expr>,
2327    ) -> CpsLowerResult<CpsValueId> {
2328        let saved_locals = self.locals.clone();
2329        let saved_local_exprs = self.local_exprs.clone();
2330        let mut last_expr_value = None;
2331        for (index, stmt) in stmts.iter().enumerate() {
2332            match stmt {
2333                runtime::Stmt::Let { pattern, value } => {
2334                    last_expr_value = None;
2335                    if unused_pure_let(
2336                        pattern,
2337                        value,
2338                        &stmts[index + 1..],
2339                        tail,
2340                        self.functions,
2341                        self.bindings,
2342                    ) {
2343                        continue;
2344                    }
2345                    if let Some((name, param, body)) = recursive_lambda_let(pattern, value) {
2346                        let value = self.lower_recursive_lambda(name, param, body)?;
2347                        self.locals
2348                            .insert(typed_ir::Path::from_name(name.clone()), value);
2349                        continue;
2350                    }
2351                    let value = self.lower_expr(value)?;
2352                    self.bind_pattern(pattern, value)?;
2353                }
2354                runtime::Stmt::Expr(expr) => {
2355                    if !stmts[index + 1..].is_empty() || tail.is_some() {
2356                        if let runtime::ExprKind::BindHere { expr: inner } = &expr.kind {
2357                            let thunk =
2358                                self.with_sync_direct_call_for_ignored_force_depth(|this| {
2359                                    this.with_sync_apply_for_immediate_force_depth(|this| {
2360                                        this.lower_expr(inner)
2361                                    })
2362                                })?;
2363                            let post_cont = self.fresh_continuation();
2364                            let ignored = self.fresh_value();
2365                            self.terminate(CpsTerminator::EffectfulForce {
2366                                thunk,
2367                                resume: post_cont,
2368                            });
2369                            self.finish_current();
2370                            self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
2371                            let value = self.lower_block(&stmts[index + 1..], tail)?;
2372                            self.locals = saved_locals;
2373                            self.local_exprs = saved_local_exprs;
2374                            return Ok(value);
2375                        }
2376                    }
2377                    last_expr_value = Some(self.lower_expr(expr)?);
2378                }
2379                runtime::Stmt::Module { .. } => {
2380                    self.locals = saved_locals;
2381                    return Err(CpsLowerError::UnsupportedExpr {
2382                        kind: "module statement",
2383                    });
2384                }
2385            }
2386        }
2387        let value = match tail {
2388            Some(tail) => self.lower_expr(tail)?,
2389            None => match last_expr_value {
2390                Some(value) => value,
2391                None => {
2392                    let value = self.fresh_value();
2393                    self.current.stmts.push(CpsStmt::Literal {
2394                        dest: value,
2395                        literal: CpsLiteral::Unit,
2396                    });
2397                    value
2398                }
2399            },
2400        };
2401        self.locals = saved_locals;
2402        self.local_exprs = saved_local_exprs;
2403        Ok(value)
2404    }
2405
2406    fn lower_match(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
2407        let runtime::ExprKind::Match {
2408            scrutinee, arms, ..
2409        } = &expr.kind
2410        else {
2411            return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
2412        };
2413        let scrutinee = self.lower_expr(scrutinee)?;
2414        let saved_locals = self.locals.clone();
2415        let saved_local_exprs = self.local_exprs.clone();
2416        let saved_resumptions = self.resumptions.clone();
2417        let merge_cont = self.fresh_continuation();
2418        let result = self.fresh_value();
2419        let fallback_cont = self.fresh_continuation();
2420        let mut arm_conts = Vec::with_capacity(arms.len());
2421        let mut guard_conts = Vec::with_capacity(arms.len());
2422        let mut next_conts = Vec::with_capacity(arms.len());
2423        for _ in arms {
2424            arm_conts.push(self.fresh_continuation());
2425            guard_conts.push(None);
2426        }
2427
2428        let mut current_test_cont = None;
2429        for (index, arm) in arms.iter().enumerate() {
2430            if let Some(test_cont) = current_test_cont {
2431                self.current = ContinuationBuilder::new(test_cont, Vec::new());
2432                self.locals = saved_locals.clone();
2433                self.local_exprs = saved_local_exprs.clone();
2434                self.resumptions = saved_resumptions.clone();
2435            }
2436            let next_cont = if index + 1 == arms.len() {
2437                fallback_cont
2438            } else {
2439                let next = self.fresh_continuation();
2440                current_test_cont = Some(next);
2441                next
2442            };
2443            next_conts.push(next_cont);
2444            let success_cont = if arm.guard.is_some() {
2445                let guard_cont = self.fresh_continuation();
2446                guard_conts[index] = Some(guard_cont);
2447                guard_cont
2448            } else {
2449                arm_conts[index]
2450            };
2451            if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
2452                self.finish_current();
2453            }
2454        }
2455
2456        self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
2457        let unit = self.fresh_value();
2458        self.current.stmts.push(CpsStmt::Literal {
2459            dest: unit,
2460            literal: CpsLiteral::Unit,
2461        });
2462        self.terminate(CpsTerminator::Continue {
2463            target: merge_cont,
2464            args: vec![unit],
2465        });
2466        self.finish_current();
2467
2468        for (index, arm) in arms.iter().enumerate() {
2469            let Some(guard_cont) = guard_conts[index] else {
2470                continue;
2471            };
2472            let Some(guard) = &arm.guard else {
2473                continue;
2474            };
2475            self.current = ContinuationBuilder::new(guard_cont, Vec::new());
2476            self.locals = saved_locals.clone();
2477            self.local_exprs = saved_local_exprs.clone();
2478            self.resumptions = saved_resumptions.clone();
2479            self.bind_pattern(&arm.pattern, scrutinee)?;
2480            if !collect_expr_performed_effects(guard).is_empty() {
2481                let (expected_effects, handler) = self.current_effect_context();
2482                let guard_value_cont = self.fresh_continuation();
2483                let guard_value = self.fresh_value();
2484                self.lower_handled_body(guard, &expected_effects, handler, Some(guard_value_cont))?;
2485                self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
2486                self.locals = saved_locals.clone();
2487                self.local_exprs = saved_local_exprs.clone();
2488                self.resumptions = saved_resumptions.clone();
2489                self.terminate(CpsTerminator::Branch {
2490                    cond: guard_value,
2491                    then_cont: arm_conts[index],
2492                    else_cont: next_conts[index],
2493                });
2494                self.finish_current();
2495            } else {
2496                let guard_value = self.lower_expr(guard)?;
2497                self.terminate(CpsTerminator::Branch {
2498                    cond: guard_value,
2499                    then_cont: arm_conts[index],
2500                    else_cont: next_conts[index],
2501                });
2502                self.finish_current();
2503            }
2504        }
2505
2506        for (arm, arm_cont) in arms.iter().zip(arm_conts) {
2507            self.current = ContinuationBuilder::new(arm_cont, Vec::new());
2508            self.locals = saved_locals.clone();
2509            self.local_exprs = saved_local_exprs.clone();
2510            self.resumptions = saved_resumptions.clone();
2511            self.bind_pattern(&arm.pattern, scrutinee)?;
2512            let value = self.lower_expr(&arm.body)?;
2513            self.terminate(CpsTerminator::Continue {
2514                target: merge_cont,
2515                args: vec![value],
2516            });
2517            self.finish_current();
2518        }
2519
2520        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
2521        self.locals = saved_locals;
2522        self.local_exprs = saved_local_exprs;
2523        self.resumptions = saved_resumptions;
2524        Ok(result)
2525    }
2526
2527    fn lower_pattern_test(
2528        &mut self,
2529        value: CpsValueId,
2530        pattern: &runtime::Pattern,
2531        then_cont: CpsContinuationId,
2532        else_cont: CpsContinuationId,
2533    ) -> CpsLowerResult<bool> {
2534        match pattern {
2535            runtime::Pattern::Wildcard { .. } | runtime::Pattern::Bind { .. } => {
2536                self.terminate(CpsTerminator::Continue {
2537                    target: then_cont,
2538                    args: Vec::new(),
2539                });
2540                Ok(true)
2541            }
2542            runtime::Pattern::Lit {
2543                lit: typed_ir::Lit::Bool(true),
2544                ..
2545            } => {
2546                self.terminate(CpsTerminator::Branch {
2547                    cond: value,
2548                    then_cont,
2549                    else_cont,
2550                });
2551                Ok(true)
2552            }
2553            runtime::Pattern::Lit {
2554                lit: typed_ir::Lit::Bool(false),
2555                ..
2556            } => {
2557                let cond = self.fresh_value();
2558                self.current.stmts.push(CpsStmt::Primitive {
2559                    dest: cond,
2560                    op: typed_ir::PrimitiveOp::BoolNot,
2561                    args: vec![value],
2562                });
2563                self.terminate(CpsTerminator::Branch {
2564                    cond,
2565                    then_cont,
2566                    else_cont,
2567                });
2568                Ok(true)
2569            }
2570            runtime::Pattern::Lit {
2571                lit: typed_ir::Lit::Unit,
2572                ..
2573            } => {
2574                self.terminate(CpsTerminator::Continue {
2575                    target: then_cont,
2576                    args: Vec::new(),
2577                });
2578                Ok(true)
2579            }
2580            runtime::Pattern::Lit {
2581                lit: typed_ir::Lit::Int(expected),
2582                ..
2583            } => {
2584                let literal = self.fresh_value();
2585                self.current.stmts.push(CpsStmt::Literal {
2586                    dest: literal,
2587                    literal: CpsLiteral::Int(expected.clone()),
2588                });
2589                let cond = self.fresh_value();
2590                self.current.stmts.push(CpsStmt::Primitive {
2591                    dest: cond,
2592                    op: typed_ir::PrimitiveOp::IntEq,
2593                    args: vec![value, literal],
2594                });
2595                self.terminate(CpsTerminator::Branch {
2596                    cond,
2597                    then_cont,
2598                    else_cont,
2599                });
2600                Ok(true)
2601            }
2602            runtime::Pattern::Tuple { items, .. } => {
2603                self.lower_tuple_pattern_test(value, items, 0, then_cont, else_cont)
2604            }
2605            runtime::Pattern::List {
2606                prefix,
2607                spread,
2608                suffix,
2609                ..
2610            } => self.lower_list_pattern_test(
2611                value,
2612                prefix,
2613                spread.as_deref(),
2614                suffix,
2615                then_cont,
2616                else_cont,
2617            ),
2618            runtime::Pattern::Record { fields, spread, .. } => {
2619                self.lower_record_pattern_test(value, fields, spread.as_ref(), then_cont, else_cont)
2620            }
2621            runtime::Pattern::Variant {
2622                tag,
2623                value: payload,
2624                ..
2625            } => {
2626                let cond = self.fresh_value();
2627                self.current.stmts.push(CpsStmt::VariantTagEq {
2628                    dest: cond,
2629                    variant: value,
2630                    tag: tag.clone(),
2631                });
2632                let matched_cont = if payload.is_some() {
2633                    self.fresh_continuation()
2634                } else {
2635                    then_cont
2636                };
2637                self.terminate(CpsTerminator::Branch {
2638                    cond,
2639                    then_cont: matched_cont,
2640                    else_cont,
2641                });
2642                if let Some(payload) = payload {
2643                    self.finish_current();
2644                    self.current = ContinuationBuilder::new(matched_cont, Vec::new());
2645                    let payload_value = self.fresh_value();
2646                    self.current.stmts.push(CpsStmt::VariantPayload {
2647                        dest: payload_value,
2648                        variant: value,
2649                    });
2650                    self.lower_pattern_test(payload_value, payload, then_cont, else_cont)
2651                } else {
2652                    Ok(true)
2653                }
2654            }
2655            runtime::Pattern::Or { left, right, .. } => {
2656                let right_cont = self.fresh_continuation();
2657                self.lower_pattern_test(value, left, then_cont, right_cont)?;
2658                self.finish_current();
2659                self.current = ContinuationBuilder::new(right_cont, Vec::new());
2660                self.lower_pattern_test(value, right, then_cont, else_cont)
2661            }
2662            runtime::Pattern::As { pattern, .. } => {
2663                self.lower_pattern_test(value, pattern, then_cont, else_cont)
2664            }
2665            _ => Err(CpsLowerError::UnsupportedPattern {
2666                kind: "match pattern",
2667            }),
2668        }
2669    }
2670
2671    fn lower_tuple_pattern_test(
2672        &mut self,
2673        value: CpsValueId,
2674        items: &[runtime::Pattern],
2675        index: usize,
2676        then_cont: CpsContinuationId,
2677        else_cont: CpsContinuationId,
2678    ) -> CpsLowerResult<bool> {
2679        let Some(item) = items.get(index) else {
2680            self.terminate(CpsTerminator::Continue {
2681                target: then_cont,
2682                args: Vec::new(),
2683            });
2684            return Ok(true);
2685        };
2686        let next_cont = self.fresh_continuation();
2687        let item_value = self.fresh_value();
2688        self.current.stmts.push(CpsStmt::TupleGet {
2689            dest: item_value,
2690            tuple: value,
2691            index,
2692        });
2693        self.lower_pattern_test(item_value, item, next_cont, else_cont)?;
2694        self.finish_current();
2695        self.current = ContinuationBuilder::new(next_cont, Vec::new());
2696        self.lower_tuple_pattern_test(value, items, index + 1, then_cont, else_cont)
2697    }
2698
2699    fn lower_list_pattern_test(
2700        &mut self,
2701        value: CpsValueId,
2702        prefix: &[runtime::Pattern],
2703        spread: Option<&runtime::Pattern>,
2704        suffix: &[runtime::Pattern],
2705        then_cont: CpsContinuationId,
2706        else_cont: CpsContinuationId,
2707    ) -> CpsLowerResult<bool> {
2708        let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2709        let required = self.emit_int_literal((prefix.len() + suffix.len()) as i64);
2710        let op = if spread.is_some() {
2711            typed_ir::PrimitiveOp::IntGe
2712        } else {
2713            typed_ir::PrimitiveOp::IntEq
2714        };
2715        let len_cond = self.emit_primitive(op, vec![len, required]);
2716        let items_cont = self.fresh_continuation();
2717        self.terminate(CpsTerminator::Branch {
2718            cond: len_cond,
2719            then_cont: items_cont,
2720            else_cont,
2721        });
2722        self.finish_current();
2723        self.current = ContinuationBuilder::new(items_cont, Vec::new());
2724
2725        let mut tests = Vec::new();
2726        for (index, item) in prefix.iter().enumerate() {
2727            let index = self.emit_int_literal(index as i64);
2728            let item_value =
2729                self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
2730            tests.push((item_value, item));
2731        }
2732        if let Some(spread) = spread {
2733            let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2734            let start = self.emit_int_literal(prefix.len() as i64);
2735            let suffix_len = self.emit_int_literal(suffix.len() as i64);
2736            let end = self.emit_primitive(typed_ir::PrimitiveOp::IntSub, vec![len, suffix_len]);
2737            let slice = self.emit_primitive(
2738                typed_ir::PrimitiveOp::ListIndexRangeRaw,
2739                vec![value, start, end],
2740            );
2741            tests.push((slice, spread));
2742        }
2743        for (offset, item) in suffix.iter().enumerate() {
2744            let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2745            let suffix_len = self.emit_int_literal(suffix.len() as i64);
2746            let suffix_start =
2747                self.emit_primitive(typed_ir::PrimitiveOp::IntSub, vec![len, suffix_len]);
2748            let offset = self.emit_int_literal(offset as i64);
2749            let index =
2750                self.emit_primitive(typed_ir::PrimitiveOp::IntAdd, vec![suffix_start, offset]);
2751            let item_value =
2752                self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
2753            tests.push((item_value, item));
2754        }
2755        self.lower_extracted_pattern_tests(tests, 0, then_cont, else_cont)
2756    }
2757
2758    fn lower_record_pattern_test(
2759        &mut self,
2760        value: CpsValueId,
2761        fields: &[runtime::RecordPatternField],
2762        spread: Option<&runtime::RecordSpreadPattern>,
2763        then_cont: CpsContinuationId,
2764        else_cont: CpsContinuationId,
2765    ) -> CpsLowerResult<bool> {
2766        let fields_done = self.fresh_continuation();
2767        self.lower_record_field_pattern_tests(value, fields, 0, fields_done, else_cont)?;
2768        self.finish_current();
2769        self.current = ContinuationBuilder::new(fields_done, Vec::new());
2770        if let Some(spread) = record_spread_pattern(spread) {
2771            let rest = self.emit_record_without_fields(value, fields);
2772            self.lower_pattern_test(rest, spread, then_cont, else_cont)
2773        } else {
2774            self.terminate(CpsTerminator::Continue {
2775                target: then_cont,
2776                args: Vec::new(),
2777            });
2778            Ok(true)
2779        }
2780    }
2781
2782    fn lower_record_field_pattern_tests(
2783        &mut self,
2784        value: CpsValueId,
2785        fields: &[runtime::RecordPatternField],
2786        index: usize,
2787        then_cont: CpsContinuationId,
2788        else_cont: CpsContinuationId,
2789    ) -> CpsLowerResult<bool> {
2790        let Some(field) = fields.get(index) else {
2791            self.terminate(CpsTerminator::Continue {
2792                target: then_cont,
2793                args: Vec::new(),
2794            });
2795            return Ok(true);
2796        };
2797        let field_value = self.fresh_value();
2798        if let Some(default) = &field.default {
2799            let default = self.lower_expr(default)?;
2800            self.current.stmts.push(CpsStmt::SelectWithDefault {
2801                dest: field_value,
2802                base: value,
2803                field: field.name.clone(),
2804                default,
2805            });
2806        } else {
2807            let present = self.fresh_value();
2808            let present_cont = self.fresh_continuation();
2809            self.current.stmts.push(CpsStmt::RecordHasField {
2810                dest: present,
2811                base: value,
2812                field: field.name.clone(),
2813            });
2814            self.terminate(CpsTerminator::Branch {
2815                cond: present,
2816                then_cont: present_cont,
2817                else_cont,
2818            });
2819            self.finish_current();
2820            self.current = ContinuationBuilder::new(present_cont, Vec::new());
2821            self.current.stmts.push(CpsStmt::Select {
2822                dest: field_value,
2823                base: value,
2824                field: field.name.clone(),
2825            });
2826        }
2827        let next_cont = self.fresh_continuation();
2828        self.lower_pattern_test(field_value, &field.pattern, next_cont, else_cont)?;
2829        self.finish_current();
2830        self.current = ContinuationBuilder::new(next_cont, Vec::new());
2831        self.lower_record_field_pattern_tests(value, fields, index + 1, then_cont, else_cont)
2832    }
2833
2834    fn lower_extracted_pattern_tests(
2835        &mut self,
2836        tests: Vec<(CpsValueId, &runtime::Pattern)>,
2837        index: usize,
2838        then_cont: CpsContinuationId,
2839        else_cont: CpsContinuationId,
2840    ) -> CpsLowerResult<bool> {
2841        let Some((value, pattern)) = tests.get(index).copied() else {
2842            self.terminate(CpsTerminator::Continue {
2843                target: then_cont,
2844                args: Vec::new(),
2845            });
2846            return Ok(true);
2847        };
2848        let next_cont = self.fresh_continuation();
2849        self.lower_pattern_test(value, pattern, next_cont, else_cont)?;
2850        self.finish_current();
2851        self.current = ContinuationBuilder::new(next_cont, Vec::new());
2852        self.lower_extracted_pattern_tests(tests, index + 1, then_cont, else_cont)
2853    }
2854
2855    fn lower_handle(
2856        &mut self,
2857        body: &runtime::Expr,
2858        arms: &[runtime::HandleArm],
2859        consumes: &[typed_ir::Path],
2860    ) -> CpsLowerResult<CpsValueId> {
2861        let mut value_arms = arms
2862            .iter()
2863            .filter(|arm| arm.resume.is_none() && arm.effect.segments.is_empty());
2864        let value_arm = value_arms.next();
2865        if value_arms.next().is_some() {
2866            return Err(CpsLowerError::UnsupportedExpr {
2867                kind: "multi-value handler",
2868            });
2869        }
2870        if value_arm.is_some_and(|arm| arm.guard.is_some()) {
2871            return Err(CpsLowerError::UnsupportedExpr {
2872                kind: "handler guard",
2873            });
2874        }
2875
2876        let effect_arms = arms
2877            .iter()
2878            .filter(|arm| arm.resume.is_some())
2879            .collect::<Vec<_>>();
2880        if effect_arms.is_empty() {
2881            let value = self.lower_expr(body)?;
2882            return self.lower_handle_value(value, value_arm);
2883        };
2884        if arms
2885            .iter()
2886            .any(|candidate| candidate.resume.is_none() && !candidate.effect.segments.is_empty())
2887        {
2888            return Err(CpsLowerError::UnsupportedExpr {
2889                kind: "handler without resume",
2890            });
2891        }
2892        let saved_locals = self.locals.clone();
2893        let saved_local_exprs = self.local_exprs.clone();
2894        let saved_resumptions = self.resumptions.clone();
2895        let saved_forced_handler_effects_len = self.forced_handler_effects.len();
2896        let saved_handler_value_conts_len = self.handler_value_conts.len();
2897
2898        let value_cont = self.fresh_continuation();
2899        let merge_cont = self.fresh_continuation();
2900        let handler = self.fresh_handler();
2901        let result = self.fresh_value();
2902        let effects = effect_arms
2903            .iter()
2904            .flat_map(|arm| scoped_handler_effects(consumes, &arm.effect))
2905            .collect::<Vec<_>>();
2906
2907        let saved_active_handler = self.active_handler.clone();
2908        self.active_handler = Some(ActiveHandlerContext {
2909            handler,
2910            expected_effects: effects.clone(),
2911            parent: saved_active_handler.clone().map(Box::new),
2912        });
2913        self.handler_value_conts.push(value_cont);
2914        self.current.stmts.push(CpsStmt::InstallHandler {
2915            handler,
2916            envs: Vec::new(),
2917            value: value_cont,
2918            // ScopeReturn lands at the merge continuation, NOT the
2919            // value-arm continuation. Effect arm bodies return an arm
2920            // answer to the Perform dispatcher; only the dispatcher may
2921            // route that answer into this handler scope's escape target.
2922            escape: merge_cont,
2923        });
2924        self.lower_handled_body(body, &effects, handler, None)?;
2925        self.handler_value_conts
2926            .truncate(saved_handler_value_conts_len);
2927        self.active_handler = saved_active_handler.clone();
2928        let used_effects = self.performed_effects_for_handler(handler);
2929        let body_made_external_call = self.handlers_with_external_calls.contains(&handler);
2930        let mut handler_entries: Vec<(typed_ir::Path, CpsContinuationId, Vec<usize>)> =
2931            Vec::with_capacity(effect_arms.len());
2932        for (arm_index, arm) in effect_arms.iter().enumerate() {
2933            for effect in scoped_handler_effects(consumes, &arm.effect) {
2934                let directly_used = used_effects
2935                    .iter()
2936                    .any(|used| effect_matches(&effect, used) || effect_matches(used, &effect));
2937                // Materialize all handler arms when the body forwards effects through
2938                // an effectful direct call (e.g. recursive helper returning [eff] T):
2939                // the callee performs effects with handler = dynamic_handler_id() so
2940                // performed_effects_for_handler cannot see them statically.
2941                if directly_used || body_made_external_call {
2942                    if let Some((_, _, arm_indices)) = handler_entries
2943                        .iter_mut()
2944                        .find(|(existing, _, _)| existing == &effect)
2945                    {
2946                        arm_indices.push(arm_index);
2947                    } else {
2948                        handler_entries.push((effect, self.fresh_continuation(), vec![arm_index]));
2949                    }
2950                }
2951            }
2952        }
2953        if !handler_entries.is_empty() {
2954            self.handlers.push(CpsHandler {
2955                id: handler,
2956                arms: handler_entries
2957                    .iter()
2958                    .map(|(effect, entry, _)| CpsHandlerArm {
2959                        effect: effect.clone(),
2960                        entry: *entry,
2961                    })
2962                    .collect(),
2963            });
2964        }
2965
2966        let handled_value = self.fresh_value();
2967        self.current = ContinuationBuilder::new(value_cont, vec![handled_value]);
2968        self.current
2969            .stmts
2970            .push(CpsStmt::UninstallHandler { handler });
2971        self.locals = saved_locals.clone();
2972        self.local_exprs = saved_local_exprs.clone();
2973        self.resumptions = saved_resumptions.clone();
2974        let handled = self.lower_handle_value(handled_value, value_arm)?;
2975        self.terminate(CpsTerminator::Continue {
2976            target: merge_cont,
2977            args: vec![handled],
2978        });
2979        self.finish_current();
2980
2981        for (effect, handler_cont, arm_indices) in handler_entries {
2982            let handler_payload = self.fresh_value();
2983            let handler_resume = self.fresh_value();
2984            self.current =
2985                ContinuationBuilder::new(handler_cont, vec![handler_payload, handler_resume]);
2986            self.current
2987                .stmts
2988                .push(CpsStmt::UninstallHandler { handler });
2989            self.locals = saved_locals.clone();
2990            self.local_exprs = saved_local_exprs.clone();
2991            self.resumptions = saved_resumptions.clone();
2992            self.lower_effect_handler_arm_chain(
2993                effect_arms.as_slice(),
2994                &arm_indices,
2995                0,
2996                EffectHandlerArmChain {
2997                    effect: &effect,
2998                    payload: handler_payload,
2999                    resume: handler_resume,
3000                    handler,
3001                    saved_locals: &saved_locals,
3002                    saved_local_exprs: &saved_local_exprs,
3003                    saved_resumptions: &saved_resumptions,
3004                },
3005            )?;
3006        }
3007
3008        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3009        self.locals = saved_locals;
3010        self.local_exprs = saved_local_exprs;
3011        self.resumptions = saved_resumptions;
3012        self.active_handler = saved_active_handler;
3013        self.forced_handler_effects
3014            .truncate(saved_forced_handler_effects_len);
3015        self.handler_value_conts
3016            .truncate(saved_handler_value_conts_len);
3017        Ok(result)
3018    }
3019
3020    fn lower_effect_handler_arm_chain(
3021        &mut self,
3022        effect_arms: &[&runtime::HandleArm],
3023        arm_indices: &[usize],
3024        index: usize,
3025        ctx: EffectHandlerArmChain<'_>,
3026    ) -> CpsLowerResult<()> {
3027        let Some(arm_index) = arm_indices.get(index).copied() else {
3028            let unit = self.fresh_value();
3029            self.current.stmts.push(CpsStmt::Literal {
3030                dest: unit,
3031                literal: CpsLiteral::Unit,
3032            });
3033            self.terminate(CpsTerminator::Return(unit));
3034            self.finish_current();
3035            return Ok(());
3036        };
3037        let arm = effect_arms[arm_index];
3038        let next_cont = self.fresh_continuation();
3039        let body_cont = self.fresh_continuation();
3040        let success_cont = if arm.guard.is_some() {
3041            self.fresh_continuation()
3042        } else {
3043            body_cont
3044        };
3045
3046        if self.lower_pattern_test(ctx.payload, &arm.payload, success_cont, next_cont)? {
3047            self.finish_current();
3048        }
3049
3050        if let Some(guard) = &arm.guard {
3051            self.current = ContinuationBuilder::new(success_cont, Vec::new());
3052            self.locals = ctx.saved_locals.clone();
3053            self.local_exprs = ctx.saved_local_exprs.clone();
3054            self.resumptions = ctx.saved_resumptions.clone();
3055            self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3056            if !collect_expr_performed_effects(guard).is_empty()
3057                || self.expr_may_perform_when_evaluated(guard)
3058            {
3059                let guard_value_cont = self.fresh_continuation();
3060                let guard_value = self.fresh_value();
3061                self.lower_handled_body(
3062                    guard,
3063                    std::slice::from_ref(ctx.effect),
3064                    ctx.handler,
3065                    Some(guard_value_cont),
3066                )?;
3067                self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
3068                self.locals = ctx.saved_locals.clone();
3069                self.local_exprs = ctx.saved_local_exprs.clone();
3070                self.resumptions = ctx.saved_resumptions.clone();
3071                self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3072                self.terminate(CpsTerminator::Branch {
3073                    cond: guard_value,
3074                    then_cont: body_cont,
3075                    else_cont: next_cont,
3076                });
3077                self.finish_current();
3078            } else {
3079                let guard_value = self.lower_expr(guard)?;
3080                self.terminate(CpsTerminator::Branch {
3081                    cond: guard_value,
3082                    then_cont: body_cont,
3083                    else_cont: next_cont,
3084                });
3085                self.finish_current();
3086            }
3087        }
3088
3089        self.current = ContinuationBuilder::new(body_cont, Vec::new());
3090        self.locals = ctx.saved_locals.clone();
3091        self.local_exprs = ctx.saved_local_exprs.clone();
3092        self.resumptions = ctx.saved_resumptions.clone();
3093        self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3094        let handled = self.lower_handler_body_expr(&arm.body, Some(ctx.handler))?;
3095        self.terminate(CpsTerminator::Return(handled));
3096        self.finish_current();
3097
3098        self.current = ContinuationBuilder::new(next_cont, Vec::new());
3099        self.locals = ctx.saved_locals.clone();
3100        self.local_exprs = ctx.saved_local_exprs.clone();
3101        self.resumptions = ctx.saved_resumptions.clone();
3102        self.lower_effect_handler_arm_chain(effect_arms, arm_indices, index + 1, ctx)
3103    }
3104
3105    fn bind_effect_handler_arm_locals(
3106        &mut self,
3107        arm: &runtime::HandleArm,
3108        payload: CpsValueId,
3109        resume: CpsValueId,
3110    ) -> CpsLowerResult<()> {
3111        let Some(resume_binding) = &arm.resume else {
3112            return Err(CpsLowerError::UnsupportedExpr {
3113                kind: "handler without resume",
3114            });
3115        };
3116        self.bind_pattern(&arm.payload, payload)?;
3117        let resume_path = typed_ir::Path::from_name(resume_binding.name.clone());
3118        self.locals.insert(resume_path.clone(), resume);
3119        self.resumptions.insert(resume_path);
3120        Ok(())
3121    }
3122
3123    fn lower_handle_value(
3124        &mut self,
3125        value: CpsValueId,
3126        value_arm: Option<&runtime::HandleArm>,
3127    ) -> CpsLowerResult<CpsValueId> {
3128        let Some(arm) = value_arm else {
3129            return Ok(value);
3130        };
3131        self.bind_pattern(&arm.payload, value)?;
3132        self.lower_handler_body_expr(&arm.body, None)
3133    }
3134
3135    fn lower_handler_body_expr(
3136        &mut self,
3137        expr: &runtime::Expr,
3138        handler: Option<CpsHandlerId>,
3139    ) -> CpsLowerResult<CpsValueId> {
3140        if let Some(inner) = handler_body_plain_value_inner(expr) {
3141            return self.lower_expr(inner);
3142        }
3143        if let Some(handler) = handler
3144            && let Some(reentry) = self.handler_reentry_apply(expr, handler)?
3145        {
3146            self.current.stmts.push(CpsStmt::ResumeWithHandler {
3147                dest: reentry.dest,
3148                resumption: reentry.resumption,
3149                arg: reentry.arg,
3150                handler,
3151                envs: reentry.envs,
3152            });
3153            return Ok(reentry.dest);
3154        }
3155        if handler.is_some() && self.apply_chain_contains_resume_argument(expr) {
3156            return self.lower_expr_with_forced_effectful_applies(expr);
3157        }
3158        if let Some(handler) = handler
3159            && let runtime::ExprKind::Block { stmts, tail } = &expr.kind
3160        {
3161            if stmts.is_empty()
3162                && let Some(tail) = tail
3163            {
3164                return self.lower_handler_body_expr(tail, Some(handler));
3165            }
3166            return self.lower_handler_body_block(stmts, tail.as_deref(), handler);
3167        }
3168        if let Some(handler) = handler
3169            && let runtime::ExprKind::If {
3170                cond,
3171                then_branch,
3172                else_branch,
3173                ..
3174            } = &expr.kind
3175        {
3176            return self.lower_handler_body_if(cond, then_branch, else_branch, handler);
3177        }
3178        if let Some(handler) = handler
3179            && let runtime::ExprKind::Match { .. } = &expr.kind
3180        {
3181            return self.lower_handler_body_match(expr, handler);
3182        }
3183        self.lower_expr(expr)
3184    }
3185
3186    fn lower_handler_body_block(
3187        &mut self,
3188        stmts: &[runtime::Stmt],
3189        tail: Option<&runtime::Expr>,
3190        handler: CpsHandlerId,
3191    ) -> CpsLowerResult<CpsValueId> {
3192        let value_cont = self.fresh_continuation();
3193        let value = self.fresh_value();
3194        let (expected_effects, condition_handler) = self.handler_body_effect_context(
3195            tail.unwrap_or_else(|| {
3196                stmts
3197                    .last()
3198                    .and_then(|stmt| match stmt {
3199                        runtime::Stmt::Let { value, .. }
3200                        | runtime::Stmt::Expr(value)
3201                        | runtime::Stmt::Module { body: value, .. } => Some(value),
3202                    })
3203                    .expect("non-empty handler block")
3204            }),
3205            handler,
3206        );
3207        self.lower_handled_block(
3208            stmts,
3209            tail,
3210            &expected_effects,
3211            condition_handler,
3212            Some(value_cont),
3213        )?;
3214        self.current = ContinuationBuilder::new(value_cont, vec![value]);
3215        Ok(value)
3216    }
3217
3218    fn lower_handler_body_if(
3219        &mut self,
3220        cond: &runtime::Expr,
3221        then_branch: &runtime::Expr,
3222        else_branch: &runtime::Expr,
3223        handler: CpsHandlerId,
3224    ) -> CpsLowerResult<CpsValueId> {
3225        if !collect_expr_performed_effects(cond).is_empty()
3226            || self.expr_may_perform_when_evaluated(cond)
3227        {
3228            return self.lower_handler_body_effect_condition_if(
3229                cond,
3230                then_branch,
3231                else_branch,
3232                handler,
3233            );
3234        }
3235
3236        let cond_value = if let Some(reentry) = self.handler_reentry_apply(cond, handler)? {
3237            self.current.stmts.push(CpsStmt::ResumeWithHandler {
3238                dest: reentry.dest,
3239                resumption: reentry.resumption,
3240                arg: reentry.arg,
3241                handler,
3242                envs: reentry.envs,
3243            });
3244            reentry.dest
3245        } else {
3246            self.lower_expr(cond)?
3247        };
3248
3249        let saved_locals = self.locals.clone();
3250        let saved_local_exprs = self.local_exprs.clone();
3251        let saved_resumptions = self.resumptions.clone();
3252        let then_cont = self.fresh_continuation();
3253        let else_cont = self.fresh_continuation();
3254        let merge_cont = self.fresh_continuation();
3255        let result = self.fresh_value();
3256
3257        self.terminate(CpsTerminator::Branch {
3258            cond: cond_value,
3259            then_cont,
3260            else_cont,
3261        });
3262        self.finish_current();
3263
3264        self.current = ContinuationBuilder::new(then_cont, Vec::new());
3265        self.locals = saved_locals.clone();
3266        self.local_exprs = saved_local_exprs.clone();
3267        self.resumptions = saved_resumptions.clone();
3268        let then_value = self.lower_handler_body_expr(then_branch, Some(handler))?;
3269        self.terminate(CpsTerminator::Continue {
3270            target: merge_cont,
3271            args: vec![then_value],
3272        });
3273        self.finish_current();
3274
3275        self.current = ContinuationBuilder::new(else_cont, Vec::new());
3276        self.locals = saved_locals.clone();
3277        self.local_exprs = saved_local_exprs.clone();
3278        self.resumptions = saved_resumptions.clone();
3279        let else_value = self.lower_handler_body_expr(else_branch, Some(handler))?;
3280        self.terminate(CpsTerminator::Continue {
3281            target: merge_cont,
3282            args: vec![else_value],
3283        });
3284        self.finish_current();
3285
3286        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3287        self.locals = saved_locals;
3288        self.local_exprs = saved_local_exprs;
3289        self.resumptions = saved_resumptions;
3290        Ok(result)
3291    }
3292
3293    fn lower_handler_body_effect_condition_if(
3294        &mut self,
3295        cond: &runtime::Expr,
3296        then_branch: &runtime::Expr,
3297        else_branch: &runtime::Expr,
3298        handler: CpsHandlerId,
3299    ) -> CpsLowerResult<CpsValueId> {
3300        let saved_locals = self.locals.clone();
3301        let saved_local_exprs = self.local_exprs.clone();
3302        let saved_resumptions = self.resumptions.clone();
3303        let cond_cont = self.fresh_continuation();
3304        let then_cont = self.fresh_continuation();
3305        let else_cont = self.fresh_continuation();
3306        let merge_cont = self.fresh_continuation();
3307        let result = self.fresh_value();
3308        let cond_value = self.fresh_value();
3309        let (expected_effects, condition_handler) = self.handler_body_effect_context(cond, handler);
3310
3311        self.lower_effectful_condition_to_continuation(
3312            cond,
3313            &expected_effects,
3314            condition_handler,
3315            cond_cont,
3316        )?;
3317
3318        self.current = ContinuationBuilder::new(cond_cont, vec![cond_value]);
3319        self.locals = saved_locals.clone();
3320        self.local_exprs = saved_local_exprs.clone();
3321        self.resumptions = saved_resumptions.clone();
3322        self.terminate(CpsTerminator::Branch {
3323            cond: cond_value,
3324            then_cont,
3325            else_cont,
3326        });
3327        self.finish_current();
3328
3329        self.current = ContinuationBuilder::new(then_cont, Vec::new());
3330        self.locals = saved_locals.clone();
3331        self.local_exprs = saved_local_exprs.clone();
3332        self.resumptions = saved_resumptions.clone();
3333        let then_value = self.lower_handler_body_expr(then_branch, Some(handler))?;
3334        self.terminate(CpsTerminator::Continue {
3335            target: merge_cont,
3336            args: vec![then_value],
3337        });
3338        self.finish_current();
3339
3340        self.current = ContinuationBuilder::new(else_cont, Vec::new());
3341        self.locals = saved_locals.clone();
3342        self.local_exprs = saved_local_exprs.clone();
3343        self.resumptions = saved_resumptions.clone();
3344        let else_value = self.lower_handler_body_expr(else_branch, Some(handler))?;
3345        self.terminate(CpsTerminator::Continue {
3346            target: merge_cont,
3347            args: vec![else_value],
3348        });
3349        self.finish_current();
3350
3351        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3352        self.locals = saved_locals;
3353        self.local_exprs = saved_local_exprs;
3354        self.resumptions = saved_resumptions;
3355        Ok(result)
3356    }
3357
3358    fn lower_effectful_condition_to_continuation(
3359        &mut self,
3360        cond: &runtime::Expr,
3361        expected_effects: &[typed_ir::Path],
3362        handler: CpsHandlerId,
3363        cond_cont: CpsContinuationId,
3364    ) -> CpsLowerResult<typed_ir::Path> {
3365        self.lower_handled_body(cond, expected_effects, handler, Some(cond_cont))
3366    }
3367
3368    fn lower_handler_body_match(
3369        &mut self,
3370        expr: &runtime::Expr,
3371        handler: CpsHandlerId,
3372    ) -> CpsLowerResult<CpsValueId> {
3373        let runtime::ExprKind::Match {
3374            scrutinee, arms, ..
3375        } = &expr.kind
3376        else {
3377            return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
3378        };
3379        let scrutinee = if !collect_expr_performed_effects(scrutinee).is_empty()
3380            || self.expr_may_perform_when_evaluated(scrutinee)
3381        {
3382            let scrutinee_cont = self.fresh_continuation();
3383            let scrutinee_value = self.fresh_value();
3384            let (expected_effects, condition_handler) =
3385                self.handler_body_effect_context(scrutinee, handler);
3386            self.lower_handled_body(
3387                scrutinee,
3388                &expected_effects,
3389                condition_handler,
3390                Some(scrutinee_cont),
3391            )?;
3392            self.current = ContinuationBuilder::new(scrutinee_cont, vec![scrutinee_value]);
3393            scrutinee_value
3394        } else if let Some(reentry) = self.handler_reentry_apply(scrutinee, handler)? {
3395            self.current.stmts.push(CpsStmt::ResumeWithHandler {
3396                dest: reentry.dest,
3397                resumption: reentry.resumption,
3398                arg: reentry.arg,
3399                handler,
3400                envs: reentry.envs,
3401            });
3402            reentry.dest
3403        } else {
3404            self.lower_expr(scrutinee)?
3405        };
3406
3407        let saved_locals = self.locals.clone();
3408        let saved_local_exprs = self.local_exprs.clone();
3409        let saved_resumptions = self.resumptions.clone();
3410        let merge_cont = self.fresh_continuation();
3411        let result = self.fresh_value();
3412        let fallback_cont = self.fresh_continuation();
3413        let mut arm_conts = Vec::with_capacity(arms.len());
3414        let mut guard_conts = Vec::with_capacity(arms.len());
3415        let mut next_conts = Vec::with_capacity(arms.len());
3416        for _ in arms {
3417            arm_conts.push(self.fresh_continuation());
3418            guard_conts.push(None);
3419        }
3420
3421        let mut current_test_cont = None;
3422        for (index, arm) in arms.iter().enumerate() {
3423            if let Some(test_cont) = current_test_cont {
3424                self.current = ContinuationBuilder::new(test_cont, Vec::new());
3425                self.locals = saved_locals.clone();
3426                self.local_exprs = saved_local_exprs.clone();
3427                self.resumptions = saved_resumptions.clone();
3428            }
3429            let next_cont = if index + 1 == arms.len() {
3430                fallback_cont
3431            } else {
3432                let next = self.fresh_continuation();
3433                current_test_cont = Some(next);
3434                next
3435            };
3436            next_conts.push(next_cont);
3437            let success_cont = if arm.guard.is_some() {
3438                let guard_cont = self.fresh_continuation();
3439                guard_conts[index] = Some(guard_cont);
3440                guard_cont
3441            } else {
3442                arm_conts[index]
3443            };
3444            if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
3445                self.finish_current();
3446            }
3447        }
3448
3449        self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
3450        let unit = self.fresh_value();
3451        self.current.stmts.push(CpsStmt::Literal {
3452            dest: unit,
3453            literal: CpsLiteral::Unit,
3454        });
3455        self.terminate(CpsTerminator::Continue {
3456            target: merge_cont,
3457            args: vec![unit],
3458        });
3459        self.finish_current();
3460
3461        for (index, arm) in arms.iter().enumerate() {
3462            let Some(guard_cont) = guard_conts[index] else {
3463                continue;
3464            };
3465            let Some(guard) = &arm.guard else {
3466                continue;
3467            };
3468            self.current = ContinuationBuilder::new(guard_cont, Vec::new());
3469            self.locals = saved_locals.clone();
3470            self.local_exprs = saved_local_exprs.clone();
3471            self.resumptions = saved_resumptions.clone();
3472            self.bind_pattern(&arm.pattern, scrutinee)?;
3473            let guard_value = self.lower_expr(guard)?;
3474            self.terminate(CpsTerminator::Branch {
3475                cond: guard_value,
3476                then_cont: arm_conts[index],
3477                else_cont: next_conts[index],
3478            });
3479            self.finish_current();
3480        }
3481
3482        for (arm, arm_cont) in arms.iter().zip(arm_conts) {
3483            self.current = ContinuationBuilder::new(arm_cont, Vec::new());
3484            self.locals = saved_locals.clone();
3485            self.local_exprs = saved_local_exprs.clone();
3486            self.resumptions = saved_resumptions.clone();
3487            self.bind_pattern(&arm.pattern, scrutinee)?;
3488            let value = self.lower_handler_body_expr(&arm.body, Some(handler))?;
3489            self.terminate(CpsTerminator::Continue {
3490                target: merge_cont,
3491                args: vec![value],
3492            });
3493            self.finish_current();
3494        }
3495
3496        self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3497        self.locals = saved_locals;
3498        self.local_exprs = saved_local_exprs;
3499        self.resumptions = saved_resumptions;
3500        Ok(result)
3501    }
3502
3503    fn handler_body_effect_context(
3504        &self,
3505        expr: &runtime::Expr,
3506        handler: CpsHandlerId,
3507    ) -> (Vec<typed_ir::Path>, CpsHandlerId) {
3508        let performed_effects = collect_expr_performed_effects(expr);
3509        let (mut expected_effects, mut condition_handler) = self.current_effect_context();
3510        if expected_effects.is_empty() && !performed_effects.is_empty() {
3511            expected_effects = performed_effects;
3512            condition_handler = handler;
3513        }
3514        (expected_effects, condition_handler)
3515    }
3516
3517    fn lower_handled_body(
3518        &mut self,
3519        body: &runtime::Expr,
3520        expected_effects: &[typed_ir::Path],
3521        handler: CpsHandlerId,
3522        value_cont: Option<CpsContinuationId>,
3523    ) -> CpsLowerResult<typed_ir::Path> {
3524        if let runtime::ExprKind::Var(path) = &body.kind
3525            && let Some(expr) = self.local_exprs.get(path).cloned()
3526        {
3527            let expr = inline_callable_expr(&expr);
3528            return self.lower_handled_body(&expr, expected_effects, handler, value_cont);
3529        }
3530
3531        if let runtime::ExprKind::LocalPushId { id, body } = &body.kind {
3532            let dest = self.fresh_value();
3533            self.current
3534                .stmts
3535                .push(CpsStmt::FreshGuard { dest, var: *id });
3536            let previous = self.effect_guards.insert(*id, dest);
3537            let result = self.lower_handled_body(body, expected_effects, handler, value_cont);
3538            restore_effect_guard(&mut self.effect_guards, *id, previous);
3539            return result;
3540        }
3541
3542        if let Some(expr) = handle_body_execution_inner(body) {
3543            return self.lower_handled_body(expr, expected_effects, handler, value_cont);
3544        }
3545
3546        if let runtime::ExprKind::BindHere { expr } = &body.kind
3547            && matches!(expr.kind, runtime::ExprKind::Block { .. })
3548        {
3549            return self.lower_handled_body(expr, expected_effects, handler, value_cont);
3550        }
3551
3552        if let Some(request) = effect_apply_body_request(body) {
3553            let (expected_effects, handler) =
3554                self.effect_context_for_request(&request, expected_effects, handler);
3555            let (effect, resumed_value) =
3556                self.begin_resume_after_perform(request, &expected_effects, handler)?;
3557            self.finish_resumed_handled_value(resumed_value, value_cont);
3558            return Ok(effect);
3559        }
3560
3561        if let runtime::ExprKind::BindHere { expr } = &body.kind {
3562            let value = self.lower_bind_here(expr)?;
3563            self.finish_handled_value(value, value_cont);
3564            return Ok(default_expected_effect(expected_effects));
3565        }
3566
3567        if let Some((resumption, arg)) = self.resume_apply(body) {
3568            let arg = self.lower_expr(arg)?;
3569            let dest = self.fresh_value();
3570            self.current.stmts.push(CpsStmt::ResumeWithHandler {
3571                dest,
3572                resumption,
3573                arg,
3574                handler,
3575                envs: Vec::new(),
3576            });
3577            for effect in expected_effects {
3578                if !self
3579                    .forced_handler_effects
3580                    .iter()
3581                    .any(|(seen_handler, seen_effect)| {
3582                        *seen_handler == handler && seen_effect == effect
3583                    })
3584                {
3585                    self.forced_handler_effects.push((handler, effect.clone()));
3586                }
3587            }
3588            self.finish_handled_value(dest, value_cont);
3589            return Ok(default_expected_effect(expected_effects));
3590        }
3591
3592        if let Some(reentry) = self.handler_reentry_apply(body, handler)? {
3593            self.current.stmts.push(CpsStmt::ResumeWithHandler {
3594                dest: reentry.dest,
3595                resumption: reentry.resumption,
3596                arg: reentry.arg,
3597                handler,
3598                envs: reentry.envs,
3599            });
3600            for effect in expected_effects {
3601                if !self
3602                    .forced_handler_effects
3603                    .iter()
3604                    .any(|(seen_handler, seen_effect)| {
3605                        *seen_handler == handler && seen_effect == effect
3606                    })
3607                {
3608                    self.forced_handler_effects.push((handler, effect.clone()));
3609                }
3610            }
3611            self.finish_handled_value(reentry.dest, value_cont);
3612            return Ok(default_expected_effect(expected_effects));
3613        }
3614
3615        if let Some((op, args)) = primitive_apply(body) {
3616            let expected = primitive_arity(op);
3617            if args.len() != expected {
3618                return Err(CpsLowerError::PrimitiveArityMismatch {
3619                    op,
3620                    expected,
3621                    actual: args.len(),
3622                });
3623            }
3624            let effect_arg = args
3625                .iter()
3626                .enumerate()
3627                .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)));
3628            let Some((effect_index, request)) = effect_arg else {
3629                let value = self.lower_expr(body)?;
3630                self.finish_handled_value(value, value_cont);
3631                return Ok(default_expected_effect(expected_effects));
3632            };
3633            let (expected_effects, handler) =
3634                self.effect_context_for_request(&request, expected_effects, handler);
3635            let (effect, resumed_value) =
3636                self.begin_resume_after_perform(request, &expected_effects, handler)?;
3637            let mut lowered_args = Vec::with_capacity(args.len());
3638            for (index, arg) in args.into_iter().enumerate() {
3639                if index == effect_index {
3640                    lowered_args.push(resumed_value);
3641                } else {
3642                    lowered_args.push(self.lower_expr(arg)?);
3643                }
3644            }
3645            let dest = self.fresh_value();
3646            self.current.stmts.push(CpsStmt::Primitive {
3647                dest,
3648                op,
3649                args: lowered_args,
3650            });
3651            self.finish_resumed_handled_value(dest, value_cont);
3652            return Ok(effect);
3653        }
3654
3655        if let Some((target_path, info, args)) = direct_apply_path(body, self.functions)? {
3656            let effect_arg = args
3657                .iter()
3658                .enumerate()
3659                .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)));
3660            let Some((effect_index, request)) = effect_arg else {
3661                let call_may_perform = self.target_may_perform_when_called(target_path);
3662                let needs_handler_wrapper_boundary =
3663                    self.direct_call_needs_handler_wrapper_boundary(target_path, &args, &body.ty);
3664                if call_may_perform || needs_handler_wrapper_boundary {
3665                    self.lower_handled_effectful_call_value(
3666                        info,
3667                        args,
3668                        &body.ty,
3669                        call_may_perform || needs_handler_wrapper_boundary,
3670                        value_cont,
3671                    )?;
3672                } else {
3673                    let value = self.lower_expr(body)?;
3674                    self.finish_handled_value(value, value_cont);
3675                }
3676                return Ok(default_expected_effect(expected_effects));
3677            };
3678            let (expected_effects, handler) =
3679                self.effect_context_for_request(&request, expected_effects, handler);
3680            let (effect, resumed_value) =
3681                self.begin_resume_after_perform(request, &expected_effects, handler)?;
3682            let mut lowered_args = Vec::with_capacity(args.len());
3683            for (index, arg) in args.into_iter().enumerate() {
3684                if index == effect_index {
3685                    lowered_args.push(resumed_value);
3686                } else {
3687                    lowered_args.push(self.lower_expr(arg)?);
3688                }
3689            }
3690            if fail_prefix_path(&info.path)
3691                && let [value] = lowered_args.as_slice()
3692            {
3693                self.finish_resumed_handled_value(*value, value_cont);
3694                return Ok(effect);
3695            }
3696            let dest = self.fresh_value();
3697            let should_force_direct = direct_call_result_needs_force(body, info);
3698            self.current.stmts.push(CpsStmt::DirectCall {
3699                dest,
3700                target: info.name.clone(),
3701                args: lowered_args,
3702            });
3703            let dest = if should_force_direct {
3704                let forced = self.fresh_value();
3705                self.current.stmts.push(CpsStmt::ForceThunk {
3706                    dest: forced,
3707                    thunk: dest,
3708                });
3709                forced
3710            } else {
3711                dest
3712            };
3713            self.finish_resumed_handled_value(dest, value_cont);
3714            return Ok(effect);
3715        }
3716
3717        if let runtime::ExprKind::If {
3718            cond,
3719            then_branch,
3720            else_branch,
3721            ..
3722        } = &body.kind
3723        {
3724            return self.lower_handled_if(
3725                cond,
3726                then_branch,
3727                else_branch,
3728                expected_effects,
3729                handler,
3730                value_cont,
3731            );
3732        }
3733
3734        if let Some((cond, then_branch, else_branch)) = bool_match(body) {
3735            return self.lower_handled_if(
3736                cond,
3737                then_branch,
3738                else_branch,
3739                expected_effects,
3740                handler,
3741                value_cont,
3742            );
3743        }
3744
3745        if let runtime::ExprKind::Match { .. } = &body.kind {
3746            return self.lower_handled_match(body, expected_effects, handler, value_cont);
3747        }
3748
3749        if let runtime::ExprKind::Block { stmts, tail } = &body.kind {
3750            return self.lower_handled_block(
3751                stmts,
3752                tail.as_deref(),
3753                expected_effects,
3754                handler,
3755                value_cont,
3756            );
3757        }
3758
3759        if body_is_thunk_value(body) {
3760            let thunk = self.lower_expr(body)?;
3761            let dest = self.fresh_value();
3762            self.current.stmts.push(CpsStmt::ForceThunk { dest, thunk });
3763            for effect in expected_effects {
3764                if !self
3765                    .forced_handler_effects
3766                    .iter()
3767                    .any(|(seen_handler, seen_effect)| {
3768                        *seen_handler == handler && seen_effect == effect
3769                    })
3770                {
3771                    self.forced_handler_effects.push((handler, effect.clone()));
3772                }
3773            }
3774            self.finish_handled_value(dest, value_cont);
3775            return Ok(default_expected_effect(expected_effects));
3776        }
3777
3778        let value = match self.lower_expr(body) {
3779            Ok(value) => value,
3780            Err(CpsLowerError::UnsupportedExpr { .. }) => {
3781                return Err(CpsLowerError::UnsupportedExpr {
3782                    kind: "handler body continuation",
3783                });
3784            }
3785            Err(error) => return Err(error),
3786        };
3787        self.finish_handled_value(value, value_cont);
3788        Ok(default_expected_effect(expected_effects))
3789    }
3790
3791    fn finish_handled_value(&mut self, value: CpsValueId, value_cont: Option<CpsContinuationId>) {
3792        match value_cont {
3793            Some(value_cont) => self.terminate(CpsTerminator::Continue {
3794                target: value_cont,
3795                args: vec![value],
3796            }),
3797            None => self.terminate(CpsTerminator::Return(value)),
3798        }
3799        self.finish_current();
3800    }
3801
3802    fn finish_resumed_handled_value(
3803        &mut self,
3804        value: CpsValueId,
3805        value_cont: Option<CpsContinuationId>,
3806    ) {
3807        if value_cont.is_some_and(|value_cont| self.handler_value_conts.contains(&value_cont)) {
3808            self.terminate(CpsTerminator::Return(value));
3809            self.finish_current();
3810            return;
3811        }
3812        self.finish_handled_value(value, value_cont);
3813    }
3814
3815    fn lower_handled_if(
3816        &mut self,
3817        cond: &runtime::Expr,
3818        then_branch: &runtime::Expr,
3819        else_branch: &runtime::Expr,
3820        expected_effects: &[typed_ir::Path],
3821        handler: CpsHandlerId,
3822        value_cont: Option<CpsContinuationId>,
3823    ) -> CpsLowerResult<typed_ir::Path> {
3824        if !collect_expr_performed_effects(cond).is_empty()
3825            || self.expr_may_perform_when_evaluated(cond)
3826        {
3827            return self.lower_handled_effect_condition_if(
3828                cond,
3829                then_branch,
3830                else_branch,
3831                expected_effects,
3832                handler,
3833                value_cont,
3834            );
3835        }
3836
3837        let cond = self.lower_expr(cond)?;
3838        let saved_locals = self.locals.clone();
3839        let saved_local_exprs = self.local_exprs.clone();
3840        let saved_resumptions = self.resumptions.clone();
3841        let then_cont = self.fresh_continuation();
3842        let else_cont = self.fresh_continuation();
3843
3844        self.terminate(CpsTerminator::Branch {
3845            cond,
3846            then_cont,
3847            else_cont,
3848        });
3849        self.finish_current();
3850
3851        self.current = ContinuationBuilder::new(then_cont, Vec::new());
3852        self.locals = saved_locals.clone();
3853        self.local_exprs = saved_local_exprs.clone();
3854        self.resumptions = saved_resumptions.clone();
3855        let then_effect =
3856            self.lower_handled_body(then_branch, expected_effects, handler, value_cont)?;
3857
3858        self.current = ContinuationBuilder::new(else_cont, Vec::new());
3859        self.locals = saved_locals.clone();
3860        self.local_exprs = saved_local_exprs.clone();
3861        self.resumptions = saved_resumptions.clone();
3862        let else_effect =
3863            self.lower_handled_body(else_branch, expected_effects, handler, value_cont)?;
3864        if !handled_effects_compatible(expected_effects, &then_effect, &else_effect) {
3865            return Err(CpsLowerError::UnsupportedExpr {
3866                kind: "handler effect mismatch",
3867            });
3868        }
3869
3870        self.locals = saved_locals;
3871        self.local_exprs = saved_local_exprs;
3872        self.resumptions = saved_resumptions;
3873        Ok(then_effect)
3874    }
3875
3876    fn lower_handled_effect_condition_if(
3877        &mut self,
3878        cond: &runtime::Expr,
3879        then_branch: &runtime::Expr,
3880        else_branch: &runtime::Expr,
3881        expected_effects: &[typed_ir::Path],
3882        handler: CpsHandlerId,
3883        value_cont: Option<CpsContinuationId>,
3884    ) -> CpsLowerResult<typed_ir::Path> {
3885        let saved_locals = self.locals.clone();
3886        let saved_local_exprs = self.local_exprs.clone();
3887        let saved_resumptions = self.resumptions.clone();
3888        let cond_cont = self.fresh_continuation();
3889        let then_cont = self.fresh_continuation();
3890        let else_cont = self.fresh_continuation();
3891        let cond_value = self.fresh_value();
3892
3893        let cond_effect = self.lower_effectful_condition_to_continuation(
3894            cond,
3895            expected_effects,
3896            handler,
3897            cond_cont,
3898        )?;
3899
3900        self.current = ContinuationBuilder::new(cond_cont, vec![cond_value]);
3901        self.locals = saved_locals.clone();
3902        self.local_exprs = saved_local_exprs.clone();
3903        self.resumptions = saved_resumptions.clone();
3904        self.terminate(CpsTerminator::Branch {
3905            cond: cond_value,
3906            then_cont,
3907            else_cont,
3908        });
3909        self.finish_current();
3910
3911        self.current = ContinuationBuilder::new(then_cont, Vec::new());
3912        self.locals = saved_locals.clone();
3913        self.local_exprs = saved_local_exprs.clone();
3914        self.resumptions = saved_resumptions.clone();
3915        let then_effect =
3916            self.lower_handled_body(then_branch, expected_effects, handler, value_cont)?;
3917
3918        self.current = ContinuationBuilder::new(else_cont, Vec::new());
3919        self.locals = saved_locals.clone();
3920        self.local_exprs = saved_local_exprs.clone();
3921        self.resumptions = saved_resumptions.clone();
3922        let else_effect =
3923            self.lower_handled_body(else_branch, expected_effects, handler, value_cont)?;
3924        if !handled_effects_compatible(expected_effects, &cond_effect, &then_effect)
3925            || !handled_effects_compatible(expected_effects, &cond_effect, &else_effect)
3926            || !handled_effects_compatible(expected_effects, &then_effect, &else_effect)
3927        {
3928            return Err(CpsLowerError::UnsupportedExpr {
3929                kind: "handler effect mismatch",
3930            });
3931        }
3932
3933        self.locals = saved_locals;
3934        self.local_exprs = saved_local_exprs;
3935        self.resumptions = saved_resumptions;
3936        Ok(cond_effect)
3937    }
3938
3939    fn lower_handled_match(
3940        &mut self,
3941        expr: &runtime::Expr,
3942        expected_effects: &[typed_ir::Path],
3943        handler: CpsHandlerId,
3944        value_cont: Option<CpsContinuationId>,
3945    ) -> CpsLowerResult<typed_ir::Path> {
3946        let runtime::ExprKind::Match {
3947            scrutinee, arms, ..
3948        } = &expr.kind
3949        else {
3950            return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
3951        };
3952        let scrutinee = self.lower_expr(scrutinee)?;
3953        let saved_locals = self.locals.clone();
3954        let saved_local_exprs = self.local_exprs.clone();
3955        let saved_resumptions = self.resumptions.clone();
3956        let fallback_cont = self.fresh_continuation();
3957        let mut arm_conts = Vec::with_capacity(arms.len());
3958        let mut guard_conts = Vec::with_capacity(arms.len());
3959        let mut next_conts = Vec::with_capacity(arms.len());
3960        for _ in arms {
3961            arm_conts.push(self.fresh_continuation());
3962            guard_conts.push(None);
3963        }
3964
3965        let mut current_test_cont = None;
3966        for (index, arm) in arms.iter().enumerate() {
3967            if let Some(test_cont) = current_test_cont {
3968                self.current = ContinuationBuilder::new(test_cont, Vec::new());
3969                self.locals = saved_locals.clone();
3970                self.local_exprs = saved_local_exprs.clone();
3971                self.resumptions = saved_resumptions.clone();
3972            }
3973            let next_cont = if index + 1 == arms.len() {
3974                fallback_cont
3975            } else {
3976                let next = self.fresh_continuation();
3977                current_test_cont = Some(next);
3978                next
3979            };
3980            next_conts.push(next_cont);
3981            let success_cont = if arm.guard.is_some() {
3982                let guard_cont = self.fresh_continuation();
3983                guard_conts[index] = Some(guard_cont);
3984                guard_cont
3985            } else {
3986                arm_conts[index]
3987            };
3988            if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
3989                self.finish_current();
3990            }
3991        }
3992
3993        self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
3994        let unit = self.fresh_value();
3995        self.current.stmts.push(CpsStmt::Literal {
3996            dest: unit,
3997            literal: CpsLiteral::Unit,
3998        });
3999        self.finish_handled_value(unit, value_cont);
4000
4001        let mut joined_effect: Option<typed_ir::Path> = None;
4002        for (index, arm) in arms.iter().enumerate() {
4003            let Some(guard_cont) = guard_conts[index] else {
4004                continue;
4005            };
4006            let Some(guard) = &arm.guard else {
4007                continue;
4008            };
4009            self.current = ContinuationBuilder::new(guard_cont, Vec::new());
4010            self.locals = saved_locals.clone();
4011            self.local_exprs = saved_local_exprs.clone();
4012            self.resumptions = saved_resumptions.clone();
4013            self.bind_pattern(&arm.pattern, scrutinee)?;
4014            if !collect_expr_performed_effects(guard).is_empty() {
4015                let guard_value_cont = self.fresh_continuation();
4016                let guard_value = self.fresh_value();
4017                let guard_effect = self.lower_handled_body(
4018                    guard,
4019                    expected_effects,
4020                    handler,
4021                    Some(guard_value_cont),
4022                )?;
4023                join_handled_effect(&mut joined_effect, expected_effects, guard_effect)?;
4024                self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
4025                self.locals = saved_locals.clone();
4026                self.local_exprs = saved_local_exprs.clone();
4027                self.resumptions = saved_resumptions.clone();
4028                self.terminate(CpsTerminator::Branch {
4029                    cond: guard_value,
4030                    then_cont: arm_conts[index],
4031                    else_cont: next_conts[index],
4032                });
4033                self.finish_current();
4034            } else {
4035                let guard_value = self.lower_expr(guard)?;
4036                self.terminate(CpsTerminator::Branch {
4037                    cond: guard_value,
4038                    then_cont: arm_conts[index],
4039                    else_cont: next_conts[index],
4040                });
4041                self.finish_current();
4042            }
4043        }
4044
4045        for (arm, arm_cont) in arms.iter().zip(arm_conts) {
4046            self.current = ContinuationBuilder::new(arm_cont, Vec::new());
4047            self.locals = saved_locals.clone();
4048            self.local_exprs = saved_local_exprs.clone();
4049            self.resumptions = saved_resumptions.clone();
4050            self.bind_pattern(&arm.pattern, scrutinee)?;
4051            let effect =
4052                self.lower_handled_body(&arm.body, expected_effects, handler, value_cont)?;
4053            join_handled_effect(&mut joined_effect, expected_effects, effect)?;
4054        }
4055
4056        self.locals = saved_locals;
4057        self.local_exprs = saved_local_exprs;
4058        self.resumptions = saved_resumptions;
4059        Ok(joined_effect.unwrap_or_else(|| default_expected_effect(expected_effects)))
4060    }
4061
4062    fn lower_handled_block(
4063        &mut self,
4064        stmts: &[runtime::Stmt],
4065        tail: Option<&runtime::Expr>,
4066        expected_effects: &[typed_ir::Path],
4067        handler: CpsHandlerId,
4068        value_cont: Option<CpsContinuationId>,
4069    ) -> CpsLowerResult<typed_ir::Path> {
4070        for (index, stmt) in stmts.iter().enumerate() {
4071            match stmt {
4072                runtime::Stmt::Let { pattern, value } => {
4073                    if unused_pure_let(
4074                        pattern,
4075                        value,
4076                        &stmts[index + 1..],
4077                        tail,
4078                        self.functions,
4079                        self.bindings,
4080                    ) {
4081                        continue;
4082                    }
4083                    if let Some((name, param, body)) = recursive_lambda_let(pattern, value) {
4084                        let value = self.lower_recursive_lambda(name, param, body)?;
4085                        self.locals
4086                            .insert(typed_ir::Path::from_name(name.clone()), value);
4087                        continue;
4088                    }
4089                    if let Some(request) = effect_apply_nested(value) {
4090                        let (routed_effects, routed_handler) =
4091                            self.effect_context_for_request(&request, expected_effects, handler);
4092                        let (effect, resumed_value) = self.begin_resume_after_perform(
4093                            request,
4094                            &routed_effects,
4095                            routed_handler,
4096                        )?;
4097                        self.bind_pattern(pattern, resumed_value)?;
4098                        let rest_effect = self.lower_handled_block(
4099                            &stmts[index + 1..],
4100                            tail,
4101                            expected_effects,
4102                            handler,
4103                            None,
4104                        )?;
4105                        if !handled_effects_compatible(expected_effects, &rest_effect, &effect) {
4106                            return Err(CpsLowerError::UnsupportedExpr {
4107                                kind: "handler effect mismatch",
4108                            });
4109                        }
4110                        return Ok(effect);
4111                    }
4112
4113                    // Effectful direct call inside a handler scope: split the
4114                    // post-call computation off into its own continuation so
4115                    // the callee's Perform (or nested EffectfulCall) can
4116                    // capture this function's "rest of the block" as a
4117                    // return frame in the resumption.
4118                    if let Some((target_path, info, args)) =
4119                        direct_apply_path(value, self.functions)?
4120                    {
4121                        let target = info.name.clone();
4122                        let call_may_perform = self.target_may_perform_when_called(target_path);
4123                        let needs_handler_wrapper_boundary = self
4124                            .direct_call_needs_handler_wrapper_boundary(
4125                                target_path,
4126                                &args,
4127                                &value.ty,
4128                            );
4129                        if call_may_perform
4130                            || needs_handler_wrapper_boundary
4131                            || (self.active_handler.is_some()
4132                                && matches!(info.ret, runtime::Type::Thunk { .. }))
4133                        {
4134                            return self.lower_handled_effectful_call_let(
4135                                pattern,
4136                                target,
4137                                info,
4138                                args,
4139                                &value.ty,
4140                                call_may_perform || needs_handler_wrapper_boundary,
4141                                &stmts[index + 1..],
4142                                tail,
4143                                expected_effects,
4144                                handler,
4145                                value_cont,
4146                            );
4147                        }
4148                    }
4149                    // Closure application: split into EffectfulApply when the
4150                    // callee's type indicates a potential effect (write13).
4151                    // No active_handler gate: helpers like `call_callback` are
4152                    // separate functions without a local handler scope, yet
4153                    // their `f 0` callback must still capture the caller rest.
4154                    if let runtime::ExprKind::Apply { callee, arg, .. } = &value.kind
4155                        && callee_type_may_perform(&callee.ty)
4156                    {
4157                        return self.lower_handled_effectful_apply_let(
4158                            pattern,
4159                            callee,
4160                            arg,
4161                            &value.ty,
4162                            &stmts[index + 1..],
4163                            tail,
4164                            expected_effects,
4165                            handler,
4166                            value_cont,
4167                        );
4168                    }
4169
4170                    let value = self.lower_expr(value)?;
4171                    self.bind_pattern(pattern, value)?;
4172                }
4173                runtime::Stmt::Expr(expr) => {
4174                    if !stmts[index + 1..].is_empty() || tail.is_some() {
4175                        if let runtime::ExprKind::BindHere { expr: inner } = &expr.kind {
4176                            let thunk =
4177                                self.with_sync_direct_call_for_ignored_force_depth(|this| {
4178                                    this.with_sync_apply_for_immediate_force_depth(|this| {
4179                                        this.lower_expr(inner)
4180                                    })
4181                                })?;
4182                            let post_cont = self.fresh_continuation();
4183                            let ignored = self.fresh_value();
4184                            self.terminate(CpsTerminator::EffectfulForce {
4185                                thunk,
4186                                resume: post_cont,
4187                            });
4188                            self.finish_current();
4189                            self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4190                            return self.lower_handled_block(
4191                                &stmts[index + 1..],
4192                                tail,
4193                                expected_effects,
4194                                handler,
4195                                value_cont,
4196                            );
4197                        }
4198                    }
4199
4200                    if let Some(request) = effect_apply_nested(expr) {
4201                        let (routed_effects, routed_handler) =
4202                            self.effect_context_for_request(&request, expected_effects, handler);
4203                        let (effect, resumed_value) = self.begin_resume_after_perform(
4204                            request,
4205                            &routed_effects,
4206                            routed_handler,
4207                        )?;
4208                        if stmts[index + 1..].is_empty() && tail.is_none() {
4209                            self.finish_handled_value(resumed_value, value_cont);
4210                            return Ok(effect);
4211                        }
4212                        let rest_effect = self.lower_handled_block(
4213                            &stmts[index + 1..],
4214                            tail,
4215                            expected_effects,
4216                            handler,
4217                            None,
4218                        )?;
4219                        if !handled_effects_compatible(expected_effects, &rest_effect, &effect) {
4220                            return Err(CpsLowerError::UnsupportedExpr {
4221                                kind: "handler effect mismatch",
4222                            });
4223                        }
4224                        return Ok(effect);
4225                    }
4226
4227                    let is_direct_call = direct_apply(expr, self.functions)?.is_some();
4228                    let mut value = self.lower_expr(expr)?;
4229                    // A non-tail block expression that calls another function
4230                    // (or has a Thunk static type) may be returning a Thunk
4231                    // that wraps effectful work. The runtime FunctionInfo
4232                    // sometimes hides the Thunk return type behind monomorphized
4233                    // role-impl wrappers, so force unconditionally for direct
4234                    // calls; ForceThunk is a no-op on plain values.
4235                    if is_direct_call || matches!(expr.ty, runtime::Type::Thunk { .. }) {
4236                        let dest = self.fresh_value();
4237                        self.current
4238                            .stmts
4239                            .push(CpsStmt::ForceThunk { dest, thunk: value });
4240                        value = dest;
4241                    }
4242                    if stmts[index + 1..].is_empty() && tail.is_none() {
4243                        self.finish_handled_value(value, value_cont);
4244                        return Ok(default_expected_effect(expected_effects));
4245                    }
4246                }
4247                runtime::Stmt::Module { .. } => {
4248                    return Err(CpsLowerError::UnsupportedExpr {
4249                        kind: "module statement",
4250                    });
4251                }
4252            }
4253        }
4254
4255        match tail {
4256            Some(tail) => self.lower_handled_body(tail, expected_effects, handler, value_cont),
4257            None => Err(CpsLowerError::UnsupportedExpr {
4258                kind: "handler body continuation",
4259            }),
4260        }
4261    }
4262
4263    /// Lower a `let pattern = direct_call(...)` inside a handler scope as
4264    /// an `EffectfulCall` terminator. The current continuation terminates
4265    /// with the call; the post-call binding/rest is moved to a fresh
4266    /// continuation that receives the call's return value.
4267    fn lower_handled_effectful_call_value(
4268        &mut self,
4269        info: &FunctionInfo,
4270        args: Vec<&runtime::Expr>,
4271        call_ty: &runtime::Type,
4272        call_may_perform: bool,
4273        value_cont: Option<CpsContinuationId>,
4274    ) -> CpsLowerResult<()> {
4275        let info_params = info.params.clone();
4276        let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4277        let lowered_args = args
4278            .into_iter()
4279            .enumerate()
4280            .map(|(idx, arg)| -> CpsLowerResult<CpsValueId> {
4281                let expected = info_params
4282                    .get(idx)
4283                    .cloned()
4284                    .unwrap_or_else(runtime::Type::unknown);
4285                let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
4286                    self.lower_expr_as_thunk_value(arg)?
4287                } else {
4288                    self.lower_expr(arg)?
4289                };
4290                Ok(self.force_if_non_thunk_demand(lowered, &expected))
4291            })
4292            .collect::<CpsLowerResult<Vec<_>>>()?;
4293        if fail_prefix_path(&info.path)
4294            && let [value] = lowered_args.as_slice()
4295        {
4296            self.finish_handled_value(*value, value_cont);
4297            return Ok(());
4298        }
4299        let post_cont = self.fresh_continuation();
4300        let result_id = self.fresh_value();
4301        self.terminate(CpsTerminator::EffectfulCall {
4302            target: info.name.clone(),
4303            args: lowered_args,
4304            resume: post_cont,
4305        });
4306        self.finish_current();
4307        if info_returns_thunk || call_may_perform {
4308            self.mark_active_handlers_external_call();
4309        }
4310
4311        self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4312        let value = if matches!(call_ty, runtime::Type::Thunk { .. }) && !call_may_perform {
4313            result_id
4314        } else {
4315            // A handler body executes the computation it handles. Direct
4316            // helpers such as lazy `or` return an effectful thunk from the
4317            // call itself, so the value path must force that thunk before the
4318            // handled value reaches a branch or value arm.
4319            let force_cont = self.fresh_continuation();
4320            let forced = self.fresh_value();
4321            self.terminate(CpsTerminator::EffectfulForce {
4322                thunk: result_id,
4323                resume: force_cont,
4324            });
4325            self.finish_current();
4326            self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4327            forced
4328        };
4329        self.finish_handled_value(value, value_cont);
4330        Ok(())
4331    }
4332
4333    #[allow(clippy::too_many_arguments)]
4334    fn lower_handled_effectful_call_let(
4335        &mut self,
4336        pattern: &runtime::Pattern,
4337        target: String,
4338        info: &FunctionInfo,
4339        args: Vec<&runtime::Expr>,
4340        call_ty: &runtime::Type,
4341        call_may_perform: bool,
4342        rest: &[runtime::Stmt],
4343        tail: Option<&runtime::Expr>,
4344        expected_effects: &[typed_ir::Path],
4345        handler: CpsHandlerId,
4346        value_cont: Option<CpsContinuationId>,
4347    ) -> CpsLowerResult<typed_ir::Path> {
4348        let info_params = info.params.clone();
4349        let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4350        let lowered_args = args
4351            .into_iter()
4352            .enumerate()
4353            .map(|(idx, arg)| -> CpsLowerResult<CpsValueId> {
4354                let expected = info_params
4355                    .get(idx)
4356                    .cloned()
4357                    .unwrap_or_else(runtime::Type::unknown);
4358                let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
4359                    self.lower_expr_as_thunk_value(arg)?
4360                } else {
4361                    self.lower_expr(arg)?
4362                };
4363                // The runtime IR may surface an argument as a Thunk-wrapped
4364                // value even when the callee's formal param type is plain
4365                // (e.g. `f: int -> int -> [e] int` produces a Thunk-wrapped
4366                // closure at the call site). Force here so the callee
4367                // receives the plain value its params expect.
4368                Ok(self.force_if_non_thunk_demand(lowered, &expected))
4369            })
4370            .collect::<CpsLowerResult<Vec<_>>>()?;
4371        if fail_prefix_path(&info.path)
4372            && let [value] = lowered_args.as_slice()
4373        {
4374            self.bind_pattern(pattern, *value)?;
4375            return self.lower_handled_block(rest, tail, expected_effects, handler, value_cont);
4376        }
4377
4378        let post_cont = self.fresh_continuation();
4379        let result_id = self.fresh_value();
4380
4381        self.terminate(CpsTerminator::EffectfulCall {
4382            target,
4383            args: lowered_args,
4384            resume: post_cont,
4385        });
4386        self.finish_current();
4387        if info_returns_thunk || call_may_perform {
4388            self.mark_active_handlers_external_call();
4389        }
4390
4391        self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4392        // Inside the handler scope, the call result may be a Thunk wrapping
4393        // effectful work (helpers lower as MakeThunk + Return). Force it via
4394        // an EffectfulForce terminator so the Perform inside the thunk's
4395        // body sees the post-force cont as a return frame, not via a
4396        // synchronous stmt ForceThunk that would lose the caller context.
4397        let bound = if matches!(call_ty, runtime::Type::Thunk { .. }) {
4398            result_id
4399        } else {
4400            let force_cont = self.fresh_continuation();
4401            let forced = self.fresh_value();
4402            self.terminate(CpsTerminator::EffectfulForce {
4403                thunk: result_id,
4404                resume: force_cont,
4405            });
4406            self.finish_current();
4407            self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4408            forced
4409        };
4410        self.bind_pattern(pattern, bound)?;
4411
4412        let rest_effect =
4413            self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)?;
4414        Ok(rest_effect)
4415    }
4416
4417    /// Lower a `let pattern = closure_apply(...)` inside a handler scope as
4418    /// an `EffectfulApply` terminator. Conservatively treats all closure
4419    /// applications in handler scope as potentially effectful (write13).
4420    #[allow(clippy::too_many_arguments)]
4421    fn lower_handled_effectful_apply_let(
4422        &mut self,
4423        pattern: &runtime::Pattern,
4424        callee: &runtime::Expr,
4425        arg: &runtime::Expr,
4426        apply_ty: &runtime::Type,
4427        rest: &[runtime::Stmt],
4428        tail: Option<&runtime::Expr>,
4429        expected_effects: &[typed_ir::Path],
4430        handler: CpsHandlerId,
4431        value_cont: Option<CpsContinuationId>,
4432    ) -> CpsLowerResult<typed_ir::Path> {
4433        let closure = self.lower_expr(callee)?;
4434        let arg_value = self.lower_expr_as_call_arg(&callee.ty, arg)?;
4435
4436        let post_cont = self.fresh_continuation();
4437        let result_id = self.fresh_value();
4438
4439        self.terminate(CpsTerminator::EffectfulApply {
4440            closure,
4441            arg: arg_value,
4442            resume: post_cont,
4443        });
4444        self.finish_current();
4445
4446        self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4447        let bound = if matches!(apply_ty, runtime::Type::Thunk { .. }) {
4448            result_id
4449        } else {
4450            let force_cont = self.fresh_continuation();
4451            let forced = self.fresh_value();
4452            self.terminate(CpsTerminator::EffectfulForce {
4453                thunk: result_id,
4454                resume: force_cont,
4455            });
4456            self.finish_current();
4457            self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4458            forced
4459        };
4460        self.bind_pattern(pattern, bound)?;
4461
4462        self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4463    }
4464
4465    /// Expr-statement variant of `lower_handled_effectful_call_let`. The
4466    /// post-call continuation discards the result but still routes a Perform
4467    /// inside the callee through the saved return frame chain.
4468    ///
4469    /// Currently unused — the Stmt::Expr path triggers test regressions in
4470    /// the Cranelift backend (todo!() for EffectfulCall). Kept for write14's
4471    /// follow-up when cps_repr / Cranelift gain support for these terminators.
4472    #[allow(dead_code, clippy::too_many_arguments)]
4473    fn lower_handled_effectful_call_expr(
4474        &mut self,
4475        target: String,
4476        info: &FunctionInfo,
4477        args: Vec<&runtime::Expr>,
4478        call_ty: &runtime::Type,
4479        rest: &[runtime::Stmt],
4480        tail: Option<&runtime::Expr>,
4481        expected_effects: &[typed_ir::Path],
4482        handler: CpsHandlerId,
4483        value_cont: Option<CpsContinuationId>,
4484    ) -> CpsLowerResult<typed_ir::Path> {
4485        let info_params = info.params.clone();
4486        let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4487        let lowered_args = args
4488            .into_iter()
4489            .enumerate()
4490            .map(|(idx, arg)| {
4491                if matches!(info_params.get(idx), Some(runtime::Type::Thunk { .. })) {
4492                    self.lower_expr_as_thunk_value(arg)
4493                } else {
4494                    self.lower_expr(arg)
4495                }
4496            })
4497            .collect::<CpsLowerResult<Vec<_>>>()?;
4498
4499        let post_cont = self.fresh_continuation();
4500        let ignored = self.fresh_value();
4501
4502        self.terminate(CpsTerminator::EffectfulCall {
4503            target,
4504            args: lowered_args,
4505            resume: post_cont,
4506        });
4507        self.finish_current();
4508        if info_returns_thunk {
4509            self.mark_active_handlers_external_call();
4510        }
4511
4512        self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4513        // Even though we discard the value, a Thunk return from an effectful
4514        // helper still needs to be unwrapped here so its body's Perform sees
4515        // a return frame (not lost via a sync ForceThunk).
4516        if !matches!(call_ty, runtime::Type::Thunk { .. }) {
4517            let force_cont = self.fresh_continuation();
4518            let forced = self.fresh_value();
4519            self.terminate(CpsTerminator::EffectfulForce {
4520                thunk: ignored,
4521                resume: force_cont,
4522            });
4523            self.finish_current();
4524            self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4525        }
4526
4527        self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4528    }
4529
4530    #[allow(dead_code, clippy::too_many_arguments)]
4531    fn lower_handled_effectful_apply_expr(
4532        &mut self,
4533        callee: &runtime::Expr,
4534        arg: &runtime::Expr,
4535        apply_ty: &runtime::Type,
4536        rest: &[runtime::Stmt],
4537        tail: Option<&runtime::Expr>,
4538        expected_effects: &[typed_ir::Path],
4539        handler: CpsHandlerId,
4540        value_cont: Option<CpsContinuationId>,
4541    ) -> CpsLowerResult<typed_ir::Path> {
4542        let closure = self.lower_expr(callee)?;
4543        let arg_value = self.lower_expr_as_call_arg(&callee.ty, arg)?;
4544
4545        let post_cont = self.fresh_continuation();
4546        let ignored = self.fresh_value();
4547
4548        self.terminate(CpsTerminator::EffectfulApply {
4549            closure,
4550            arg: arg_value,
4551            resume: post_cont,
4552        });
4553        self.finish_current();
4554
4555        self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4556        if !matches!(apply_ty, runtime::Type::Thunk { .. }) {
4557            let force_cont = self.fresh_continuation();
4558            let forced = self.fresh_value();
4559            self.terminate(CpsTerminator::EffectfulForce {
4560                thunk: ignored,
4561                resume: force_cont,
4562            });
4563            self.finish_current();
4564            self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4565        }
4566
4567        self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4568    }
4569
4570    fn begin_resume_after_perform(
4571        &mut self,
4572        request: CpsEffectApply<'_>,
4573        expected_effects: &[typed_ir::Path],
4574        handler: CpsHandlerId,
4575    ) -> CpsLowerResult<(typed_ir::Path, CpsValueId)> {
4576        let effect = request.effect.clone();
4577        let payload = request.payload;
4578        let blocked = request.blocked;
4579        if let Some(payload) = handle_body_execution_inner(payload) {
4580            return self.begin_resume_after_perform(
4581                CpsEffectApply {
4582                    effect,
4583                    payload,
4584                    blocked,
4585                },
4586                expected_effects,
4587                handler,
4588            );
4589        }
4590
4591        if let Some((op, args)) = primitive_apply(payload) {
4592            let expected = primitive_arity(op);
4593            if args.len() != expected {
4594                return Err(CpsLowerError::PrimitiveArityMismatch {
4595                    op,
4596                    expected,
4597                    actual: args.len(),
4598                });
4599            }
4600            if let Some((effect_index, inner_request)) = args
4601                .iter()
4602                .enumerate()
4603                .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)))
4604            {
4605                let (_, resumed_value) =
4606                    self.begin_resume_after_perform(inner_request, expected_effects, handler)?;
4607                let mut lowered_args = Vec::with_capacity(args.len());
4608                for (index, arg) in args.into_iter().enumerate() {
4609                    if index == effect_index {
4610                        lowered_args.push(resumed_value);
4611                    } else {
4612                        lowered_args.push(self.lower_expr(arg)?);
4613                    }
4614                }
4615                let payload = self.fresh_value();
4616                self.current.stmts.push(CpsStmt::Primitive {
4617                    dest: payload,
4618                    op,
4619                    args: lowered_args,
4620                });
4621                return self.begin_resume_after_perform_value(
4622                    effect,
4623                    payload,
4624                    blocked,
4625                    expected_effects,
4626                    handler,
4627                );
4628            }
4629        }
4630
4631        if let Some((target, info, args)) = direct_apply(payload, self.functions)? {
4632            if let Some((effect_index, inner_request)) = args
4633                .iter()
4634                .enumerate()
4635                .filter(|(index, _)| {
4636                    !matches!(info.params.get(*index), Some(runtime::Type::Thunk { .. }))
4637                })
4638                .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)))
4639            {
4640                let (_, resumed_value) =
4641                    self.begin_resume_after_perform(inner_request, expected_effects, handler)?;
4642                let mut lowered_args = Vec::with_capacity(args.len());
4643                for (index, arg) in args.into_iter().enumerate() {
4644                    if index == effect_index {
4645                        lowered_args.push(resumed_value);
4646                    } else {
4647                        lowered_args.push(self.lower_expr(arg)?);
4648                    }
4649                }
4650                let payload_expr = payload;
4651                let payload = self.fresh_value();
4652                self.current.stmts.push(CpsStmt::DirectCall {
4653                    dest: payload,
4654                    target,
4655                    args: lowered_args,
4656                });
4657                let payload = if direct_call_result_needs_force(payload_expr, info) {
4658                    let forced = self.fresh_value();
4659                    self.current.stmts.push(CpsStmt::ForceThunk {
4660                        dest: forced,
4661                        thunk: payload,
4662                    });
4663                    forced
4664                } else {
4665                    payload
4666                };
4667                return self.begin_resume_after_perform_value(
4668                    effect,
4669                    payload,
4670                    blocked,
4671                    expected_effects,
4672                    handler,
4673                );
4674            }
4675        }
4676
4677        let payload = self.lower_expr(payload)?;
4678        self.begin_resume_after_perform_value(effect, payload, blocked, expected_effects, handler)
4679    }
4680
4681    fn begin_resume_after_perform_value(
4682        &mut self,
4683        effect: typed_ir::Path,
4684        payload: CpsValueId,
4685        blocked: Option<runtime::EffectIdRef>,
4686        _expected_effects: &[typed_ir::Path],
4687        handler: CpsHandlerId,
4688    ) -> CpsLowerResult<(typed_ir::Path, CpsValueId)> {
4689        let blocked = blocked
4690            .map(|blocked| self.lower_effect_id_ref(blocked))
4691            .transpose()?;
4692        let resume_cont = self.fresh_continuation();
4693        self.terminate(CpsTerminator::Perform {
4694            effect: effect.clone(),
4695            payload,
4696            resume: resume_cont,
4697            handler,
4698            blocked,
4699        });
4700        self.finish_current();
4701
4702        let resumed_value = self.fresh_value();
4703        self.current = ContinuationBuilder::new(resume_cont, vec![resumed_value]);
4704        Ok((effect, resumed_value))
4705    }
4706
4707    fn bind_pattern(
4708        &mut self,
4709        pattern: &runtime::Pattern,
4710        value: CpsValueId,
4711    ) -> CpsLowerResult<()> {
4712        match pattern {
4713            runtime::Pattern::Wildcard { .. } => Ok(()),
4714            runtime::Pattern::Bind { name, .. } => {
4715                let path = typed_ir::Path::from_name(name.clone());
4716                self.local_exprs.remove(&path);
4717                self.locals.insert(path, value);
4718                Ok(())
4719            }
4720            runtime::Pattern::Lit { .. } => Ok(()),
4721            runtime::Pattern::Tuple { items, .. } => {
4722                for (index, item) in items.iter().enumerate() {
4723                    let item_value = self.fresh_value();
4724                    self.current.stmts.push(CpsStmt::TupleGet {
4725                        dest: item_value,
4726                        tuple: value,
4727                        index,
4728                    });
4729                    self.bind_pattern(item, item_value)?;
4730                }
4731                Ok(())
4732            }
4733            runtime::Pattern::List {
4734                prefix,
4735                spread,
4736                suffix,
4737                ..
4738            } => self.bind_list_pattern(prefix, spread.as_deref(), suffix, value),
4739            runtime::Pattern::Record { fields, spread, .. } => {
4740                self.bind_record_pattern(fields, spread.as_ref(), value)
4741            }
4742            runtime::Pattern::Variant {
4743                value: Some(payload),
4744                ..
4745            } => {
4746                let payload_value = self.fresh_value();
4747                self.current.stmts.push(CpsStmt::VariantPayload {
4748                    dest: payload_value,
4749                    variant: value,
4750                });
4751                self.bind_pattern(payload, payload_value)
4752            }
4753            runtime::Pattern::Variant { value: None, .. } => Ok(()),
4754            runtime::Pattern::Or { .. } => Err(CpsLowerError::UnsupportedPattern { kind: "or" }),
4755            runtime::Pattern::As { pattern, name, .. } => {
4756                self.bind_pattern(pattern, value)?;
4757                self.locals
4758                    .insert(typed_ir::Path::from_name(name.clone()), value);
4759                Ok(())
4760            }
4761        }
4762    }
4763
4764    fn bind_list_pattern(
4765        &mut self,
4766        prefix: &[runtime::Pattern],
4767        spread: Option<&runtime::Pattern>,
4768        suffix: &[runtime::Pattern],
4769        value: CpsValueId,
4770    ) -> CpsLowerResult<()> {
4771        let len = if spread.is_some() || !suffix.is_empty() {
4772            Some(self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]))
4773        } else {
4774            None
4775        };
4776        for (index, item) in prefix.iter().enumerate() {
4777            let index = self.emit_int_literal(index as i64);
4778            let item_value =
4779                self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
4780            self.bind_pattern(item, item_value)?;
4781        }
4782        if let Some(spread) = spread {
4783            let start = self.emit_int_literal(prefix.len() as i64);
4784            let suffix_len = self.emit_int_literal(suffix.len() as i64);
4785            let end = self.emit_primitive(
4786                typed_ir::PrimitiveOp::IntSub,
4787                vec![len.expect("list spread requires len"), suffix_len],
4788            );
4789            let slice = self.emit_primitive(
4790                typed_ir::PrimitiveOp::ListIndexRangeRaw,
4791                vec![value, start, end],
4792            );
4793            self.bind_pattern(spread, slice)?;
4794        }
4795        for (offset, item) in suffix.iter().enumerate() {
4796            let suffix_len = self.emit_int_literal(suffix.len() as i64);
4797            let suffix_start = self.emit_primitive(
4798                typed_ir::PrimitiveOp::IntSub,
4799                vec![len.expect("list suffix requires len"), suffix_len],
4800            );
4801            let offset = self.emit_int_literal(offset as i64);
4802            let index =
4803                self.emit_primitive(typed_ir::PrimitiveOp::IntAdd, vec![suffix_start, offset]);
4804            let item_value =
4805                self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
4806            self.bind_pattern(item, item_value)?;
4807        }
4808        Ok(())
4809    }
4810
4811    fn bind_record_pattern(
4812        &mut self,
4813        fields: &[runtime::RecordPatternField],
4814        spread: Option<&runtime::RecordSpreadPattern>,
4815        value: CpsValueId,
4816    ) -> CpsLowerResult<()> {
4817        for field in fields {
4818            let field_value = self.fresh_value();
4819            if let Some(default) = &field.default {
4820                let default = self.lower_expr(default)?;
4821                self.current.stmts.push(CpsStmt::SelectWithDefault {
4822                    dest: field_value,
4823                    base: value,
4824                    field: field.name.clone(),
4825                    default,
4826                });
4827            } else {
4828                self.current.stmts.push(CpsStmt::Select {
4829                    dest: field_value,
4830                    base: value,
4831                    field: field.name.clone(),
4832                });
4833            }
4834            self.bind_pattern(&field.pattern, field_value)?;
4835        }
4836        if let Some(spread) = record_spread_pattern(spread) {
4837            let rest = self.emit_record_without_fields(value, fields);
4838            self.bind_pattern(spread, rest)?;
4839        }
4840        Ok(())
4841    }
4842
4843    fn emit_record_without_fields(
4844        &mut self,
4845        value: CpsValueId,
4846        fields: &[runtime::RecordPatternField],
4847    ) -> CpsValueId {
4848        let dest = self.fresh_value();
4849        self.current.stmts.push(CpsStmt::RecordWithoutFields {
4850            dest,
4851            base: value,
4852            fields: fields.iter().map(|field| field.name.clone()).collect(),
4853        });
4854        dest
4855    }
4856
4857    fn emit_int_literal(&mut self, value: i64) -> CpsValueId {
4858        let dest = self.fresh_value();
4859        self.current.stmts.push(CpsStmt::Literal {
4860            dest,
4861            literal: CpsLiteral::Int(value.to_string()),
4862        });
4863        dest
4864    }
4865
4866    fn emit_primitive(&mut self, op: typed_ir::PrimitiveOp, args: Vec<CpsValueId>) -> CpsValueId {
4867        let dest = self.fresh_value();
4868        self.current
4869            .stmts
4870            .push(CpsStmt::Primitive { dest, op, args });
4871        dest
4872    }
4873
4874    fn fresh_value(&mut self) -> CpsValueId {
4875        let value = CpsValueId(self.next_value);
4876        self.next_value += 1;
4877        value
4878    }
4879
4880    fn fresh_continuation(&mut self) -> CpsContinuationId {
4881        let continuation = CpsContinuationId(self.next_continuation);
4882        self.next_continuation += 1;
4883        continuation
4884    }
4885
4886    fn fresh_handler(&mut self) -> CpsHandlerId {
4887        let handler = CpsHandlerId(self.next_handler);
4888        self.next_handler += 1;
4889        handler
4890    }
4891
4892    fn current_effect_context(&self) -> (Vec<typed_ir::Path>, CpsHandlerId) {
4893        self.active_handler
4894            .as_ref()
4895            .map(|context| (context.expected_effects.clone(), context.handler))
4896            .unwrap_or_else(|| (Vec::new(), dynamic_handler_id()))
4897    }
4898
4899    fn effect_context_for_request(
4900        &self,
4901        request: &CpsEffectApply<'_>,
4902        expected_effects: &[typed_ir::Path],
4903        handler: CpsHandlerId,
4904    ) -> (Vec<typed_ir::Path>, CpsHandlerId) {
4905        if let Some(context) = self.active_context_for_effect(&request.effect) {
4906            return (context.expected_effects.clone(), context.handler);
4907        }
4908        if matches_any_effect(expected_effects, &request.effect) {
4909            return (expected_effects.to_vec(), handler);
4910        }
4911        (Vec::new(), dynamic_handler_id())
4912    }
4913
4914    fn active_context_for_effect(&self, effect: &typed_ir::Path) -> Option<&ActiveHandlerContext> {
4915        let mut current = self.active_handler.as_ref();
4916        while let Some(context) = current {
4917            if matches_any_effect(&context.expected_effects, effect) {
4918                return Some(context);
4919            }
4920            current = context.parent.as_deref();
4921        }
4922        None
4923    }
4924
4925    fn performed_effects_for_handler(&self, handler: CpsHandlerId) -> Vec<typed_ir::Path> {
4926        let mut effects = Vec::new();
4927        for continuation in &self.continuations {
4928            if let CpsTerminator::Perform {
4929                effect,
4930                handler: used,
4931                ..
4932            } = &continuation.terminator
4933                && *used == handler
4934                && !effects.iter().any(|seen| seen == effect)
4935            {
4936                effects.push(effect.clone());
4937            }
4938        }
4939        for (used, effect) in &self.forced_handler_effects {
4940            if *used == handler && !effects.iter().any(|seen| seen == effect) {
4941                effects.push(effect.clone());
4942            }
4943        }
4944        effects
4945    }
4946
4947    fn target_may_perform_when_called(&self, target: &typed_ir::Path) -> bool {
4948        let mut visiting = HashSet::new();
4949        let mut memo = HashMap::new();
4950        binding_may_perform_when_called(
4951            target,
4952            self.functions,
4953            self.bindings,
4954            &mut visiting,
4955            &mut memo,
4956        )
4957    }
4958
4959    fn direct_call_has_handler_reentry_arg(
4960        &self,
4961        target: &typed_ir::Path,
4962        args: &[&runtime::Expr],
4963    ) -> bool {
4964        if self.active_handler.is_none() {
4965            return false;
4966        }
4967        let Some(binding) = self.bindings.get(target) else {
4968            return false;
4969        };
4970        if handler_wrapper_info(binding).is_none() {
4971            return false;
4972        }
4973        args.iter().any(|arg| self.expr_contains_resume_apply(arg))
4974    }
4975
4976    fn direct_call_needs_handler_wrapper_boundary(
4977        &self,
4978        target: &typed_ir::Path,
4979        args: &[&runtime::Expr],
4980        result_ty: &runtime::Type,
4981    ) -> bool {
4982        if !runtime_type_is_bool_value(result_ty) {
4983            return false;
4984        }
4985        if path_name(target) == self.name {
4986            return false;
4987        }
4988        let Some(binding) = self.bindings.get(target) else {
4989            return false;
4990        };
4991        let Some(wrapper) = handler_wrapper_info(binding) else {
4992            return false;
4993        };
4994        handler_wrapper_args_have_unconsumed_effects_for_wrapper(args, &wrapper)
4995    }
4996
4997    fn expr_may_perform_when_evaluated(&self, expr: &runtime::Expr) -> bool {
4998        let mut visiting = HashSet::new();
4999        let mut memo = HashMap::new();
5000        expr_may_perform_when_evaluated(
5001            expr,
5002            self.functions,
5003            self.bindings,
5004            &mut visiting,
5005            &mut memo,
5006        )
5007    }
5008
5009    fn plan_direct_call<'expr, 'functions>(
5010        &self,
5011        expr: &'expr runtime::Expr,
5012        target_path: &'expr typed_ir::Path,
5013        info: &'functions FunctionInfo,
5014        args: Vec<&'expr runtime::Expr>,
5015    ) -> DirectCallPlan<'expr, 'functions> {
5016        let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
5017        let target_may_perform = self.target_may_perform_when_called(target_path);
5018        let needs_handler_wrapper_boundary =
5019            self.direct_call_needs_handler_wrapper_boundary(target_path, &args, &expr.ty);
5020        let force_handler_reentry_args =
5021            self.direct_call_has_handler_reentry_arg(target_path, &args);
5022        let should_inline = (!matches!(expr.ty, runtime::Type::Thunk { .. })
5023            && args.iter().any(|arg| is_inline_argument(arg)))
5024            || (self.active_handler.is_some() && info_returns_thunk && target_may_perform);
5025        let ignored_immediate_force =
5026            self.sync_direct_call_for_ignored_force_depth.is_active() && info_returns_thunk;
5027        let ignored_unit_immediate_force =
5028            ignored_immediate_force && runtime_type_is_unit_value(&expr.ty);
5029        let mode = if (self.higher_order_helper
5030            && info_returns_thunk
5031            && !ignored_unit_immediate_force)
5032            || (!ignored_immediate_force && (target_may_perform || needs_handler_wrapper_boundary))
5033        {
5034            DirectCallMode::EffectfulWithResume
5035        } else {
5036            DirectCallMode::SyncDirect
5037        };
5038        DirectCallPlan {
5039            expr,
5040            target: info.name.clone(),
5041            info,
5042            args,
5043            mode,
5044            target_may_perform,
5045            info_returns_thunk,
5046            force_handler_reentry_args,
5047            should_inline,
5048        }
5049    }
5050
5051    fn lower_direct_call_plan(
5052        &mut self,
5053        plan: DirectCallPlan<'_, '_>,
5054    ) -> CpsLowerResult<CpsValueId> {
5055        if (fail_prefix_path(&plan.info.path)
5056            || self
5057                .bindings
5058                .get(&plan.info.path)
5059                .is_some_and(|binding| binding_is_throw_forwarder(binding)))
5060            && let [arg] = plan.args.as_slice()
5061        {
5062            let expected = plan
5063                .info
5064                .params
5065                .first()
5066                .cloned()
5067                .unwrap_or_else(runtime::Type::unknown);
5068            let value = self.lower_direct_call_arg(arg, &expected)?;
5069            return Ok(self.force_if_non_thunk_demand(value, &plan.expr.ty));
5070        }
5071        if plan.should_inline
5072            && let Some(value) = self.lower_inline_direct_apply(plan.expr)?
5073        {
5074            return Ok(self.force_if_non_thunk_demand(value, &plan.expr.ty));
5075        }
5076
5077        let args = self.lower_direct_call_args(&plan)?;
5078        match plan.mode {
5079            DirectCallMode::EffectfulWithResume => {
5080                // Effectful helpers must cross a continuation boundary even when
5081                // their surface result is plain: wrapper functions often force an
5082                // inner thunk before returning, so `info.ret` alone can hide that
5083                // the call performs under the caller's active handler.
5084                self.mark_active_handlers_external_call();
5085                let post_cont = self.fresh_continuation();
5086                let result = self.fresh_value();
5087                self.terminate(CpsTerminator::EffectfulCall {
5088                    target: plan.target,
5089                    args,
5090                    resume: post_cont,
5091                });
5092                self.finish_current();
5093                self.current = ContinuationBuilder::new(post_cont, vec![result]);
5094                // write18: mirror the sync DirectCall path's demand-side
5095                // forcing. fold_impl/each/once return Thunks via MakeThunk +
5096                // Return, so the post-call cont receives a Thunk. If the
5097                // call site's static type is non-Thunk, force the result
5098                // here so downstream uses see the unwrapped value. Without
5099                // this, fold_impl's left recursive call returns a Thunk that
5100                // is silently used as z for the right call without ever
5101                // running the left iteration — skipping leaves and giving
5102                // the rightmost element instead of the proper fold.
5103                Ok(self.force_if_non_thunk_demand(result, &plan.expr.ty))
5104            }
5105            DirectCallMode::SyncDirect => {
5106                let dest = self.fresh_value();
5107                self.current.stmts.push(CpsStmt::DirectCall {
5108                    dest,
5109                    target: plan.target,
5110                    args,
5111                });
5112                if plan.info_returns_thunk || plan.target_may_perform {
5113                    self.mark_active_handlers_external_call();
5114                }
5115                // Result demand forcing. Many effectful helpers (`each`, `once`,
5116                // ...) lower as `MakeThunk` + `Return` so that direct callers
5117                // that need them as a thunk (`once_mono1` expects each as a
5118                // thunk arg) receive a thunk handle. But callers that bind the
5119                // result to a non-thunk static type (`(each ...).once` returns
5120                // `opt int`) must force it. ForceThunk is a no-op on non-thunk
5121                // values, so it's safe to insert whenever the consumer's
5122                // expected type is non-Thunk.
5123                Ok(self.force_if_non_thunk_demand(dest, &plan.expr.ty))
5124            }
5125        }
5126    }
5127
5128    fn lower_direct_call_args(
5129        &mut self,
5130        plan: &DirectCallPlan<'_, '_>,
5131    ) -> CpsLowerResult<Vec<CpsValueId>> {
5132        let info_params = plan.info.params.clone();
5133        plan.args
5134            .iter()
5135            .enumerate()
5136            .map(|(idx, arg)| {
5137                let expected = info_params
5138                    .get(idx)
5139                    .cloned()
5140                    .unwrap_or_else(runtime::Type::unknown);
5141                let force_effectful_arg = plan.target_may_perform
5142                    && expr_contains_indirect_apply(arg, self.functions)
5143                    && !type_is_callable_after_force(&arg.ty)
5144                    && !matches!(expected, runtime::Type::Thunk { .. })
5145                    && !matches!(arg.ty, runtime::Type::Thunk { .. });
5146                let needs_forced_effectful_depth = (plan.force_handler_reentry_args
5147                    && self.expr_contains_resume_apply(arg))
5148                    || force_effectful_arg;
5149                if needs_forced_effectful_depth {
5150                    self.with_force_effectful_apply_depth(|this| {
5151                        this.lower_direct_call_arg(arg, &expected)
5152                    })
5153                } else {
5154                    self.lower_direct_call_arg(arg, &expected)
5155                }
5156            })
5157            .collect()
5158    }
5159
5160    fn lower_direct_call_arg(
5161        &mut self,
5162        arg: &runtime::Expr,
5163        expected: &runtime::Type,
5164    ) -> CpsLowerResult<CpsValueId> {
5165        let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
5166            self.lower_expr_as_thunk_value(arg)?
5167        } else {
5168            self.lower_expr(arg)?
5169        };
5170        Ok(self.force_if_non_thunk_demand(lowered, expected))
5171    }
5172
5173    fn lower_inline_direct_apply(
5174        &mut self,
5175        expr: &runtime::Expr,
5176    ) -> CpsLowerResult<Option<CpsValueId>> {
5177        let Some((target, info, args)) = direct_apply_path(expr, self.functions)? else {
5178            return Ok(None);
5179        };
5180        if path_name(target) == self.name || !self.inline_stack.insert(target.clone()) {
5181            return Ok(None);
5182        }
5183        let Some(binding) = self.bindings.get(target) else {
5184            self.inline_stack.remove(target);
5185            return Ok(None);
5186        };
5187        if binding_has_self_direct_call(target, &binding.body, self.functions) {
5188            self.inline_stack.remove(target);
5189            return Ok(None);
5190        }
5191        let (params, body) = collect_callable_params(&binding.body);
5192        if params.len() != args.len() {
5193            self.inline_stack.remove(target);
5194            return Ok(None);
5195        }
5196        let info_params = info.params.clone();
5197
5198        let saved_locals = self.locals.clone();
5199        let saved_local_exprs = self.local_exprs.clone();
5200        let saved_resumptions = self.resumptions.clone();
5201        for (idx, (param, arg)) in params.into_iter().zip(args).enumerate() {
5202            let path = typed_ir::Path::from_name(param);
5203            let expected = info_params
5204                .get(idx)
5205                .cloned()
5206                .unwrap_or_else(runtime::Type::unknown);
5207            if (is_inline_argument(arg) || matches!(arg.ty, runtime::Type::Thunk { .. }))
5208                && !expr_uses_path(arg, &path)
5209            {
5210                self.local_exprs.insert(path, arg.clone());
5211            } else {
5212                let value = self.lower_direct_call_arg(arg, &expected)?;
5213                self.locals.insert(path, value);
5214            }
5215        }
5216        let value = self.lower_expr(&body);
5217        self.inline_stack.remove(target);
5218        self.locals = saved_locals;
5219        self.local_exprs = saved_local_exprs;
5220        self.resumptions = saved_resumptions;
5221        value.map(Some)
5222    }
5223
5224    fn local_expr_apply_case<'expr>(
5225        &self,
5226        expr: &'expr runtime::Expr,
5227    ) -> Option<(runtime::Expr, &'expr runtime::Expr)> {
5228        let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
5229            return None;
5230        };
5231        let callee = transparent_effect_expr(callee);
5232        let runtime::ExprKind::Var(path) = &callee.kind else {
5233            return None;
5234        };
5235        let callee = self.local_exprs.get(path).cloned()?;
5236        if callable_expr_is_thunk_wrapped(&callee) {
5237            return Some((callee, arg));
5238        }
5239        let inline_callee = inline_callable_expr(&callee);
5240        let (params, _) = collect_lambda_params(&inline_callee);
5241        (params.len() == 1).then_some((callee, arg))
5242    }
5243
5244    fn lower_local_expr_apply_case(
5245        &mut self,
5246        callee: &runtime::Expr,
5247        arg: &runtime::Expr,
5248        result_ty: &runtime::Type,
5249    ) -> CpsLowerResult<CpsValueId> {
5250        if callable_expr_is_thunk_wrapped(&callee) {
5251            let closure = self.lower_expr(&callee)?;
5252            let forced = self.fresh_value();
5253            self.current.stmts.push(CpsStmt::ForceThunk {
5254                dest: forced,
5255                thunk: closure,
5256            });
5257            let callee_ty = callable_type_after_force(&callee.ty);
5258            let arg = self.lower_expr_as_call_arg(callee_ty, arg)?;
5259            if self.force_effectful_apply_depth.is_active()
5260                || (self.sync_apply_for_immediate_force_depth.is_inactive()
5261                    && self.higher_order_helper
5262                    && matches!(result_ty, runtime::Type::Thunk { .. }))
5263            {
5264                let post_cont = self.fresh_continuation();
5265                let result = self.fresh_value();
5266                self.terminate(CpsTerminator::EffectfulApply {
5267                    closure: forced,
5268                    arg,
5269                    resume: post_cont,
5270                });
5271                self.finish_current();
5272                self.mark_active_handlers_external_call();
5273                self.current = ContinuationBuilder::new(post_cont, vec![result]);
5274                return Ok(self.force_if_non_thunk_demand(result, result_ty));
5275            }
5276            let dest = self.fresh_value();
5277            self.current.stmts.push(CpsStmt::ApplyClosure {
5278                dest,
5279                closure: forced,
5280                arg,
5281            });
5282            return Ok(self.force_if_non_thunk_demand(dest, result_ty));
5283        }
5284        let callee = inline_callable_expr(&callee);
5285        let (params, body) = collect_lambda_params(&callee);
5286        if params.len() != 1 {
5287            unreachable!("local expr apply classifier only selects unary local callables")
5288        }
5289        let saved_locals = self.locals.clone();
5290        let saved_local_exprs = self.local_exprs.clone();
5291        let saved_resumptions = self.resumptions.clone();
5292        let path = typed_ir::Path::from_name(params[0].clone());
5293        if (is_inline_argument(arg) || matches!(arg.ty, runtime::Type::Thunk { .. }))
5294            && !expr_uses_path(arg, &path)
5295        {
5296            self.local_exprs.insert(path, arg.clone());
5297        } else {
5298            let value = self.lower_expr(arg)?;
5299            self.locals.insert(path, value);
5300        }
5301        let value = self.lower_expr(body);
5302        self.locals = saved_locals;
5303        self.local_exprs = saved_local_exprs;
5304        self.resumptions = saved_resumptions;
5305        value
5306    }
5307
5308    fn handler_reentry_apply(
5309        &mut self,
5310        expr: &runtime::Expr,
5311        handler: CpsHandlerId,
5312    ) -> CpsLowerResult<Option<HandlerReentry>> {
5313        let Some((target, _, args)) = direct_apply_path(expr, self.functions)? else {
5314            return Ok(None);
5315        };
5316        let Some(binding) = self.bindings.get(target) else {
5317            return Ok(None);
5318        };
5319        let Some(wrapper) = handler_wrapper_info(binding) else {
5320            return Ok(None);
5321        };
5322        if wrapper.params.len() != args.len() || wrapper.params.is_empty() {
5323            return Ok(None);
5324        }
5325
5326        let mut resume_candidate = None;
5327        for (index, arg) in args.iter().enumerate() {
5328            let Some((resumption, resume_arg)) = self.resume_thunk_argument(arg) else {
5329                continue;
5330            };
5331            if resume_candidate.is_some() {
5332                return Ok(None);
5333            }
5334            resume_candidate = Some((index, resumption, resume_arg));
5335        }
5336        let Some((resume_index, resumption, resume_arg)) = resume_candidate else {
5337            return Ok(None);
5338        };
5339
5340        let state_params = wrapper
5341            .params
5342            .iter()
5343            .enumerate()
5344            .filter_map(|(index, param)| (index != resume_index).then_some(param))
5345            .collect::<Vec<_>>();
5346        let state_args = args
5347            .iter()
5348            .enumerate()
5349            .filter_map(|(index, arg)| (index != resume_index).then_some(*arg))
5350            .map(|arg| self.lower_expr(arg))
5351            .collect::<CpsLowerResult<Vec<_>>>()?;
5352        let arg = self.lower_expr(resume_arg)?;
5353        let envs = self.handler_reentry_envs(
5354            handler,
5355            &wrapper.arms,
5356            &wrapper.consumes,
5357            &state_params,
5358            &state_args,
5359        );
5360        let dest = self.fresh_value();
5361        Ok(Some(HandlerReentry {
5362            dest,
5363            resumption,
5364            arg,
5365            envs,
5366        }))
5367    }
5368
5369    fn resume_thunk_argument<'expr>(
5370        &self,
5371        expr: &'expr runtime::Expr,
5372    ) -> Option<(CpsValueId, &'expr runtime::Expr)> {
5373        let expr = transparent_effect_expr(expr);
5374        let runtime::ExprKind::Thunk { expr, .. } = &expr.kind else {
5375            return None;
5376        };
5377        let expr = handle_body_execution_inner(expr).unwrap_or(expr);
5378        self.resume_apply(expr)
5379    }
5380
5381    fn handler_reentry_envs(
5382        &self,
5383        handler: CpsHandlerId,
5384        arms: &[runtime::HandleArm],
5385        consumes: &[typed_ir::Path],
5386        state_params: &[&typed_ir::Name],
5387        state_args: &[CpsValueId],
5388    ) -> Vec<CpsHandlerEnv> {
5389        let Some(handler) = self
5390            .handlers
5391            .iter()
5392            .find(|candidate| candidate.id == handler)
5393        else {
5394            return Vec::new();
5395        };
5396        let mut envs = Vec::new();
5397        for arm in arms {
5398            let values = state_params
5399                .iter()
5400                .zip(state_args.iter().copied())
5401                .filter_map(|(param, value)| {
5402                    expr_uses_path(&arm.body, &typed_ir::Path::from_name((*param).clone()))
5403                        .then_some(value)
5404                })
5405                .collect::<Vec<_>>();
5406            if values.is_empty() {
5407                continue;
5408            }
5409            let targets = state_params
5410                .iter()
5411                .zip(state_args.iter().copied())
5412                .filter_map(|(param, value)| {
5413                    let path = typed_ir::Path::from_name((*param).clone());
5414                    expr_uses_path(&arm.body, &path).then(|| {
5415                        self.function_param_values
5416                            .get(&path)
5417                            .copied()
5418                            .unwrap_or(value)
5419                    })
5420                })
5421                .collect::<Vec<_>>();
5422            for effect in scoped_handler_effects(consumes, &arm.effect) {
5423                let Some(entry) = handler
5424                    .arms
5425                    .iter()
5426                    .find(|candidate| effect_matches(&candidate.effect, &effect))
5427                    .map(|arm| arm.entry)
5428                else {
5429                    continue;
5430                };
5431                envs.push(CpsHandlerEnv {
5432                    entry,
5433                    values: values.clone(),
5434                    targets: targets.clone(),
5435                });
5436            }
5437        }
5438        envs
5439    }
5440
5441    fn resume_apply<'expr>(
5442        &self,
5443        expr: &'expr runtime::Expr,
5444    ) -> Option<(CpsValueId, &'expr runtime::Expr)> {
5445        let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
5446            return None;
5447        };
5448        let runtime::ExprKind::Var(path) = &callee.kind else {
5449            return None;
5450        };
5451        if !self.resumptions.contains(path) {
5452            return None;
5453        }
5454        let resumption = *self.locals.get(path)?;
5455        Some((resumption, arg.as_ref()))
5456    }
5457
5458    fn apply_chain_contains_resume_argument(&self, expr: &runtime::Expr) -> bool {
5459        let mut current = transparent_effect_expr(expr);
5460        while let runtime::ExprKind::Apply { callee, arg, .. } = &current.kind {
5461            if self.expr_contains_resume_apply(arg) {
5462                return true;
5463            }
5464            current = transparent_effect_expr(callee);
5465        }
5466        false
5467    }
5468
5469    fn expr_contains_resume_apply(&self, expr: &runtime::Expr) -> bool {
5470        if self.resume_apply(expr).is_some() {
5471            return true;
5472        }
5473        match &expr.kind {
5474            runtime::ExprKind::Lambda { body, .. }
5475            | runtime::ExprKind::Thunk { expr: body, .. }
5476            | runtime::ExprKind::LocalPushId { body, .. }
5477            | runtime::ExprKind::BindHere { expr: body }
5478            | runtime::ExprKind::AddId { thunk: body, .. }
5479            | runtime::ExprKind::Coerce { expr: body, .. }
5480            | runtime::ExprKind::Pack { expr: body, .. } => self.expr_contains_resume_apply(body),
5481            runtime::ExprKind::Apply { callee, arg, .. } => {
5482                self.expr_contains_resume_apply(callee) || self.expr_contains_resume_apply(arg)
5483            }
5484            runtime::ExprKind::If {
5485                cond,
5486                then_branch,
5487                else_branch,
5488                ..
5489            } => {
5490                self.expr_contains_resume_apply(cond)
5491                    || self.expr_contains_resume_apply(then_branch)
5492                    || self.expr_contains_resume_apply(else_branch)
5493            }
5494            runtime::ExprKind::Tuple(items) => items
5495                .iter()
5496                .any(|item| self.expr_contains_resume_apply(item)),
5497            runtime::ExprKind::Record { fields, spread } => {
5498                fields
5499                    .iter()
5500                    .any(|field| self.expr_contains_resume_apply(&field.value))
5501                    || spread.as_ref().is_some_and(|spread| match spread {
5502                        runtime::RecordSpreadExpr::Head(expr)
5503                        | runtime::RecordSpreadExpr::Tail(expr) => {
5504                            self.expr_contains_resume_apply(expr)
5505                        }
5506                    })
5507            }
5508            runtime::ExprKind::Variant {
5509                value: Some(value), ..
5510            }
5511            | runtime::ExprKind::Select { base: value, .. } => {
5512                self.expr_contains_resume_apply(value)
5513            }
5514            runtime::ExprKind::Match {
5515                scrutinee, arms, ..
5516            } => {
5517                self.expr_contains_resume_apply(scrutinee)
5518                    || arms.iter().any(|arm| {
5519                        arm.guard
5520                            .as_ref()
5521                            .is_some_and(|guard| self.expr_contains_resume_apply(guard))
5522                            || self.expr_contains_resume_apply(&arm.body)
5523                    })
5524            }
5525            runtime::ExprKind::Block { stmts, tail } => {
5526                stmts.iter().any(|stmt| match stmt {
5527                    runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
5528                        self.expr_contains_resume_apply(value)
5529                    }
5530                    runtime::Stmt::Module { body, .. } => self.expr_contains_resume_apply(body),
5531                }) || tail
5532                    .as_ref()
5533                    .is_some_and(|tail| self.expr_contains_resume_apply(tail))
5534            }
5535            runtime::ExprKind::Handle { body, arms, .. } => {
5536                self.expr_contains_resume_apply(body)
5537                    || arms.iter().any(|arm| {
5538                        arm.guard
5539                            .as_ref()
5540                            .is_some_and(|guard| self.expr_contains_resume_apply(guard))
5541                            || self.expr_contains_resume_apply(&arm.body)
5542                    })
5543            }
5544            runtime::ExprKind::Var(_)
5545            | runtime::ExprKind::EffectOp(_)
5546            | runtime::ExprKind::PrimitiveOp(_)
5547            | runtime::ExprKind::Lit(_)
5548            | runtime::ExprKind::Variant { value: None, .. }
5549            | runtime::ExprKind::PeekId
5550            | runtime::ExprKind::FindId { .. } => false,
5551        }
5552    }
5553
5554    fn terminate(&mut self, terminator: CpsTerminator) {
5555        self.current.terminator = Some(terminator);
5556    }
5557
5558    fn finish_current(&mut self) {
5559        let terminator = self
5560            .current
5561            .terminator
5562            .take()
5563            .expect("CPS lowerer finished an unterminated continuation");
5564        let id = self.current.id;
5565        let params = std::mem::take(&mut self.current.params);
5566        let captures = std::mem::take(&mut self.current.captures);
5567        let stmts = std::mem::take(&mut self.current.stmts);
5568        self.finish_continuation(id, params, captures, stmts, terminator);
5569    }
5570
5571    fn finish_continuation(
5572        &mut self,
5573        id: CpsContinuationId,
5574        params: Vec<CpsValueId>,
5575        captures: Vec<CpsValueId>,
5576        mut stmts: Vec<CpsStmt>,
5577        terminator: CpsTerminator,
5578    ) {
5579        if let Some(force_index) = first_known_thunk_force_with_rest(&stmts) {
5580            let rest = stmts.split_off(force_index + 1);
5581            let force = stmts
5582                .pop()
5583                .expect("force_index points at a ForceThunk statement");
5584            let CpsStmt::ForceThunk { dest, thunk } = force else {
5585                unreachable!("force_index points at a ForceThunk statement")
5586            };
5587            let resume = self.fresh_continuation();
5588            let raw_forced = self.fresh_value();
5589            self.continuations.push(CpsContinuation {
5590                id,
5591                params,
5592                captures,
5593                shot_kind: CpsShotKind::MultiShot,
5594                stmts,
5595                terminator: CpsTerminator::EffectfulForce { thunk, resume },
5596            });
5597            // `EffectfulForce` establishes the continuation boundary, then the
5598            // resumed `ForceThunk` preserves the original statement's deep
5599            // forcing semantics under the restored handler/frame context.
5600            let mut resumed_stmts = Vec::with_capacity(rest.len() + 1);
5601            resumed_stmts.push(CpsStmt::ForceThunk {
5602                dest,
5603                thunk: raw_forced,
5604            });
5605            resumed_stmts.extend(rest);
5606            self.finish_continuation(
5607                resume,
5608                vec![raw_forced],
5609                Vec::new(),
5610                resumed_stmts,
5611                terminator,
5612            );
5613            return;
5614        }
5615        self.continuations.push(CpsContinuation {
5616            id,
5617            params,
5618            captures,
5619            shot_kind: CpsShotKind::MultiShot,
5620            stmts,
5621            terminator,
5622        });
5623    }
5624}
5625
5626fn restore_effect_guard(
5627    guards: &mut HashMap<runtime::EffectIdVar, CpsValueId>,
5628    id: runtime::EffectIdVar,
5629    previous: Option<CpsValueId>,
5630) {
5631    match previous {
5632        Some(previous) => {
5633            guards.insert(id, previous);
5634        }
5635        None => {
5636            guards.remove(&id);
5637        }
5638    }
5639}
5640
5641fn effect_matches(expected: &typed_ir::Path, actual: &typed_ir::Path) -> bool {
5642    actual == expected
5643        || (!expected.segments.is_empty()
5644            && actual.segments.len() == expected.segments.len() + 1
5645            && actual.segments.starts_with(&expected.segments))
5646        || (expected.segments.len() == 1 && actual.segments.last() == expected.segments.first())
5647}
5648
5649fn scoped_handler_effects(
5650    consumes: &[typed_ir::Path],
5651    effect: &typed_ir::Path,
5652) -> Vec<typed_ir::Path> {
5653    if effect.segments.is_empty()
5654        || consumes.is_empty()
5655        || !consumes.iter().any(is_local_ref_effect_scope)
5656    {
5657        return vec![effect.clone()];
5658    }
5659
5660    let mut scoped = Vec::new();
5661    for consume in consumes {
5662        let effect_already_scoped = effect_matches(consume, effect)
5663            || (!consume.segments.is_empty() && effect.segments.starts_with(&consume.segments));
5664        let effect_names_consumed_effect =
5665            effect.segments.len() == 1 && consume.segments.last() == effect.segments.first();
5666        let path = if effect_already_scoped {
5667            effect.clone()
5668        } else if effect_names_consumed_effect {
5669            consume.clone()
5670        } else {
5671            typed_ir::Path {
5672                segments: consume
5673                    .segments
5674                    .iter()
5675                    .chain(effect.segments.iter())
5676                    .cloned()
5677                    .collect(),
5678            }
5679        };
5680        if !scoped.iter().any(|existing| existing == &path) {
5681            scoped.push(path);
5682        }
5683    }
5684    scoped
5685}
5686
5687fn is_local_ref_effect_scope(path: &typed_ir::Path) -> bool {
5688    path.segments
5689        .first()
5690        .is_some_and(|segment| segment.0.starts_with('&'))
5691}
5692
5693fn is_inline_argument(expr: &runtime::Expr) -> bool {
5694    match &expr.kind {
5695        runtime::ExprKind::Lambda { .. }
5696        | runtime::ExprKind::Thunk { .. }
5697        | runtime::ExprKind::LocalPushId { .. } => true,
5698        runtime::ExprKind::BindHere { expr }
5699        | runtime::ExprKind::AddId { thunk: expr, .. }
5700        | runtime::ExprKind::Coerce { expr, .. }
5701        | runtime::ExprKind::Pack { expr, .. } => is_inline_argument(expr),
5702        _ => false,
5703    }
5704}
5705
5706fn inline_callable_expr(expr: &runtime::Expr) -> runtime::Expr {
5707    match &expr.kind {
5708        runtime::ExprKind::Thunk { expr, .. }
5709        | runtime::ExprKind::LocalPushId { body: expr, .. }
5710        | runtime::ExprKind::BindHere { expr }
5711        | runtime::ExprKind::AddId { thunk: expr, .. }
5712        | runtime::ExprKind::Coerce { expr, .. }
5713        | runtime::ExprKind::Pack { expr, .. } => inline_callable_expr(expr),
5714        _ => expr.clone(),
5715    }
5716}
5717
5718fn callable_expr_is_thunk_wrapped(expr: &runtime::Expr) -> bool {
5719    match &expr.kind {
5720        runtime::ExprKind::Thunk { .. } => true,
5721        runtime::ExprKind::LocalPushId { body, .. }
5722        | runtime::ExprKind::BindHere { expr: body }
5723        | runtime::ExprKind::AddId { thunk: body, .. }
5724        | runtime::ExprKind::Coerce { expr: body, .. }
5725        | runtime::ExprKind::Pack { expr: body, .. } => callable_expr_is_thunk_wrapped(body),
5726        _ => false,
5727    }
5728}
5729
5730struct HandlerWrapperInfo {
5731    params: Vec<typed_ir::Name>,
5732    arms: Vec<runtime::HandleArm>,
5733    consumes: Vec<typed_ir::Path>,
5734}
5735
5736struct HandlerReentry {
5737    dest: CpsValueId,
5738    resumption: CpsValueId,
5739    arg: CpsValueId,
5740    envs: Vec<CpsHandlerEnv>,
5741}
5742
5743fn handler_wrapper_info(binding: &runtime::Binding) -> Option<HandlerWrapperInfo> {
5744    let (params, body) = collect_lambda_params(&binding.body);
5745    let handled_param = params.last()?;
5746    let (handled_body, arms, handler) = handler_wrapper_handle(body)?;
5747    let handled_body = handle_body_execution_inner(handled_body).unwrap_or(handled_body);
5748    let handled_body = transparent_expr(handled_body);
5749    let runtime::ExprKind::Var(body_var) = &handled_body.kind else {
5750        return None;
5751    };
5752    if body_var != &typed_ir::Path::from_name(handled_param.clone()) {
5753        return None;
5754    }
5755    Some(HandlerWrapperInfo {
5756        params,
5757        arms: arms.to_vec(),
5758        consumes: handler.consumes.clone(),
5759    })
5760}
5761
5762fn handler_wrapper_handle(
5763    expr: &runtime::Expr,
5764) -> Option<(
5765    &runtime::Expr,
5766    &[runtime::HandleArm],
5767    &runtime::HandleEffect,
5768)> {
5769    let mut current = expr;
5770    loop {
5771        match &current.kind {
5772            runtime::ExprKind::LocalPushId { body, .. } => current = body,
5773            runtime::ExprKind::BindHere { expr }
5774            | runtime::ExprKind::Coerce { expr, .. }
5775            | runtime::ExprKind::Pack { expr, .. } => current = expr,
5776            runtime::ExprKind::AddId { thunk, .. }
5777            | runtime::ExprKind::Thunk { expr: thunk, .. } => {
5778                current = thunk;
5779            }
5780            runtime::ExprKind::Handle {
5781                body,
5782                arms,
5783                handler,
5784                ..
5785            } => return Some((body, arms, handler)),
5786            _ => return None,
5787        }
5788    }
5789}
5790
5791fn handler_wrapper_args_have_unconsumed_effects(
5792    target: &typed_ir::Path,
5793    args: &[&runtime::Expr],
5794    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5795) -> bool {
5796    let Some(binding) = bindings.get(target) else {
5797        return false;
5798    };
5799    let Some(wrapper) = handler_wrapper_info(binding) else {
5800        return false;
5801    };
5802    handler_wrapper_args_have_unconsumed_effects_for_wrapper(args, &wrapper)
5803}
5804
5805fn handler_wrapper_args_have_unconsumed_effects_for_wrapper(
5806    args: &[&runtime::Expr],
5807    wrapper: &HandlerWrapperInfo,
5808) -> bool {
5809    // A wrapper can safely run effects it consumes itself. Effects left for
5810    // an outer handler need the caller's post-call continuation in the
5811    // captured resumption.
5812    args.iter().any(|arg| {
5813        collect_expr_performed_effects(arg).iter().any(|effect| {
5814            !wrapper
5815                .consumes
5816                .iter()
5817                .any(|consume| effect_matches(consume, effect))
5818        })
5819    })
5820}
5821
5822fn binding_may_perform_when_called(
5823    target: &typed_ir::Path,
5824    functions: &HashMap<typed_ir::Path, FunctionInfo>,
5825    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5826    visiting: &mut HashSet<typed_ir::Path>,
5827    memo: &mut HashMap<typed_ir::Path, bool>,
5828) -> bool {
5829    if let Some(result) = memo.get(target) {
5830        return *result;
5831    }
5832    if !visiting.insert(target.clone()) {
5833        return false;
5834    }
5835    let result = bindings.get(target).is_some_and(|binding| {
5836        if matches!(binding.body.kind, runtime::ExprKind::PrimitiveOp(_)) {
5837            return false;
5838        }
5839        let (_, body) = collect_callable_params(&binding.body);
5840        expr_may_perform_when_evaluated(&body, functions, bindings, visiting, memo)
5841    });
5842    visiting.remove(target);
5843    memo.insert(target.clone(), result);
5844    result
5845}
5846
5847fn expr_may_perform_when_evaluated(
5848    expr: &runtime::Expr,
5849    functions: &HashMap<typed_ir::Path, FunctionInfo>,
5850    bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5851    visiting: &mut HashSet<typed_ir::Path>,
5852    memo: &mut HashMap<typed_ir::Path, bool>,
5853) -> bool {
5854    if let Some(inner) = handle_body_execution_inner(expr) {
5855        return expr_may_perform_when_evaluated(inner, functions, bindings, visiting, memo);
5856    }
5857    if effect_apply_body_request(expr).is_some() {
5858        return true;
5859    }
5860    if let Ok(Some((target, _, args))) = direct_apply_path(expr, functions) {
5861        return args
5862            .iter()
5863            .any(|arg| expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo))
5864            || binding_may_perform_when_called(target, functions, bindings, visiting, memo);
5865    }
5866    if let Some((_, args)) = primitive_apply(expr) {
5867        return args
5868            .iter()
5869            .any(|arg| expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo));
5870    }
5871
5872    match &expr.kind {
5873        runtime::ExprKind::Apply { callee, arg, .. } => {
5874            expr_may_perform_when_evaluated(callee, functions, bindings, visiting, memo)
5875                || expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo)
5876                || callee_type_may_perform(&callee.ty)
5877        }
5878        runtime::ExprKind::If {
5879            cond,
5880            then_branch,
5881            else_branch,
5882            ..
5883        } => {
5884            expr_may_perform_when_evaluated(cond, functions, bindings, visiting, memo)
5885                || expr_may_perform_when_evaluated(then_branch, functions, bindings, visiting, memo)
5886                || expr_may_perform_when_evaluated(else_branch, functions, bindings, visiting, memo)
5887        }
5888        runtime::ExprKind::Tuple(items) => items
5889            .iter()
5890            .any(|item| expr_may_perform_when_evaluated(item, functions, bindings, visiting, memo)),
5891        runtime::ExprKind::Record { fields, spread } => {
5892            fields.iter().any(|field| {
5893                expr_may_perform_when_evaluated(&field.value, functions, bindings, visiting, memo)
5894            }) || spread.as_ref().is_some_and(|spread| match spread {
5895                runtime::RecordSpreadExpr::Head(expr) | runtime::RecordSpreadExpr::Tail(expr) => {
5896                    expr_may_perform_when_evaluated(expr, functions, bindings, visiting, memo)
5897                }
5898            })
5899        }
5900        runtime::ExprKind::Variant {
5901            value: Some(value), ..
5902        }
5903        | runtime::ExprKind::Select { base: value, .. } => {
5904            expr_may_perform_when_evaluated(value, functions, bindings, visiting, memo)
5905        }
5906        runtime::ExprKind::Match {
5907            scrutinee, arms, ..
5908        } => {
5909            expr_may_perform_when_evaluated(scrutinee, functions, bindings, visiting, memo)
5910                || arms.iter().any(|arm| {
5911                    arm.guard.as_ref().is_some_and(|guard| {
5912                        expr_may_perform_when_evaluated(guard, functions, bindings, visiting, memo)
5913                    }) || expr_may_perform_when_evaluated(
5914                        &arm.body, functions, bindings, visiting, memo,
5915                    )
5916                })
5917        }
5918        runtime::ExprKind::Block { stmts, tail } => {
5919            stmts.iter().any(|stmt| match stmt {
5920                runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
5921                    expr_may_perform_when_evaluated(value, functions, bindings, visiting, memo)
5922                }
5923                runtime::Stmt::Module { body, .. } => {
5924                    expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5925                }
5926            }) || tail.as_ref().is_some_and(|tail| {
5927                expr_may_perform_when_evaluated(tail, functions, bindings, visiting, memo)
5928            })
5929        }
5930        runtime::ExprKind::Handle { body, arms, .. } => {
5931            expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5932                || arms.iter().any(|arm| {
5933                    arm.guard.as_ref().is_some_and(|guard| {
5934                        expr_may_perform_when_evaluated(guard, functions, bindings, visiting, memo)
5935                    }) || expr_may_perform_when_evaluated(
5936                        &arm.body, functions, bindings, visiting, memo,
5937                    )
5938                })
5939        }
5940        runtime::ExprKind::Lambda { .. } | runtime::ExprKind::Thunk { .. } => false,
5941        runtime::ExprKind::LocalPushId { body, .. }
5942        | runtime::ExprKind::BindHere { expr: body }
5943        | runtime::ExprKind::AddId { thunk: body, .. }
5944        | runtime::ExprKind::Coerce { expr: body, .. }
5945        | runtime::ExprKind::Pack { expr: body, .. } => {
5946            expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5947        }
5948        runtime::ExprKind::Var(_)
5949        | runtime::ExprKind::EffectOp(_)
5950        | runtime::ExprKind::PrimitiveOp(_)
5951        | runtime::ExprKind::Lit(_)
5952        | runtime::ExprKind::Variant { value: None, .. }
5953        | runtime::ExprKind::PeekId
5954        | runtime::ExprKind::FindId { .. } => false,
5955    }
5956}
5957
5958fn expr_contains_indirect_apply(
5959    expr: &runtime::Expr,
5960    functions: &HashMap<typed_ir::Path, FunctionInfo>,
5961) -> bool {
5962    let this_is_indirect_apply = matches!(expr.kind, runtime::ExprKind::Apply { .. })
5963        && primitive_apply(expr).is_none()
5964        && effect_apply_body_request(expr).is_none()
5965        && direct_apply_path(expr, functions).ok().flatten().is_none();
5966    if this_is_indirect_apply {
5967        return true;
5968    }
5969
5970    match &expr.kind {
5971        runtime::ExprKind::Lambda { body, .. }
5972        | runtime::ExprKind::Thunk { expr: body, .. }
5973        | runtime::ExprKind::LocalPushId { body, .. }
5974        | runtime::ExprKind::BindHere { expr: body }
5975        | runtime::ExprKind::AddId { thunk: body, .. }
5976        | runtime::ExprKind::Coerce { expr: body, .. }
5977        | runtime::ExprKind::Pack { expr: body, .. } => {
5978            expr_contains_indirect_apply(body, functions)
5979        }
5980        runtime::ExprKind::Apply { callee, arg, .. } => {
5981            expr_contains_indirect_apply(callee, functions)
5982                || expr_contains_indirect_apply(arg, functions)
5983        }
5984        runtime::ExprKind::If {
5985            cond,
5986            then_branch,
5987            else_branch,
5988            ..
5989        } => {
5990            expr_contains_indirect_apply(cond, functions)
5991                || expr_contains_indirect_apply(then_branch, functions)
5992                || expr_contains_indirect_apply(else_branch, functions)
5993        }
5994        runtime::ExprKind::Tuple(items) => items
5995            .iter()
5996            .any(|item| expr_contains_indirect_apply(item, functions)),
5997        runtime::ExprKind::Record { fields, spread } => {
5998            fields
5999                .iter()
6000                .any(|field| expr_contains_indirect_apply(&field.value, functions))
6001                || spread.as_ref().is_some_and(|spread| match spread {
6002                    runtime::RecordSpreadExpr::Head(expr)
6003                    | runtime::RecordSpreadExpr::Tail(expr) => {
6004                        expr_contains_indirect_apply(expr, functions)
6005                    }
6006                })
6007        }
6008        runtime::ExprKind::Variant {
6009            value: Some(value), ..
6010        }
6011        | runtime::ExprKind::Select { base: value, .. } => {
6012            expr_contains_indirect_apply(value, functions)
6013        }
6014        runtime::ExprKind::Match {
6015            scrutinee, arms, ..
6016        } => {
6017            expr_contains_indirect_apply(scrutinee, functions)
6018                || arms.iter().any(|arm| {
6019                    arm.guard
6020                        .as_ref()
6021                        .is_some_and(|guard| expr_contains_indirect_apply(guard, functions))
6022                        || expr_contains_indirect_apply(&arm.body, functions)
6023                })
6024        }
6025        runtime::ExprKind::Block { stmts, tail } => {
6026            stmts.iter().any(|stmt| match stmt {
6027                runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
6028                    expr_contains_indirect_apply(value, functions)
6029                }
6030                runtime::Stmt::Module { body, .. } => expr_contains_indirect_apply(body, functions),
6031            }) || tail
6032                .as_ref()
6033                .is_some_and(|tail| expr_contains_indirect_apply(tail, functions))
6034        }
6035        runtime::ExprKind::Handle { body, arms, .. } => {
6036            expr_contains_indirect_apply(body, functions)
6037                || arms.iter().any(|arm| {
6038                    arm.guard
6039                        .as_ref()
6040                        .is_some_and(|guard| expr_contains_indirect_apply(guard, functions))
6041                        || expr_contains_indirect_apply(&arm.body, functions)
6042                })
6043        }
6044        runtime::ExprKind::Var(_)
6045        | runtime::ExprKind::EffectOp(_)
6046        | runtime::ExprKind::PrimitiveOp(_)
6047        | runtime::ExprKind::Lit(_)
6048        | runtime::ExprKind::Variant { value: None, .. }
6049        | runtime::ExprKind::PeekId
6050        | runtime::ExprKind::FindId { .. } => false,
6051    }
6052}
6053
6054fn expr_uses_path(expr: &runtime::Expr, path: &typed_ir::Path) -> bool {
6055    match &expr.kind {
6056        runtime::ExprKind::Var(candidate) => candidate == path,
6057        runtime::ExprKind::Lambda { body, .. }
6058        | runtime::ExprKind::Thunk { expr: body, .. }
6059        | runtime::ExprKind::LocalPushId { body, .. }
6060        | runtime::ExprKind::BindHere { expr: body }
6061        | runtime::ExprKind::AddId { thunk: body, .. }
6062        | runtime::ExprKind::Coerce { expr: body, .. }
6063        | runtime::ExprKind::Pack { expr: body, .. } => expr_uses_path(body, path),
6064        runtime::ExprKind::Apply { callee, arg, .. } => {
6065            expr_uses_path(callee, path) || expr_uses_path(arg, path)
6066        }
6067        runtime::ExprKind::If {
6068            cond,
6069            then_branch,
6070            else_branch,
6071            ..
6072        } => {
6073            expr_uses_path(cond, path)
6074                || expr_uses_path(then_branch, path)
6075                || expr_uses_path(else_branch, path)
6076        }
6077        runtime::ExprKind::Tuple(items) => items.iter().any(|item| expr_uses_path(item, path)),
6078        runtime::ExprKind::Record { fields, spread } => {
6079            fields
6080                .iter()
6081                .any(|field| expr_uses_path(&field.value, path))
6082                || spread.as_ref().is_some_and(|spread| match spread {
6083                    runtime::RecordSpreadExpr::Head(expr)
6084                    | runtime::RecordSpreadExpr::Tail(expr) => expr_uses_path(expr, path),
6085                })
6086        }
6087        runtime::ExprKind::Variant {
6088            value: Some(value), ..
6089        }
6090        | runtime::ExprKind::Select { base: value, .. } => expr_uses_path(value, path),
6091        runtime::ExprKind::Match {
6092            scrutinee, arms, ..
6093        } => {
6094            expr_uses_path(scrutinee, path)
6095                || arms.iter().any(|arm| {
6096                    arm.guard
6097                        .as_ref()
6098                        .is_some_and(|guard| expr_uses_path(guard, path))
6099                        || expr_uses_path(&arm.body, path)
6100                })
6101        }
6102        runtime::ExprKind::Block { stmts, tail } => {
6103            stmts.iter().any(|stmt| match stmt {
6104                runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
6105                    expr_uses_path(value, path)
6106                }
6107                runtime::Stmt::Module { body, .. } => expr_uses_path(body, path),
6108            }) || tail.as_ref().is_some_and(|tail| expr_uses_path(tail, path))
6109        }
6110        runtime::ExprKind::Handle { body, arms, .. } => {
6111            expr_uses_path(body, path)
6112                || arms.iter().any(|arm| {
6113                    arm.guard
6114                        .as_ref()
6115                        .is_some_and(|guard| expr_uses_path(guard, path))
6116                        || expr_uses_path(&arm.body, path)
6117                })
6118        }
6119        runtime::ExprKind::EffectOp(_)
6120        | runtime::ExprKind::PrimitiveOp(_)
6121        | runtime::ExprKind::Lit(_)
6122        | runtime::ExprKind::Variant { value: None, .. }
6123        | runtime::ExprKind::PeekId
6124        | runtime::ExprKind::FindId { .. } => false,
6125    }
6126}
6127
6128fn matches_any_effect(expected: &[typed_ir::Path], actual: &typed_ir::Path) -> bool {
6129    expected
6130        .iter()
6131        .any(|expected| effect_matches(expected, actual))
6132}
6133
6134fn record_spread_pattern(
6135    spread: Option<&runtime::RecordSpreadPattern>,
6136) -> Option<&runtime::Pattern> {
6137    match spread {
6138        Some(runtime::RecordSpreadPattern::Head(pattern))
6139        | Some(runtime::RecordSpreadPattern::Tail(pattern)) => Some(pattern),
6140        None => None,
6141    }
6142}
6143
6144fn handled_effects_compatible(
6145    expected: &[typed_ir::Path],
6146    left: &typed_ir::Path,
6147    right: &typed_ir::Path,
6148) -> bool {
6149    left == right || matches_any_effect(expected, left) || matches_any_effect(expected, right)
6150}
6151
6152fn join_handled_effect(
6153    joined: &mut Option<typed_ir::Path>,
6154    expected: &[typed_ir::Path],
6155    effect: typed_ir::Path,
6156) -> CpsLowerResult<()> {
6157    if let Some(previous) = joined {
6158        if !handled_effects_compatible(expected, previous, &effect) {
6159            return Err(CpsLowerError::UnsupportedExpr {
6160                kind: "handler effect mismatch",
6161            });
6162        }
6163    } else {
6164        *joined = Some(effect);
6165    }
6166    Ok(())
6167}
6168
6169fn default_expected_effect(expected: &[typed_ir::Path]) -> typed_ir::Path {
6170    expected.first().cloned().unwrap_or_default()
6171}
6172
6173fn dynamic_handler_id() -> CpsHandlerId {
6174    CpsHandlerId(usize::MAX)
6175}
6176
6177fn body_is_thunk_value(expr: &runtime::Expr) -> bool {
6178    matches!(expr.ty, runtime::Type::Thunk { .. })
6179        && !matches!(expr.kind, runtime::ExprKind::Thunk { .. })
6180}
6181
6182struct ContinuationBuilder {
6183    id: CpsContinuationId,
6184    params: Vec<CpsValueId>,
6185    captures: Vec<CpsValueId>,
6186    stmts: Vec<CpsStmt>,
6187    terminator: Option<CpsTerminator>,
6188}
6189
6190impl ContinuationBuilder {
6191    fn new(id: CpsContinuationId, params: Vec<CpsValueId>) -> Self {
6192        Self {
6193            id,
6194            params,
6195            captures: Vec::new(),
6196            stmts: Vec::new(),
6197            terminator: None,
6198        }
6199    }
6200}
6201
6202fn first_known_thunk_force_with_rest(stmts: &[CpsStmt]) -> Option<usize> {
6203    let mut known_thunks = HashSet::new();
6204    for (index, stmt) in stmts.iter().enumerate() {
6205        match stmt {
6206            CpsStmt::MakeThunk { dest, .. }
6207            | CpsStmt::MakeRecursiveClosure { dest, .. }
6208            | CpsStmt::AddThunkBoundary { dest, .. } => {
6209                if matches!(
6210                    stmt,
6211                    CpsStmt::MakeThunk { .. } | CpsStmt::AddThunkBoundary { .. }
6212                ) {
6213                    known_thunks.insert(*dest);
6214                }
6215            }
6216            CpsStmt::ForceThunk { thunk, .. } if known_thunks.contains(thunk) => {
6217                return Some(index);
6218            }
6219            _ => {}
6220        }
6221    }
6222    None
6223}
6224
6225fn collect_lambda_params(expr: &runtime::Expr) -> (Vec<typed_ir::Name>, &runtime::Expr) {
6226    let mut params = Vec::new();
6227    let mut current = expr;
6228    while let runtime::ExprKind::Lambda { param, body, .. } = &current.kind {
6229        params.push(param.clone());
6230        current = body;
6231    }
6232    (params, current)
6233}
6234
6235fn collect_callable_params(expr: &runtime::Expr) -> (Vec<typed_ir::Name>, runtime::Expr) {
6236    let (mut params, body) = collect_lambda_params(expr);
6237    let mut body = body.clone();
6238    while let runtime::ExprKind::Block {
6239        stmts,
6240        tail: Some(tail),
6241    } = &body.kind
6242    {
6243        let (tail_params, tail_body) = collect_lambda_params(tail);
6244        if tail_params.is_empty() {
6245            break;
6246        }
6247        params.extend(tail_params);
6248        body = runtime::Expr::typed(
6249            runtime::ExprKind::Block {
6250                stmts: stmts.clone(),
6251                tail: Some(Box::new(tail_body.clone())),
6252            },
6253            body.ty.clone(),
6254        );
6255    }
6256    (params, body)
6257}
6258
6259fn recursive_lambda_let<'a>(
6260    pattern: &'a runtime::Pattern,
6261    value: &'a runtime::Expr,
6262) -> Option<(&'a typed_ir::Name, &'a typed_ir::Name, &'a runtime::Expr)> {
6263    let runtime::Pattern::Bind { name, .. } = pattern else {
6264        return None;
6265    };
6266    let runtime::ExprKind::Lambda { param, body, .. } = &value.kind else {
6267        return None;
6268    };
6269    let self_path = typed_ir::Path::from_name(name.clone());
6270    expr_uses_path(body, &self_path).then_some((name, param, body.as_ref()))
6271}
6272
6273fn lower_literal(lit: &typed_ir::Lit) -> CpsLiteral {
6274    match lit {
6275        typed_ir::Lit::Int(value) => CpsLiteral::Int(value.clone()),
6276        typed_ir::Lit::Float(value) => CpsLiteral::Float(value.clone()),
6277        typed_ir::Lit::String(value) => CpsLiteral::String(value.clone()),
6278        typed_ir::Lit::Bool(value) => CpsLiteral::Bool(*value),
6279        typed_ir::Lit::Unit => CpsLiteral::Unit,
6280    }
6281}
6282
6283fn primitive_apply(expr: &runtime::Expr) -> Option<(typed_ir::PrimitiveOp, Vec<&runtime::Expr>)> {
6284    let mut args = Vec::new();
6285    let mut current = expr;
6286    loop {
6287        current = transparent_effect_expr(current);
6288        match &current.kind {
6289            runtime::ExprKind::Apply { callee, arg, .. } => {
6290                args.push(arg.as_ref());
6291                current = callee;
6292            }
6293            _ => break,
6294        }
6295    }
6296    let runtime::ExprKind::PrimitiveOp(op) = &current.kind else {
6297        return None;
6298    };
6299    args.reverse();
6300    Some((*op, args))
6301}
6302
6303#[derive(Debug, Clone)]
6304struct CpsEffectApply<'a> {
6305    effect: typed_ir::Path,
6306    payload: &'a runtime::Expr,
6307    blocked: Option<runtime::EffectIdRef>,
6308}
6309
6310fn effect_apply(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6311    let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
6312        return None;
6313    };
6314    let callee = transparent_effect_expr(callee);
6315    let effect = match &callee.kind {
6316        runtime::ExprKind::EffectOp(effect) => effect,
6317        runtime::ExprKind::Var(path) if debug_role_method_path(path) => path,
6318        _ => return None,
6319    };
6320    Some(CpsEffectApply {
6321        effect: effect.clone(),
6322        payload: arg.as_ref(),
6323        blocked: None,
6324    })
6325}
6326
6327fn effect_apply_nested(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6328    if let Some(inner) = handle_body_execution_inner(expr) {
6329        return effect_apply_nested(inner);
6330    }
6331    let mut current = expr;
6332    let mut blocked = None;
6333    loop {
6334        match &current.kind {
6335            runtime::ExprKind::BindHere { expr }
6336            | runtime::ExprKind::Coerce { expr, .. }
6337            | runtime::ExprKind::Pack { expr, .. } => current = expr,
6338            runtime::ExprKind::AddId {
6339                id, allowed, thunk, ..
6340            } => {
6341                let request = effect_apply_nested(thunk)?;
6342                if blocked.is_none() && !effect_allowed_by_type(allowed, &request.effect) {
6343                    blocked = Some(*id);
6344                }
6345                return Some(CpsEffectApply {
6346                    blocked: blocked.or(request.blocked),
6347                    ..request
6348                });
6349            }
6350            _ => {
6351                let mut request = effect_apply(current)?;
6352                request.blocked = blocked.or(request.blocked);
6353                return Some(request);
6354            }
6355        }
6356    }
6357}
6358
6359fn effect_apply_body_request(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6360    match &expr.kind {
6361        runtime::ExprKind::BindHere { .. }
6362        | runtime::ExprKind::AddId { .. }
6363        | runtime::ExprKind::Coerce { .. }
6364        | runtime::ExprKind::Pack { .. } => effect_apply_nested(expr),
6365        _ => effect_apply(expr),
6366    }
6367}
6368
6369fn transparent_effect_expr(expr: &runtime::Expr) -> &runtime::Expr {
6370    let mut current = expr;
6371    loop {
6372        match &current.kind {
6373            runtime::ExprKind::BindHere { expr }
6374            | runtime::ExprKind::Coerce { expr, .. }
6375            | runtime::ExprKind::Pack { expr, .. }
6376            | runtime::ExprKind::AddId { thunk: expr, .. } => current = expr,
6377            _ => return current,
6378        }
6379    }
6380}
6381
6382fn effect_allowed_by_type(allowed: &typed_ir::Type, effect: &typed_ir::Path) -> bool {
6383    match allowed {
6384        typed_ir::Type::Any => true,
6385        typed_ir::Type::Never => false,
6386        typed_ir::Type::Named { path, .. } => effect_path_matches_allowed(path, effect),
6387        typed_ir::Type::Row { items, tail } => {
6388            items
6389                .iter()
6390                .any(|item| effect_allowed_by_type(item, effect))
6391                || matches!(tail.as_ref(), typed_ir::Type::Any)
6392        }
6393        _ => false,
6394    }
6395}
6396
6397fn effect_path_matches_allowed(allowed: &typed_ir::Path, effect: &typed_ir::Path) -> bool {
6398    if effect.segments.starts_with(&allowed.segments) {
6399        return true;
6400    }
6401    if allowed.segments.len() > 1
6402        && effect.segments.len() == allowed.segments.len()
6403        && effect.segments[..effect.segments.len() - 1]
6404            == allowed.segments[..allowed.segments.len() - 1]
6405        && effect_segment_matches_allowed(
6406            &allowed.segments[allowed.segments.len() - 1],
6407            &effect.segments[effect.segments.len() - 1],
6408        )
6409    {
6410        return true;
6411    }
6412    effect
6413        .segments
6414        .iter()
6415        .enumerate()
6416        .skip(1)
6417        .any(|(index, _)| effect.segments[index..].starts_with(&allowed.segments))
6418}
6419
6420fn effect_segment_matches_allowed(allowed: &typed_ir::Name, effect: &typed_ir::Name) -> bool {
6421    allowed == effect
6422        || effect
6423            .0
6424            .strip_suffix("#effect")
6425            .is_some_and(|base| base == allowed.0)
6426}
6427
6428fn handle_body_execution_inner(expr: &runtime::Expr) -> Option<&runtime::Expr> {
6429    // VM handle evaluation runs a thunk-valued body inside the handler boundary.
6430    // Treat only the whole BindHere* -> Thunk wrapper as that execution
6431    // marker. AddId is a real effect boundary; stripping it here would hide
6432    // the guard from effect_apply_nested and let local handlers recapture a
6433    // callback effect.
6434    let mut current = expr;
6435    loop {
6436        match &current.kind {
6437            runtime::ExprKind::BindHere { expr }
6438            | runtime::ExprKind::Coerce { expr, .. }
6439            | runtime::ExprKind::Pack { expr, .. } => current = expr,
6440            _ => break,
6441        }
6442    }
6443    let runtime::ExprKind::Thunk { expr, .. } = &current.kind else {
6444        return None;
6445    };
6446    let mut inner = expr.as_ref();
6447    loop {
6448        match &inner.kind {
6449            runtime::ExprKind::BindHere { expr }
6450            | runtime::ExprKind::Coerce { expr, .. }
6451            | runtime::ExprKind::Pack { expr, .. } => inner = expr,
6452            _ => break,
6453        }
6454    }
6455    Some(inner)
6456}
6457
6458fn handler_body_plain_value_inner(expr: &runtime::Expr) -> Option<&runtime::Expr> {
6459    let inner = handle_body_execution_inner(expr)?;
6460    match inner.kind {
6461        runtime::ExprKind::Var(_) | runtime::ExprKind::Lit(_) => Some(inner),
6462        _ => None,
6463    }
6464}
6465
6466fn direct_apply<'expr, 'functions>(
6467    expr: &'expr runtime::Expr,
6468    functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6469) -> CpsLowerResult<Option<(String, &'functions FunctionInfo, Vec<&'expr runtime::Expr>)>> {
6470    let Some((_, target, args)) = direct_apply_path(expr, functions)? else {
6471        return Ok(None);
6472    };
6473    Ok(Some((target.name.clone(), target, args)))
6474}
6475
6476fn direct_call_result_needs_force(expr: &runtime::Expr, target: &FunctionInfo) -> bool {
6477    matches!(target.ret, runtime::Type::Thunk { .. })
6478        && !matches!(expr.ty, runtime::Type::Thunk { .. })
6479}
6480
6481fn bool_match(expr: &runtime::Expr) -> Option<(&runtime::Expr, &runtime::Expr, &runtime::Expr)> {
6482    let runtime::ExprKind::Match {
6483        scrutinee, arms, ..
6484    } = &expr.kind
6485    else {
6486        return None;
6487    };
6488    if arms.len() != 2 || arms.iter().any(|arm| arm.guard.is_some()) {
6489        return None;
6490    }
6491    let mut then_branch = None;
6492    let mut else_branch = None;
6493    for arm in arms {
6494        match &arm.pattern {
6495            runtime::Pattern::Lit {
6496                lit: typed_ir::Lit::Bool(true),
6497                ..
6498            } => then_branch = Some(&arm.body),
6499            runtime::Pattern::Lit {
6500                lit: typed_ir::Lit::Bool(false),
6501                ..
6502            } => else_branch = Some(&arm.body),
6503            _ => return None,
6504        }
6505    }
6506    Some((scrutinee, then_branch?, else_branch?))
6507}
6508
6509/// Conservatively report whether a function-typed value may perform an
6510/// effect when applied. Used by lower_handled_block to decide between
6511/// EffectfulApply (terminator, captures caller rest) and ApplyClosure
6512/// (stmt, synchronous). Unknown / polymorphic types are treated as
6513/// effectful so we don't lose caller rest at a callback boundary
6514/// (e.g. std::fold's `f z x`).
6515fn callee_type_may_perform(ty: &runtime::Type) -> bool {
6516    match ty {
6517        runtime::Type::Fun { ret, .. } => {
6518            matches!(ret.as_ref(), runtime::Type::Thunk { .. }) || callee_type_may_perform(ret)
6519        }
6520        runtime::Type::Thunk { .. } => true,
6521        runtime::Type::Unknown => true,
6522        runtime::Type::Core(_) => false,
6523    }
6524}
6525
6526fn type_is_callable_after_force(ty: &runtime::Type) -> bool {
6527    matches!(callable_type_after_force(ty), runtime::Type::Fun { .. })
6528}
6529
6530fn runtime_type_is_unit_value(ty: &runtime::Type) -> bool {
6531    matches!(
6532        ty,
6533        runtime::Type::Core(typed_ir::Type::Named { path, args })
6534            if args.is_empty()
6535                && path.segments.len() == 1
6536                && path.segments[0].0 == "unit"
6537    )
6538}
6539
6540fn runtime_type_is_bool_value(ty: &runtime::Type) -> bool {
6541    match ty {
6542        runtime::Type::Thunk { value, .. } => runtime_type_is_bool_value(value),
6543        runtime::Type::Core(typed_ir::Type::Named { path, args }) => {
6544            args.is_empty() && path.segments.len() == 1 && path.segments[0].0 == "bool"
6545        }
6546        runtime::Type::Unknown | runtime::Type::Core(_) | runtime::Type::Fun { .. } => false,
6547    }
6548}
6549
6550fn callable_type_after_force(ty: &runtime::Type) -> &runtime::Type {
6551    match ty {
6552        runtime::Type::Thunk { value, .. } => value,
6553        _ => ty,
6554    }
6555}
6556
6557fn direct_apply_path<'expr, 'functions>(
6558    expr: &'expr runtime::Expr,
6559    functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6560) -> CpsLowerResult<
6561    Option<(
6562        &'expr typed_ir::Path,
6563        &'functions FunctionInfo,
6564        Vec<&'expr runtime::Expr>,
6565    )>,
6566> {
6567    let mut args = Vec::new();
6568    let mut current = expr;
6569    loop {
6570        current = transparent_effect_expr(current);
6571        match &current.kind {
6572            runtime::ExprKind::Apply { callee, arg, .. } => {
6573                args.push(arg.as_ref());
6574                current = callee;
6575            }
6576            _ => break,
6577        }
6578    }
6579    let runtime::ExprKind::Var(path) = &current.kind else {
6580        return Ok(None);
6581    };
6582    let Some(target) = functions.get(path) else {
6583        return Ok(None);
6584    };
6585    if args.is_empty() {
6586        return Ok(None);
6587    }
6588    if args.len() < target.arity {
6589        return Ok(None);
6590    }
6591    if args.len() > target.arity {
6592        return Ok(None);
6593    }
6594    args.reverse();
6595    Ok(Some((path, target, args)))
6596}
6597
6598fn partial_direct_apply_path<'expr, 'functions>(
6599    expr: &'expr runtime::Expr,
6600    functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6601) -> CpsLowerResult<
6602    Option<(
6603        &'expr typed_ir::Path,
6604        &'functions FunctionInfo,
6605        Vec<&'expr runtime::Expr>,
6606    )>,
6607> {
6608    let mut args = Vec::new();
6609    let mut current = expr;
6610    loop {
6611        current = transparent_effect_expr(current);
6612        match &current.kind {
6613            runtime::ExprKind::Apply { callee, arg, .. } => {
6614                args.push(arg.as_ref());
6615                current = callee;
6616            }
6617            _ => break,
6618        }
6619    }
6620    let runtime::ExprKind::Var(path) = &current.kind else {
6621        return Ok(None);
6622    };
6623    let Some(target) = functions.get(path) else {
6624        return Ok(None);
6625    };
6626    if args.is_empty() || args.len() >= target.arity {
6627        return Ok(None);
6628    }
6629    args.reverse();
6630    Ok(Some((path, target, args)))
6631}
6632
6633fn primitive_arity(op: typed_ir::PrimitiveOp) -> usize {
6634    use typed_ir::PrimitiveOp;
6635    match op {
6636        PrimitiveOp::BoolNot
6637        | PrimitiveOp::ListEmpty
6638        | PrimitiveOp::ListSingleton
6639        | PrimitiveOp::ListLen
6640        | PrimitiveOp::ListViewRaw
6641        | PrimitiveOp::StringLen
6642        | PrimitiveOp::StringToBytes
6643        | PrimitiveOp::BytesLen
6644        | PrimitiveOp::BytesToUtf8Raw
6645        | PrimitiveOp::BytesToPath
6646        | PrimitiveOp::PathToBytes
6647        | PrimitiveOp::IntToString
6648        | PrimitiveOp::IntToHex
6649        | PrimitiveOp::IntToUpperHex
6650        | PrimitiveOp::FloatToString
6651        | PrimitiveOp::BoolToString => 1,
6652        PrimitiveOp::BoolEq
6653        | PrimitiveOp::ListMerge
6654        | PrimitiveOp::ListIndex
6655        | PrimitiveOp::ListIndexRange
6656        | PrimitiveOp::StringIndex
6657        | PrimitiveOp::StringIndexRange
6658        | PrimitiveOp::IntAdd
6659        | PrimitiveOp::IntSub
6660        | PrimitiveOp::IntMul
6661        | PrimitiveOp::IntDiv
6662        | PrimitiveOp::IntEq
6663        | PrimitiveOp::IntLt
6664        | PrimitiveOp::IntLe
6665        | PrimitiveOp::IntGt
6666        | PrimitiveOp::IntGe
6667        | PrimitiveOp::FloatAdd
6668        | PrimitiveOp::FloatSub
6669        | PrimitiveOp::FloatMul
6670        | PrimitiveOp::FloatDiv
6671        | PrimitiveOp::FloatEq
6672        | PrimitiveOp::FloatLt
6673        | PrimitiveOp::FloatLe
6674        | PrimitiveOp::FloatGt
6675        | PrimitiveOp::FloatGe
6676        | PrimitiveOp::StringEq
6677        | PrimitiveOp::StringConcat
6678        | PrimitiveOp::BytesEq
6679        | PrimitiveOp::BytesConcat
6680        | PrimitiveOp::BytesIndex
6681        | PrimitiveOp::BytesIndexRange => 2,
6682        PrimitiveOp::ListSplice
6683        | PrimitiveOp::ListIndexRangeRaw
6684        | PrimitiveOp::StringSplice
6685        | PrimitiveOp::StringIndexRangeRaw => 3,
6686        PrimitiveOp::ListSpliceRaw | PrimitiveOp::StringSpliceRaw => 4,
6687    }
6688}
6689
6690fn path_name(path: &typed_ir::Path) -> String {
6691    path.segments
6692        .iter()
6693        .map(|segment| segment.0.as_str())
6694        .collect::<Vec<_>>()
6695        .join("::")
6696}
6697
6698fn debug_role_method_path(path: &typed_ir::Path) -> bool {
6699    let [std, prelude, role, method] = path.segments.as_slice() else {
6700        return false;
6701    };
6702    std.0 == "std" && prelude.0 == "prelude" && role.0 == "Debug" && method.0 == "debug"
6703}
6704
6705fn throw_role_method_path(path: &typed_ir::Path) -> bool {
6706    let [std, module, role, method] = path.segments.as_slice() else {
6707        return false;
6708    };
6709    std.0 == "std" && module.0 == "error" && role.0 == "Throw" && method.0 == "throw"
6710}
6711
6712fn fail_prefix_path(path: &typed_ir::Path) -> bool {
6713    let [std, prelude, op] = path.segments.as_slice() else {
6714        return false;
6715    };
6716    std.0 == "std" && prelude.0 == "prelude" && op.0.starts_with("#op:prefix:fail")
6717}
6718
6719#[cfg(test)]
6720mod tests {
6721    use crate::cps_eval::eval_cps_module;
6722    use crate::cps_repr::{eval_cps_repr_module, lower_cps_repr_module};
6723    use crate::cps_validate::validate_cps_module;
6724
6725    use super::*;
6726
6727    fn unknown_lit(lit: typed_ir::Lit) -> runtime::Expr {
6728        runtime::Expr::typed(runtime::ExprKind::Lit(lit), runtime::Type::unknown())
6729    }
6730
6731    fn primitive(op: typed_ir::PrimitiveOp) -> runtime::Expr {
6732        runtime::Expr::typed(runtime::ExprKind::PrimitiveOp(op), runtime::Type::unknown())
6733    }
6734
6735    fn apply(callee: runtime::Expr, arg: runtime::Expr) -> runtime::Expr {
6736        runtime::Expr::typed(
6737            runtime::ExprKind::Apply {
6738                callee: Box::new(callee),
6739                arg: Box::new(arg),
6740                evidence: None,
6741                instantiation: None,
6742            },
6743            runtime::Type::unknown(),
6744        )
6745    }
6746
6747    fn if_expr(
6748        cond: runtime::Expr,
6749        then_branch: runtime::Expr,
6750        else_branch: runtime::Expr,
6751    ) -> runtime::Expr {
6752        runtime::Expr::typed(
6753            runtime::ExprKind::If {
6754                cond: Box::new(cond),
6755                then_branch: Box::new(then_branch),
6756                else_branch: Box::new(else_branch),
6757                evidence: None,
6758            },
6759            runtime::Type::unknown(),
6760        )
6761    }
6762
6763    fn var(name: &str) -> runtime::Expr {
6764        runtime::Expr::typed(
6765            runtime::ExprKind::Var(typed_ir::Path::from_name(typed_ir::Name(name.to_string()))),
6766            runtime::Type::unknown(),
6767        )
6768    }
6769
6770    fn effect_op(name: &str) -> runtime::Expr {
6771        runtime::Expr::typed(
6772            runtime::ExprKind::EffectOp(typed_ir::Path::from_name(typed_ir::Name(
6773                name.to_string(),
6774            ))),
6775            runtime::Type::unknown(),
6776        )
6777    }
6778
6779    fn effect_op_path(path: typed_ir::Path) -> runtime::Expr {
6780        runtime::Expr::typed(runtime::ExprKind::EffectOp(path), runtime::Type::unknown())
6781    }
6782
6783    fn bind_pattern(name: &str) -> runtime::Pattern {
6784        runtime::Pattern::Bind {
6785            name: typed_ir::Name(name.to_string()),
6786            ty: runtime::Type::unknown(),
6787        }
6788    }
6789
6790    fn handle_once(
6791        effect: &str,
6792        payload: &str,
6793        resume: &str,
6794        body: runtime::Expr,
6795        arm_body: runtime::Expr,
6796    ) -> runtime::Expr {
6797        let effect = typed_ir::Path::from_name(typed_ir::Name(effect.to_string()));
6798        runtime::Expr::typed(
6799            runtime::ExprKind::Handle {
6800                body: Box::new(body),
6801                arms: vec![runtime::HandleArm {
6802                    effect: effect.clone(),
6803                    payload: bind_pattern(payload),
6804                    resume: Some(runtime::ResumeBinding {
6805                        name: typed_ir::Name(resume.to_string()),
6806                        ty: runtime::Type::unknown(),
6807                    }),
6808                    guard: None,
6809                    body: arm_body,
6810                }],
6811                evidence: runtime::JoinEvidence {
6812                    result: typed_ir::Type::Unknown,
6813                },
6814                handler: runtime::HandleEffect {
6815                    consumes: vec![effect],
6816                    residual_before: None,
6817                    residual_after: None,
6818                },
6819            },
6820            runtime::Type::unknown(),
6821        )
6822    }
6823
6824    fn handle_once_with_value(
6825        effect: &str,
6826        payload: &str,
6827        resume: &str,
6828        body: runtime::Expr,
6829        arm_body: runtime::Expr,
6830        value_payload: &str,
6831        value_body: runtime::Expr,
6832    ) -> runtime::Expr {
6833        let effect = typed_ir::Path::from_name(typed_ir::Name(effect.to_string()));
6834        runtime::Expr::typed(
6835            runtime::ExprKind::Handle {
6836                body: Box::new(body),
6837                arms: vec![
6838                    runtime::HandleArm {
6839                        effect: effect.clone(),
6840                        payload: bind_pattern(payload),
6841                        resume: Some(runtime::ResumeBinding {
6842                            name: typed_ir::Name(resume.to_string()),
6843                            ty: runtime::Type::unknown(),
6844                        }),
6845                        guard: None,
6846                        body: arm_body,
6847                    },
6848                    runtime::HandleArm {
6849                        effect: typed_ir::Path::default(),
6850                        payload: bind_pattern(value_payload),
6851                        resume: None,
6852                        guard: None,
6853                        body: value_body,
6854                    },
6855                ],
6856                evidence: runtime::JoinEvidence {
6857                    result: typed_ir::Type::Unknown,
6858                },
6859                handler: runtime::HandleEffect {
6860                    consumes: vec![effect],
6861                    residual_before: None,
6862                    residual_after: None,
6863                },
6864            },
6865            runtime::Type::unknown(),
6866        )
6867    }
6868
6869    fn handle_value(
6870        body: runtime::Expr,
6871        value_payload: &str,
6872        value_body: runtime::Expr,
6873    ) -> runtime::Expr {
6874        runtime::Expr::typed(
6875            runtime::ExprKind::Handle {
6876                body: Box::new(body),
6877                arms: vec![runtime::HandleArm {
6878                    effect: typed_ir::Path::default(),
6879                    payload: bind_pattern(value_payload),
6880                    resume: None,
6881                    guard: None,
6882                    body: value_body,
6883                }],
6884                evidence: runtime::JoinEvidence {
6885                    result: typed_ir::Type::Unknown,
6886                },
6887                handler: runtime::HandleEffect {
6888                    consumes: Vec::new(),
6889                    residual_before: None,
6890                    residual_after: None,
6891                },
6892            },
6893            runtime::Type::unknown(),
6894        )
6895    }
6896
6897    fn handle_once_expr(
6898        effect: typed_ir::Path,
6899        payload: &str,
6900        resume: &str,
6901        body: runtime::Expr,
6902        arm_body: runtime::Expr,
6903    ) -> runtime::Expr {
6904        runtime::Expr::typed(
6905            runtime::ExprKind::Handle {
6906                body: Box::new(body),
6907                arms: vec![runtime::HandleArm {
6908                    effect: effect.clone(),
6909                    payload: bind_pattern(payload),
6910                    resume: Some(runtime::ResumeBinding {
6911                        name: typed_ir::Name(resume.to_string()),
6912                        ty: runtime::Type::unknown(),
6913                    }),
6914                    guard: None,
6915                    body: arm_body,
6916                }],
6917                evidence: runtime::JoinEvidence {
6918                    result: typed_ir::Type::Unknown,
6919                },
6920                handler: runtime::HandleEffect {
6921                    consumes: vec![effect],
6922                    residual_before: None,
6923                    residual_after: None,
6924                },
6925            },
6926            runtime::Type::unknown(),
6927        )
6928    }
6929
6930    fn block(stmts: Vec<runtime::Stmt>, tail: runtime::Expr) -> runtime::Expr {
6931        runtime::Expr::typed(
6932            runtime::ExprKind::Block {
6933                stmts,
6934                tail: Some(Box::new(tail)),
6935            },
6936            runtime::Type::unknown(),
6937        )
6938    }
6939
6940    fn thunk(expr: runtime::Expr) -> runtime::Expr {
6941        runtime::Expr::typed(
6942            runtime::ExprKind::Thunk {
6943                effect: typed_ir::Type::Unknown,
6944                value: runtime::Type::unknown(),
6945                expr: Box::new(expr),
6946            },
6947            runtime::Type::unknown(),
6948        )
6949    }
6950
6951    fn bind_here(expr: runtime::Expr) -> runtime::Expr {
6952        runtime::Expr::typed(
6953            runtime::ExprKind::BindHere {
6954                expr: Box::new(expr),
6955            },
6956            runtime::Type::unknown(),
6957        )
6958    }
6959
6960    fn local_push_id(id: usize, body: runtime::Expr) -> runtime::Expr {
6961        runtime::Expr::typed(
6962            runtime::ExprKind::LocalPushId {
6963                id: runtime::EffectIdVar(id),
6964                body: Box::new(body),
6965            },
6966            runtime::Type::unknown(),
6967        )
6968    }
6969
6970    fn peek_id() -> runtime::Expr {
6971        runtime::Expr::typed(runtime::ExprKind::PeekId, runtime::Type::unknown())
6972    }
6973
6974    fn add_id(
6975        id: runtime::EffectIdRef,
6976        allowed: typed_ir::Type,
6977        thunk: runtime::Expr,
6978    ) -> runtime::Expr {
6979        add_id_with_active(id, allowed, false, thunk)
6980    }
6981
6982    fn add_id_with_active(
6983        id: runtime::EffectIdRef,
6984        allowed: typed_ir::Type,
6985        active: bool,
6986        thunk: runtime::Expr,
6987    ) -> runtime::Expr {
6988        runtime::Expr::typed(
6989            runtime::ExprKind::AddId {
6990                id,
6991                allowed,
6992                active,
6993                thunk: Box::new(thunk),
6994            },
6995            runtime::Type::unknown(),
6996        )
6997    }
6998
6999    fn lambda(param: &str, body: runtime::Expr) -> runtime::Expr {
7000        runtime::Expr::typed(
7001            runtime::ExprKind::Lambda {
7002                param: typed_ir::Name(param.to_string()),
7003                param_effect_annotation: None,
7004                param_function_allowed_effects: None,
7005                body: Box::new(body),
7006            },
7007            runtime::Type::unknown(),
7008        )
7009    }
7010
7011    fn binding(name: &str, body: runtime::Expr) -> runtime::Binding {
7012        runtime::Binding {
7013            name: typed_ir::Path::from_name(typed_ir::Name(name.to_string())),
7014            type_params: Vec::new(),
7015            scheme: typed_ir::Scheme {
7016                requirements: Vec::new(),
7017                body: typed_ir::Type::Unknown,
7018            },
7019            body,
7020        }
7021    }
7022
7023    fn effect_path(effect: &str, op: &str) -> typed_ir::Path {
7024        typed_ir::Path {
7025            segments: vec![
7026                typed_ir::Name(effect.to_string()),
7027                typed_ir::Name(op.to_string()),
7028            ],
7029        }
7030    }
7031
7032    fn module_with_root(expr: runtime::Expr) -> runtime::Module {
7033        module_with_bindings_and_root(Vec::new(), expr)
7034    }
7035
7036    fn module_with_bindings_and_root(
7037        bindings: Vec<runtime::Binding>,
7038        expr: runtime::Expr,
7039    ) -> runtime::Module {
7040        runtime::Module {
7041            path: typed_ir::Path::default(),
7042            bindings,
7043            root_exprs: vec![expr],
7044            roots: vec![runtime::Root::Expr(0)],
7045            role_impls: Vec::new(),
7046        }
7047    }
7048
7049    #[test]
7050    fn lowers_pure_int_add_to_multishot_cps() {
7051        let expr = apply(
7052            apply(
7053                primitive(typed_ir::PrimitiveOp::IntAdd),
7054                unknown_lit(typed_ir::Lit::Int("20".to_string())),
7055            ),
7056            unknown_lit(typed_ir::Lit::Int("22".to_string())),
7057        );
7058        let module = module_with_root(expr);
7059        let lowered = lower_cps_module(&module).expect("lowered");
7060
7061        validate_cps_module(&lowered).expect("valid CPS");
7062        assert_eq!(lowered.roots.len(), 1);
7063        assert_eq!(lowered.roots[0].continuations.len(), 1);
7064        assert_eq!(
7065            lowered.roots[0].continuations[0].shot_kind,
7066            CpsShotKind::MultiShot
7067        );
7068        assert_eq!(
7069            lowered.roots[0].continuations[0].stmts,
7070            vec![
7071                CpsStmt::Literal {
7072                    dest: CpsValueId(0),
7073                    literal: CpsLiteral::Int("20".to_string()),
7074                },
7075                CpsStmt::Literal {
7076                    dest: CpsValueId(1),
7077                    literal: CpsLiteral::Int("22".to_string()),
7078                },
7079                CpsStmt::Primitive {
7080                    dest: CpsValueId(2),
7081                    op: typed_ir::PrimitiveOp::IntAdd,
7082                    args: vec![CpsValueId(0), CpsValueId(1)],
7083                },
7084                // lower_root forces the return value when the static
7085                // return type is non-Thunk; ForceThunk is a no-op on
7086                // plain values.
7087                CpsStmt::ForceThunk {
7088                    dest: CpsValueId(3),
7089                    thunk: CpsValueId(2),
7090                },
7091            ]
7092        );
7093        assert_eq!(
7094            lowered.roots[0].continuations[0].terminator,
7095            CpsTerminator::Return(CpsValueId(3))
7096        );
7097    }
7098
7099    #[test]
7100    fn lowers_local_push_and_peek_id_to_guard_statements() {
7101        let module = module_with_root(local_push_id(0, peek_id()));
7102        let lowered = lower_cps_module(&module).expect("lowered");
7103
7104        validate_cps_module(&lowered).expect("valid CPS");
7105        let stmts = &lowered.roots[0].continuations[0].stmts;
7106        assert!(matches!(stmts[0], CpsStmt::FreshGuard { .. }));
7107        assert!(matches!(stmts[1], CpsStmt::PeekGuard { .. }));
7108        assert_eq!(
7109            eval_cps_module(&lowered).expect("evaluated"),
7110            vec![runtime::VmValue::EffectId(0)]
7111        );
7112    }
7113
7114    #[test]
7115    fn lowers_add_id_var_to_enclosing_local_push_guard() {
7116        let module = module_with_root(local_push_id(
7117            0,
7118            local_push_id(
7119                1,
7120                add_id(
7121                    runtime::EffectIdRef::Var(runtime::EffectIdVar(0)),
7122                    typed_ir::Type::Never,
7123                    thunk(unknown_lit(typed_ir::Lit::Int("7".to_string()))),
7124                ),
7125            ),
7126        ));
7127        let lowered = lower_cps_module(&module).expect("lowered");
7128
7129        validate_cps_module(&lowered).expect("valid CPS");
7130        let fresh_guards: HashMap<_, _> = lowered.roots[0]
7131            .continuations
7132            .iter()
7133            .flat_map(|continuation| continuation.stmts.iter())
7134            .filter_map(|stmt| match stmt {
7135                CpsStmt::FreshGuard { dest, var } => Some((*var, *dest)),
7136                _ => None,
7137            })
7138            .collect();
7139        let outer_guard = *fresh_guards
7140            .get(&runtime::EffectIdVar(0))
7141            .expect("outer fresh guard");
7142        let inner_guard = *fresh_guards
7143            .get(&runtime::EffectIdVar(1))
7144            .expect("inner fresh guard");
7145        assert_ne!(outer_guard, inner_guard);
7146        assert!(
7147            lowered.roots[0]
7148                .continuations
7149                .iter()
7150                .flat_map(|continuation| continuation.stmts.iter())
7151                .any(|stmt| matches!(stmt, CpsStmt::AddThunkBoundary { guard: found, .. } if *found == outer_guard))
7152        );
7153        assert_eq!(
7154            eval_cps_module(&lowered).expect("evaluated"),
7155            vec![runtime::VmValue::Int("7".to_string())]
7156        );
7157    }
7158
7159    #[test]
7160    fn lowers_add_id_blocked_effect_to_perform_blocked_guard() {
7161        let body = add_id(
7162            runtime::EffectIdRef::Peek,
7163            typed_ir::Type::Never,
7164            apply(
7165                effect_op("choose"),
7166                unknown_lit(typed_ir::Lit::Int("1".to_string())),
7167            ),
7168        );
7169        let module = module_with_root(handle_once("choose", "x", "k", body, var("x")));
7170        let lowered = lower_cps_module(&module).expect("lowered");
7171
7172        validate_cps_module(&lowered).expect("valid CPS");
7173        let perform = lowered.roots[0]
7174            .continuations
7175            .iter()
7176            .find_map(|continuation| match &continuation.terminator {
7177                CpsTerminator::Perform { blocked, .. } => *blocked,
7178                _ => None,
7179            })
7180            .expect("blocked perform guard");
7181        assert!(
7182            lowered.roots[0].continuations[0]
7183                .stmts
7184                .iter()
7185                .any(|stmt| matches!(stmt, CpsStmt::PeekGuard { dest } if *dest == perform))
7186        );
7187    }
7188
7189    #[test]
7190    fn active_add_id_blocks_inner_handler_during_thunk_force() {
7191        let perform = apply(
7192            effect_op("choose"),
7193            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7194        );
7195        let guarded_thunk = add_id_with_active(
7196            runtime::EffectIdRef::Peek,
7197            typed_ir::Type::Never,
7198            true,
7199            thunk(perform),
7200        );
7201        let inner = handle_once(
7202            "choose",
7203            "_x",
7204            "_k",
7205            bind_here(guarded_thunk),
7206            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7207        );
7208        let body = local_push_id(0, inner);
7209        let root = handle_once(
7210            "choose",
7211            "_x",
7212            "_k",
7213            body,
7214            unknown_lit(typed_ir::Lit::Int("20".to_string())),
7215        );
7216        let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7217
7218        validate_cps_module(&lowered).expect("valid CPS");
7219        assert!(matches!(
7220            eval_cps_module(&lowered),
7221            Err(crate::cps_eval::CpsEvalError::MissingHandler { .. })
7222        ));
7223        assert!(eval_cps_repr_module(&lower_cps_repr_module(&lowered)).is_err());
7224    }
7225
7226    #[test]
7227    fn active_add_id_var_blocks_inner_handler_during_callback_force() {
7228        let perform = apply(
7229            effect_op("choose"),
7230            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7231        );
7232        let guarded_thunk = add_id_with_active(
7233            runtime::EffectIdRef::Var(runtime::EffectIdVar(0)),
7234            typed_ir::Type::Never,
7235            true,
7236            thunk(perform),
7237        );
7238        let inner = handle_once(
7239            "choose",
7240            "_x",
7241            "_k",
7242            bind_here(guarded_thunk),
7243            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7244        );
7245        let body = local_push_id(0, inner);
7246        let root = handle_once(
7247            "choose",
7248            "_x",
7249            "_k",
7250            body,
7251            unknown_lit(typed_ir::Lit::Int("20".to_string())),
7252        );
7253        let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7254
7255        validate_cps_module(&lowered).expect("valid CPS");
7256        assert!(matches!(
7257            eval_cps_module(&lowered),
7258            Err(crate::cps_eval::CpsEvalError::MissingHandler { .. })
7259        ));
7260        assert!(eval_cps_repr_module(&lower_cps_repr_module(&lowered)).is_err());
7261    }
7262
7263    #[test]
7264    fn active_add_id_allows_matching_effect_for_inner_handler() {
7265        let perform = apply(
7266            effect_op("choose"),
7267            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7268        );
7269        let guarded_thunk = add_id_with_active(
7270            runtime::EffectIdRef::Peek,
7271            typed_ir::Type::Named {
7272                path: typed_ir::Path::from_name(typed_ir::Name("choose".to_string())),
7273                args: Vec::new(),
7274            },
7275            true,
7276            thunk(perform),
7277        );
7278        let inner = handle_once(
7279            "choose",
7280            "_x",
7281            "_k",
7282            bind_here(guarded_thunk),
7283            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7284        );
7285        let body = local_push_id(0, inner);
7286        let root = handle_once(
7287            "choose",
7288            "_x",
7289            "_k",
7290            body,
7291            unknown_lit(typed_ir::Lit::Int("20".to_string())),
7292        );
7293        let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7294
7295        validate_cps_module(&lowered).expect("valid CPS");
7296        assert_eq!(
7297            eval_cps_module(&lowered).expect("evaluated"),
7298            vec![runtime::VmValue::Int("10".to_string())]
7299        );
7300        assert_eq!(
7301            eval_cps_repr_module(&lower_cps_repr_module(&lowered)).expect("repr evaluated"),
7302            vec![runtime::VmValue::Int("10".to_string())]
7303        );
7304    }
7305
7306    #[test]
7307    fn skips_unreachable_non_function_binding() {
7308        let module = runtime::Module {
7309            path: typed_ir::Path::default(),
7310            bindings: vec![runtime::Binding {
7311                name: typed_ir::Path::from_name(typed_ir::Name("unused".to_string())),
7312                type_params: Vec::new(),
7313                scheme: typed_ir::Scheme {
7314                    requirements: Vec::new(),
7315                    body: typed_ir::Type::Unknown,
7316                },
7317                body: unknown_lit(typed_ir::Lit::Int("0".to_string())),
7318            }],
7319            root_exprs: vec![unknown_lit(typed_ir::Lit::Int("41".to_string()))],
7320            roots: vec![runtime::Root::Expr(0)],
7321            role_impls: Vec::new(),
7322        };
7323        let lowered = lower_cps_module(&module).expect("lowered");
7324
7325        assert!(lowered.functions.is_empty());
7326        assert_eq!(
7327            eval_cps_module(&lowered).expect("evaluated"),
7328            vec![runtime::VmValue::Int("41".to_string())]
7329        );
7330    }
7331
7332    #[test]
7333    fn inlines_thunk_handler_wrapper_call() {
7334        let effect = effect_path("sub", "return");
7335        let handler = binding(
7336            "run",
7337            lambda(
7338                "x",
7339                handle_once_expr(effect.clone(), "a", "_k", var("x"), thunk(var("a"))),
7340            ),
7341        );
7342        let root = apply(
7343            var("run"),
7344            thunk(apply(
7345                effect_op_path(effect),
7346                unknown_lit(typed_ir::Lit::Int("41".to_string())),
7347            )),
7348        );
7349        let module = module_with_bindings_and_root(vec![handler], root);
7350        let lowered = lower_cps_module(&module).expect("lowered");
7351
7352        validate_cps_module(&lowered).expect("valid CPS");
7353        assert!(lowered.functions.is_empty());
7354        assert_eq!(
7355            eval_cps_module(&lowered).expect("evaluated"),
7356            vec![runtime::VmValue::Int("41".to_string())]
7357        );
7358    }
7359
7360    #[test]
7361    fn lowers_if_to_multishot_continuation_graph() {
7362        let module = module_with_root(if_expr(
7363            unknown_lit(typed_ir::Lit::Bool(true)),
7364            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7365            unknown_lit(typed_ir::Lit::Int("2".to_string())),
7366        ));
7367        let lowered = lower_cps_module(&module).expect("lowered");
7368        let root = &lowered.roots[0];
7369
7370        validate_cps_module(&lowered).expect("valid CPS");
7371        assert_eq!(root.continuations.len(), 4);
7372        assert!(
7373            root.continuations
7374                .iter()
7375                .all(|continuation| continuation.shot_kind == CpsShotKind::MultiShot)
7376        );
7377        assert_eq!(
7378            root.continuations[0].terminator,
7379            CpsTerminator::Branch {
7380                cond: CpsValueId(0),
7381                then_cont: CpsContinuationId(1),
7382                else_cont: CpsContinuationId(2),
7383            }
7384        );
7385        assert_eq!(root.continuations[3].params, vec![CpsValueId(1)]);
7386        // lower_root force-emits a ForceThunk on the join-block's incoming
7387        // value before the Return, so the returned slot is the forced one
7388        // rather than the join parameter directly.
7389        assert_eq!(root.continuations[3].stmts.len(), 1);
7390        assert!(matches!(
7391            root.continuations[3].stmts[0],
7392            CpsStmt::ForceThunk {
7393                dest: CpsValueId(4),
7394                thunk: CpsValueId(1),
7395            }
7396        ));
7397        assert_eq!(
7398            root.continuations[3].terminator,
7399            CpsTerminator::Return(CpsValueId(4))
7400        );
7401    }
7402
7403    #[test]
7404    fn lowers_direct_call_to_cps() {
7405        let inc = binding(
7406            "inc",
7407            lambda(
7408                "x",
7409                apply(
7410                    apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
7411                    unknown_lit(typed_ir::Lit::Int("1".to_string())),
7412                ),
7413            ),
7414        );
7415        let root = apply(
7416            var("inc"),
7417            unknown_lit(typed_ir::Lit::Int("41".to_string())),
7418        );
7419        let module = module_with_bindings_and_root(vec![inc], root);
7420        let lowered = lower_cps_module(&module).expect("lowered");
7421
7422        validate_cps_module(&lowered).expect("valid CPS");
7423        assert_eq!(lowered.functions.len(), 1);
7424        assert_eq!(lowered.functions[0].name, "inc");
7425        assert_eq!(lowered.functions[0].params, vec![CpsValueId(0)]);
7426        assert_eq!(
7427            lowered.roots[0].continuations[0].stmts,
7428            vec![
7429                CpsStmt::Literal {
7430                    dest: CpsValueId(0),
7431                    literal: CpsLiteral::Int("41".to_string()),
7432                },
7433                // Unknown parameter expectations are forced before direct
7434                // calls. ForceThunk is a no-op for plain values and keeps
7435                // thunk-producing helpers on the same path.
7436                CpsStmt::ForceThunk {
7437                    dest: CpsValueId(1),
7438                    thunk: CpsValueId(0),
7439                },
7440                CpsStmt::DirectCall {
7441                    dest: CpsValueId(2),
7442                    target: "inc".to_string(),
7443                    args: vec![CpsValueId(1)],
7444                },
7445                // force_if_non_thunk_demand always emits a ForceThunk after
7446                // a direct call whose static result type is non-Thunk; it's
7447                // a runtime no-op on plain values but covers callees that
7448                // wrap their result in MakeThunk (e.g. effectful helpers).
7449                CpsStmt::ForceThunk {
7450                    dest: CpsValueId(3),
7451                    thunk: CpsValueId(2),
7452                },
7453                // lower_root also forces the final return value when the
7454                // root expression's type is non-Thunk, so the body's already-
7455                // forced direct-call result gets one more (no-op) force.
7456                CpsStmt::ForceThunk {
7457                    dest: CpsValueId(4),
7458                    thunk: CpsValueId(3),
7459                },
7460            ]
7461        );
7462    }
7463
7464    #[test]
7465    fn lowers_single_effect_handler_with_resumption() {
7466        let body = apply(
7467            effect_op("choose"),
7468            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7469        );
7470        let resume_x = apply(var("k"), var("x"));
7471        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7472        let arm_body = apply(
7473            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7474            resume_two,
7475        );
7476        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7477        let lowered = lower_cps_module(&module).expect("lowered");
7478        let root = &lowered.roots[0];
7479
7480        validate_cps_module(&lowered).expect("valid CPS");
7481        assert_eq!(root.handlers.len(), 1);
7482        assert!(
7483            root.continuations.iter().any(|continuation| matches!(
7484                continuation.terminator,
7485                CpsTerminator::Perform { .. }
7486            ))
7487        );
7488        assert!(
7489            root.continuations
7490                .iter()
7491                .flat_map(|continuation| &continuation.stmts)
7492                .any(|stmt| matches!(stmt, CpsStmt::Resume { .. }))
7493        );
7494        assert_eq!(
7495            eval_cps_module(&lowered).expect("evaluated"),
7496            vec![runtime::VmValue::Int("3".to_string())]
7497        );
7498    }
7499
7500    #[test]
7501    fn lowers_value_handler_arm() {
7502        let body = unknown_lit(typed_ir::Lit::Int("1".to_string()));
7503        let value_body = apply(
7504            apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7505            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7506        );
7507        let module = module_with_root(handle_value(body, "value", value_body));
7508        let lowered = lower_cps_module(&module).expect("lowered");
7509
7510        validate_cps_module(&lowered).expect("valid CPS");
7511        assert_eq!(
7512            eval_cps_module(&lowered).expect("evaluated"),
7513            vec![runtime::VmValue::Int("11".to_string())]
7514        );
7515    }
7516
7517    #[test]
7518    fn leaves_resume_result_outside_value_arm() {
7519        let body = apply(
7520            effect_op("choose"),
7521            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7522        );
7523        let arm_body = apply(var("k"), var("x"));
7524        let value_body = apply(
7525            apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7526            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7527        );
7528        let module = module_with_root(handle_once_with_value(
7529            "choose", "x", "k", body, arm_body, "value", value_body,
7530        ));
7531        let lowered = lower_cps_module(&module).expect("lowered");
7532
7533        validate_cps_module(&lowered).expect("valid CPS");
7534        assert_eq!(
7535            eval_cps_module(&lowered).expect("evaluated"),
7536            vec![runtime::VmValue::Int("1".to_string())]
7537        );
7538    }
7539
7540    #[test]
7541    fn leaves_multishot_resume_results_outside_value_arm() {
7542        let body = apply(
7543            effect_op("choose"),
7544            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7545        );
7546        let resume_x = apply(var("k"), var("x"));
7547        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7548        let arm_body = apply(
7549            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7550            resume_two,
7551        );
7552        let value_body = apply(
7553            apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7554            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7555        );
7556        let module = module_with_root(handle_once_with_value(
7557            "choose", "x", "k", body, arm_body, "value", value_body,
7558        ));
7559        let lowered = lower_cps_module(&module).expect("lowered");
7560
7561        validate_cps_module(&lowered).expect("valid CPS");
7562        assert_eq!(
7563            eval_cps_module(&lowered).expect("evaluated"),
7564            vec![runtime::VmValue::Int("3".to_string())]
7565        );
7566    }
7567
7568    #[test]
7569    fn lowers_effect_handler_body_rest_into_resumption_continuation() {
7570        let choose_one = apply(
7571            effect_op("choose"),
7572            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7573        );
7574        let body = apply(
7575            apply(primitive(typed_ir::PrimitiveOp::IntAdd), choose_one),
7576            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7577        );
7578        let resume_x = apply(var("k"), var("x"));
7579        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7580        let arm_body = apply(
7581            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7582            resume_two,
7583        );
7584        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7585        let lowered = lower_cps_module(&module).expect("lowered");
7586
7587        validate_cps_module(&lowered).expect("valid CPS");
7588        assert_eq!(
7589            eval_cps_module(&lowered).expect("evaluated"),
7590            vec![runtime::VmValue::Int("23".to_string())]
7591        );
7592    }
7593
7594    #[test]
7595    fn lowers_direct_call_rest_into_resumption_continuation() {
7596        let inc = binding(
7597            "inc",
7598            lambda(
7599                "x",
7600                apply(
7601                    apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
7602                    unknown_lit(typed_ir::Lit::Int("10".to_string())),
7603                ),
7604            ),
7605        );
7606        let body = apply(
7607            var("inc"),
7608            apply(
7609                effect_op("choose"),
7610                unknown_lit(typed_ir::Lit::Int("1".to_string())),
7611            ),
7612        );
7613        let resume_x = apply(var("k"), var("x"));
7614        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7615        let arm_body = apply(
7616            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7617            resume_two,
7618        );
7619        let module = module_with_bindings_and_root(
7620            vec![inc],
7621            handle_once("choose", "x", "k", body, arm_body),
7622        );
7623        let lowered = lower_cps_module(&module).expect("lowered");
7624
7625        validate_cps_module(&lowered).expect("valid CPS");
7626        assert_eq!(
7627            eval_cps_module(&lowered).expect("evaluated"),
7628            vec![runtime::VmValue::Int("23".to_string())]
7629        );
7630    }
7631
7632    #[test]
7633    fn lowers_block_rest_into_resumption_continuation() {
7634        let body = block(
7635            vec![runtime::Stmt::Let {
7636                pattern: bind_pattern("y"),
7637                value: apply(
7638                    effect_op("choose"),
7639                    unknown_lit(typed_ir::Lit::Int("1".to_string())),
7640                ),
7641            }],
7642            apply(
7643                apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7644                unknown_lit(typed_ir::Lit::Int("10".to_string())),
7645            ),
7646        );
7647        let resume_x = apply(var("k"), var("x"));
7648        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7649        let arm_body = apply(
7650            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7651            resume_two,
7652        );
7653        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7654        let lowered = lower_cps_module(&module).expect("lowered");
7655
7656        validate_cps_module(&lowered).expect("valid CPS");
7657        assert_eq!(
7658            eval_cps_module(&lowered).expect("evaluated"),
7659            vec![runtime::VmValue::Int("23".to_string())]
7660        );
7661    }
7662
7663    #[test]
7664    fn lowers_multiple_block_effects_into_nested_resumption_continuations() {
7665        let body = block(
7666            vec![
7667                runtime::Stmt::Let {
7668                    pattern: bind_pattern("a"),
7669                    value: apply(
7670                        effect_op("choose"),
7671                        unknown_lit(typed_ir::Lit::Int("1".to_string())),
7672                    ),
7673                },
7674                runtime::Stmt::Let {
7675                    pattern: bind_pattern("b"),
7676                    value: apply(
7677                        effect_op("choose"),
7678                        unknown_lit(typed_ir::Lit::Int("2".to_string())),
7679                    ),
7680                },
7681            ],
7682            apply(
7683                apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("a")),
7684                var("b"),
7685            ),
7686        );
7687        let arm_body = apply(var("k"), var("x"));
7688        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7689        let lowered = lower_cps_module(&module).expect("lowered");
7690
7691        validate_cps_module(&lowered).expect("valid CPS");
7692        assert!(
7693            lowered.roots[0]
7694                .continuations
7695                .iter()
7696                .filter(|continuation| matches!(
7697                    continuation.terminator,
7698                    CpsTerminator::Perform { .. }
7699                ))
7700                .count()
7701                >= 2
7702        );
7703        assert_eq!(
7704            eval_cps_module(&lowered).expect("evaluated"),
7705            vec![runtime::VmValue::Int("3".to_string())]
7706        );
7707    }
7708
7709    #[test]
7710    fn lowers_block_expr_statement_rest_into_resumption_continuation() {
7711        let body = block(
7712            vec![runtime::Stmt::Expr(apply(
7713                effect_op("choose"),
7714                unknown_lit(typed_ir::Lit::Int("1".to_string())),
7715            ))],
7716            apply(
7717                apply(
7718                    primitive(typed_ir::PrimitiveOp::IntAdd),
7719                    unknown_lit(typed_ir::Lit::Int("10".to_string())),
7720                ),
7721                unknown_lit(typed_ir::Lit::Int("20".to_string())),
7722            ),
7723        );
7724        let resume_x = apply(var("k"), var("x"));
7725        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7726        let arm_body = apply(
7727            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7728            resume_two,
7729        );
7730        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7731        let lowered = lower_cps_module(&module).expect("lowered");
7732
7733        validate_cps_module(&lowered).expect("valid CPS");
7734        assert_eq!(
7735            eval_cps_module(&lowered).expect("evaluated"),
7736            vec![runtime::VmValue::Int("60".to_string())]
7737        );
7738    }
7739
7740    #[test]
7741    fn unwraps_thunked_handle_body_before_cps_effect_lowering() {
7742        let body = bind_here(thunk(block(
7743            vec![runtime::Stmt::Let {
7744                pattern: bind_pattern("y"),
7745                value: apply(
7746                    effect_op("choose"),
7747                    unknown_lit(typed_ir::Lit::Int("1".to_string())),
7748                ),
7749            }],
7750            apply(
7751                apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7752                unknown_lit(typed_ir::Lit::Int("10".to_string())),
7753            ),
7754        )));
7755        let resume_x = apply(var("k"), var("x"));
7756        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7757        let arm_body = apply(
7758            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7759            resume_two,
7760        );
7761        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7762        let lowered = lower_cps_module(&module).expect("lowered");
7763
7764        validate_cps_module(&lowered).expect("valid CPS");
7765        assert_eq!(
7766            eval_cps_module(&lowered).expect("evaluated"),
7767            vec![runtime::VmValue::Int("23".to_string())]
7768        );
7769    }
7770
7771    #[test]
7772    fn infers_capture_for_block_value_used_after_effect() {
7773        let body = block(
7774            vec![
7775                runtime::Stmt::Let {
7776                    pattern: bind_pattern("z"),
7777                    value: unknown_lit(typed_ir::Lit::Int("10".to_string())),
7778                },
7779                runtime::Stmt::Let {
7780                    pattern: bind_pattern("y"),
7781                    value: apply(
7782                        effect_op("choose"),
7783                        unknown_lit(typed_ir::Lit::Int("1".to_string())),
7784                    ),
7785                },
7786            ],
7787            apply(
7788                apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7789                var("z"),
7790            ),
7791        );
7792        let resume_x = apply(var("k"), var("x"));
7793        let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7794        let arm_body = apply(
7795            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7796            resume_two,
7797        );
7798        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7799        let lowered = lower_cps_module(&module).expect("lowered");
7800
7801        validate_cps_module(&lowered).expect("valid CPS");
7802        assert!(
7803            lowered.roots[0]
7804                .continuations
7805                .iter()
7806                .any(|continuation| !continuation.captures.is_empty())
7807        );
7808        assert_eq!(
7809            eval_cps_module(&lowered).expect("evaluated"),
7810            vec![runtime::VmValue::Int("23".to_string())]
7811        );
7812    }
7813
7814    #[test]
7815    fn lowers_bind_here_tail_effect_statement() {
7816        let body = bind_here(apply(
7817            effect_op("choose"),
7818            unknown_lit(typed_ir::Lit::Int("1".to_string())),
7819        ));
7820        let arm_body = apply(var("k"), var("x"));
7821        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7822
7823        let lowered = lower_cps_module(&module).expect("lowered");
7824        validate_cps_module(&lowered).expect("valid CPS");
7825        assert_eq!(
7826            eval_cps_module(&lowered).expect("evaluated"),
7827            vec![runtime::VmValue::Int("1".to_string())]
7828        );
7829    }
7830
7831    #[test]
7832    fn lowers_if_branches_with_distinct_resume_continuations() {
7833        let then_branch = apply(
7834            apply(
7835                primitive(typed_ir::PrimitiveOp::IntAdd),
7836                apply(
7837                    effect_op("choose"),
7838                    unknown_lit(typed_ir::Lit::Int("1".to_string())),
7839                ),
7840            ),
7841            unknown_lit(typed_ir::Lit::Int("10".to_string())),
7842        );
7843        let else_branch = apply(
7844            apply(
7845                primitive(typed_ir::PrimitiveOp::IntAdd),
7846                apply(
7847                    effect_op("choose"),
7848                    unknown_lit(typed_ir::Lit::Int("2".to_string())),
7849                ),
7850            ),
7851            unknown_lit(typed_ir::Lit::Int("20".to_string())),
7852        );
7853        let body = if_expr(
7854            unknown_lit(typed_ir::Lit::Bool(true)),
7855            then_branch,
7856            else_branch,
7857        );
7858        let resume_x = apply(var("k"), var("x"));
7859        let resume_three = apply(var("k"), unknown_lit(typed_ir::Lit::Int("3".to_string())));
7860        let arm_body = apply(
7861            apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7862            resume_three,
7863        );
7864        let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7865        let lowered = lower_cps_module(&module).expect("lowered");
7866
7867        validate_cps_module(&lowered).expect("valid CPS");
7868        assert_eq!(
7869            eval_cps_module(&lowered).expect("evaluated"),
7870            vec![runtime::VmValue::Int("24".to_string())]
7871        );
7872    }
7873
7874    #[test]
7875    fn lowers_overapplied_direct_call_as_apply_to_result() {
7876        let make_id = binding("make_id", lambda("x", lambda("y", var("y"))));
7877        let root = apply(
7878            apply(
7879                var("make_id"),
7880                unknown_lit(typed_ir::Lit::Int("1".to_string())),
7881            ),
7882            unknown_lit(typed_ir::Lit::Int("2".to_string())),
7883        );
7884        let module = module_with_bindings_and_root(vec![make_id], root);
7885
7886        assert_eq!(
7887            eval_cps_module(&lower_cps_module(&module).expect("lowered")).expect("evaluated"),
7888            vec![runtime::VmValue::Int("2".to_string())]
7889        );
7890    }
7891}