use super::propagation::plain_return;
use crate::EmitEffects;
use crate::Planner;
use crate::ReturnContext;
use crate::abi::transition;
use crate::context::expression::ExpressionContext;
use crate::control_flow::fallible::{ConstructorKind, Fallible, FalliblePlanner};
use crate::definitions::functions::is_go_never;
use crate::plan::bodies::{LoweredBlock, LoweredStatement};
use crate::plan::placement::is_unit_call;
use syntax::ast::Expression;
use syntax::types::Type;
impl Planner<'_> {
pub(crate) fn lower_try_block(
&mut self,
items: &[Expression],
ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
fx.require_stdlib();
let return_ctx = self.return_ctx();
let effective_ty = resolve_fallible_block_type(items, ty, Some(return_ctx.as_ref()));
let fallible = Fallible::from_type(&effective_ty)
.expect("`try` block must have Result or Option type");
let result_var = self.fresh_var(Some("tryResult"));
self.declare(&result_var);
let full_ty = {
let mut fe = FalliblePlanner::new(self, &fallible, fx);
fe.full_type_string()
};
let body_ctx = ReturnContext::TaggedBlock(effective_ty);
self.push_return_ctx(body_ctx.clone());
let body = self.with_fresh_scope(|planner| planner.lower_try_body(items, &fallible, fx));
self.pop_return_ctx();
let setup = vec![LoweredStatement::ClosureBind {
name: result_var.clone(),
closure_open: format!("func() {} {{\n", full_ty),
body,
closure_close: "}()\n".to_string(),
}];
(setup, result_var)
}
fn lower_try_body(
&mut self,
items: &[Expression],
fallible: &Fallible,
fx: &mut EmitEffects,
) -> LoweredBlock {
let Some((last, rest)) = items.split_last() else {
return LoweredBlock {
statements: vec![self.lower_try_unit_return(fallible, fx)],
};
};
let mut statements: Vec<LoweredStatement> = rest
.iter()
.map(|item| self.lower_statement(item, fx))
.collect();
statements.extend(self.lower_try_tail(last, fallible, fx));
LoweredBlock { statements }
}
fn lower_try_tail(
&mut self,
last: &Expression,
fallible: &Fallible,
fx: &mut EmitEffects,
) -> Vec<LoweredStatement> {
if last.diverges().is_some() || last.get_type().is_never() {
let mut statements = vec![self.lower_statement(last, fx)];
if !is_go_never(last) {
statements.push(LoweredStatement::UnreachablePanic);
}
return statements;
}
let is_statement_only = matches!(
last,
Expression::Let { .. }
| Expression::Const { .. }
| Expression::Assignment { .. }
| Expression::While { .. }
| Expression::WhileLet { .. }
| Expression::For { .. }
| Expression::Loop { .. }
);
if is_statement_only || is_unit_call(last) {
return vec![
self.lower_statement(last, fx),
self.lower_try_unit_return(fallible, fx),
];
}
let (mut statements, final_expression) =
self.lower_value(last, ExpressionContext::value(), fx);
if final_expression.is_empty() {
statements.push(self.lower_try_unit_return(fallible, fx));
} else {
statements.push(self.lower_try_success_return(&final_expression, fallible, fx));
}
statements
}
fn lower_try_unit_return(
&mut self,
fallible: &Fallible,
fx: &mut EmitEffects,
) -> LoweredStatement {
let (unit_val, effects) = self.zero_value(fallible.ok_ty());
fx.extend(&effects);
self.lower_try_success_return(&unit_val, fallible, fx)
}
fn lower_try_success_return(
&mut self,
value: &str,
fallible: &Fallible,
fx: &mut EmitEffects,
) -> LoweredStatement {
let ok_return = {
let mut fe = FalliblePlanner::new(self, fallible, fx);
fe.emit_success(value)
};
plain_return(ok_return)
}
pub(super) fn try_lower_error_constructor(
&mut self,
expression: &Expression,
fallible: &Fallible,
fx: &mut EmitEffects,
) -> Option<Vec<LoweredStatement>> {
let mut statements: Vec<LoweredStatement> = Vec::new();
let err_arg = match expression {
Expression::Call {
expression: func,
args,
..
} => {
if fallible.classify_constructor(func) != Some(ConstructorKind::Failure) {
return None;
}
if !args.is_empty() {
let (setup, value) = self.lower_value(&args[0], ExpressionContext::value(), fx);
statements.extend(setup);
Some(value)
} else {
Some(String::new())
}
}
Expression::Identifier { .. } => {
if fallible.classify_constructor(expression) != Some(ConstructorKind::Failure) {
return None;
}
Some(String::new())
}
_ => return None,
};
let return_ctx = self.return_ctx();
if let Some(shape) = return_ctx.lowered_shape() {
let return_ty = return_ctx.expect_ty();
let values = if fallible.is_result() {
let err_expr = err_arg.as_deref().unwrap_or("");
transition::lowered_err_values(self, &shape, &return_ty, err_expr, fx)
} else {
transition::lowered_none_values(self, &shape, &return_ty, fx)
};
statements.push(plain_return(values.join(", ")));
} else {
fx.require_stdlib();
let err_return = {
let mut fe = FalliblePlanner::new(self, fallible, fx);
fe.emit_contextual_failure(err_arg.as_deref())
};
statements.push(plain_return(err_return));
}
Some(statements)
}
pub(crate) fn lower_recover_block(
&mut self,
items: &[Expression],
ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
fx.require_stdlib();
let return_ctx = self.return_ctx();
let effective_ty = resolve_fallible_block_type(items, ty, Some(return_ctx.as_ref()));
let fallible = Fallible::from_type(&effective_ty)
.expect("recover block type must be Result<T, PanicValue>");
let result_var = self.fresh_var(Some("recoverResult"));
self.declare(&result_var);
let inner_ty_str = self.go_type_string(fallible.ok_ty(), fx);
let body_return_ctx = self.return_context_for_type(fallible.ok_ty().clone());
self.push_return_ctx(body_return_ctx.clone());
let body =
self.with_fresh_scope(|planner| planner.lower_recover_body_block(items, &fallible, fx));
self.pop_return_ctx();
let setup = vec![LoweredStatement::ClosureBind {
name: result_var.clone(),
closure_open: format!("lisette.RecoverBlock(func() {} {{\n", inner_ty_str),
body,
closure_close: "})\n".to_string(),
}];
(setup, result_var)
}
fn lower_recover_body_block(
&mut self,
items: &[Expression],
fallible: &Fallible,
fx: &mut EmitEffects,
) -> LoweredBlock {
let Some((last, rest)) = items.split_last() else {
return LoweredBlock {
statements: vec![self.lower_zero_return(fallible.ok_ty(), fx)],
};
};
let mut statements: Vec<LoweredStatement> = rest
.iter()
.map(|item| self.lower_statement(item, fx))
.collect();
statements.extend(self.lower_recover_tail(last, fallible, fx));
LoweredBlock { statements }
}
fn lower_recover_tail(
&mut self,
last: &Expression,
fallible: &Fallible,
fx: &mut EmitEffects,
) -> Vec<LoweredStatement> {
let item_ty = last.get_type();
if item_ty.is_never() {
let mut statements = vec![self.lower_statement(last, fx)];
if !is_go_never(last) {
statements.push(LoweredStatement::UnreachablePanic);
}
return statements;
}
if item_ty.is_unit() || item_ty.is_variable() {
return vec![
self.lower_statement(last, fx),
self.lower_zero_return(fallible.ok_ty(), fx),
];
}
let (mut statements, expression) = self.lower_value(last, ExpressionContext::value(), fx);
statements.push(plain_return(expression));
statements
}
fn lower_zero_return(&mut self, ty: &Type, fx: &mut EmitEffects) -> LoweredStatement {
let (zero, effects) = self.zero_value(ty);
fx.extend(&effects);
plain_return(zero)
}
}
fn resolve_fallible_block_type(
items: &[Expression],
ty: &Type,
outer: Option<&ReturnContext>,
) -> Type {
let tail_is_never = items.last().is_some_and(|last| {
let t = last.get_type();
t.is_never() || last.diverges().is_some()
});
let base = Fallible::from_type(ty);
let needs_return_context = tail_is_never
|| base
.as_ref()
.is_some_and(|f| f.ok_ty().is_variable() || f.ok_ty().is_never());
if !needs_return_context {
return ty.clone();
}
let resolved = outer
.expect("fallible block type resolution requires a threaded outer return context")
.clone();
resolved
.ty()
.filter(|ty| Fallible::from_type(ty).is_some())
.cloned()
.unwrap_or_else(|| ty.clone())
}