use crate::EmitEffects;
use crate::Planner;
use crate::abi::AbiShape;
use crate::context::expression::ExpressionContext;
use crate::patterns::binding_decls::pattern_binds_name;
use crate::patterns::tree_emitter::TreePlanner;
use crate::plan::bodies::{ElseArm, IfPlan, LoweredBlock, LoweredStatement, PlacePlan};
use crate::plan::calls::CallReturnShape;
use crate::state::bindings::BindingValue;
use syntax::ast::{Expression, MatchArm, Pattern};
enum SubjectDeclaration {
PlainDiscard {
var: String,
},
Deferred {
var: String,
expression: String,
},
None,
}
impl Planner<'_> {
pub(crate) fn lower_match_to_block(
&mut self,
subject: &Expression,
arms: &[MatchArm],
place: &PlacePlan,
fx: &mut EmitEffects,
) -> LoweredBlock {
let mut statements: Vec<LoweredStatement> = Vec::new();
if subject.get_type().is_never() {
statements.push(self.lower_statement(subject, fx));
return LoweredBlock { statements };
}
if let Some(fused) = self.lower_fused_lowered_match(subject, arms, place, fx) {
statements.extend(fused);
return LoweredBlock { statements };
}
let subject_ty = subject.get_type();
let (subject_var, declaration) =
self.lower_match_subject_var(&mut statements, subject, arms, fx);
let block = self.lower_match_tree(arms, subject_var.clone(), subject_ty, place, fx);
let used = block.references_var(&subject_var);
match declaration {
SubjectDeclaration::PlainDiscard { var } => {
if !used {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", var)));
}
}
SubjectDeclaration::Deferred { var, expression } => {
if used {
statements.push(LoweredStatement::RawGo(format!(
"{} := {}\n",
var, expression
)));
} else {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", expression)));
}
}
SubjectDeclaration::None => {}
}
statements.extend(block.statements);
LoweredBlock { statements }
}
fn lower_match_tree(
&mut self,
arms: &[MatchArm],
subject_var: String,
subject_ty: syntax::types::Type,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> LoweredBlock {
let tree_emitter = TreePlanner::new(self, arms, subject_var, subject_ty, fx);
tree_emitter.lower(place)
}
fn lower_fused_lowered_match(
&mut self,
subject: &Expression,
arms: &[MatchArm],
place: &PlacePlan,
fx: &mut EmitEffects,
) -> Option<Vec<LoweredStatement>> {
let plan = self.plan_call(subject)?;
let CallReturnShape::Lowered(shape) = plan.return_shape else {
return None;
};
if !matches!(shape, AbiShape::ResultTuple | AbiShape::BareError) {
return None;
}
let (ok_arm, err_arm) = classify_result_arms(arms)?;
let ok_binding = simple_payload_binding(ok_arm);
let err_binding = simple_payload_binding(err_arm);
err_binding?;
if ok_binding.is_none() && !ok_arm_payload_is_omitted(ok_arm, &shape) {
return None;
}
let ok_name = ok_binding.filter(|n| *n != "_");
let err_name = err_binding.filter(|n| *n != "_");
let val_var = if matches!(shape, AbiShape::ResultTuple) && ok_name.is_some() {
let v = self.fresh_var(Some("ret"));
self.declare(&v);
Some(v)
} else {
None
};
let err_var = self.fresh_var(Some("ret"));
self.declare(&err_var);
let (mut statements, call_str) =
self.lower_call(subject, None, ExpressionContext::value(), fx);
let bind_line = match &val_var {
Some(v) => format!("{}, {} := {}\n", v, err_var, call_str),
None => match shape {
AbiShape::ResultTuple => format!("_, {} := {}\n", err_var, call_str),
AbiShape::BareError => format!("{} := {}\n", err_var, call_str),
AbiShape::PartialTuple
| AbiShape::CommaOk
| AbiShape::NullableReturn
| AbiShape::Tuple { .. } => unreachable!("rejected above"),
},
};
statements.push(LoweredStatement::RawGo(bind_line));
let then_body = self.lower_fused_arm(
ok_name.zip(val_var.as_deref()),
&ok_arm.expression,
place,
fx,
);
let else_body = self.lower_fused_arm(
err_name.map(|n| (n, err_var.as_str())),
&err_arm.expression,
place,
fx,
);
statements.push(LoweredStatement::If(IfPlan {
directive: String::new(),
condition_setup: String::new(),
condition: format!("{} == nil", err_var),
then_body,
else_arm: ElseArm::Else {
body: else_body,
inline: false,
},
}));
Some(statements)
}
fn lower_fused_arm(
&mut self,
binding: Option<(&str, &str)>,
body: &Expression,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> LoweredBlock {
self.scope.push_binding_frame();
let bound = binding.map(|(name, value)| {
let go_name = self.scope.bind(name, name);
self.declare(&go_name);
(go_name, value.to_string())
});
let body_block = self.lower_block_to_place(body, place, fx);
let mut statements = Vec::new();
if let Some((go_name, value)) = &bound {
statements.push(LoweredStatement::TempBind {
name: go_name.clone(),
value: value.clone(),
});
if !body_block.references_var(go_name) {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", go_name)));
}
}
statements.extend(body_block.statements);
self.scope.pop_binding_frame();
LoweredBlock { statements }
}
fn lower_match_subject_var(
&mut self,
setup: &mut Vec<LoweredStatement>,
subject: &Expression,
arms: &[MatchArm],
fx: &mut EmitEffects,
) -> (String, SubjectDeclaration) {
let any_guard = arms.iter().any(|arm| arm.has_guard());
if let Expression::Identifier { value, .. } = subject
&& !any_guard
{
let name = value.to_string();
let has_collision = arms
.iter()
.any(|arm| pattern_binds_name(&arm.pattern, &name));
let bound_to_inline = matches!(
self.scope.resolve_identifier_binding(&name),
Some(BindingValue::InlineExpr(_))
);
if !has_collision && !name.contains('.') && !bound_to_inline {
let var = self.scope.resolve_or_escape_go_name(&name);
return (var.clone(), SubjectDeclaration::PlainDiscard { var });
}
}
if matches!(subject, Expression::Literal { .. }) {
let staged = self.stage_operand(subject, ExpressionContext::value(), fx);
setup.extend(staged.setup);
return (staged.value, SubjectDeclaration::None);
}
let var = self.fresh_var(Some("subject"));
self.declare(&var);
let staged = self.stage_composite(subject, ExpressionContext::value(), fx);
setup.extend(staged.setup);
let declaration = SubjectDeclaration::Deferred {
var: var.clone(),
expression: staged.value,
};
(var, declaration)
}
}
fn classify_result_arms(arms: &[MatchArm]) -> Option<(&MatchArm, &MatchArm)> {
if arms.len() != 2 || arms.iter().any(|a| a.has_guard()) {
return None;
}
let kind = |arm: &MatchArm| -> Option<&str> {
let Pattern::EnumVariant {
identifier, rest, ..
} = &arm.pattern
else {
return None;
};
if *rest {
return None;
}
match identifier.as_str() {
"Ok" | "Result.Ok" => Some("Ok"),
"Err" | "Result.Err" => Some("Err"),
_ => None,
}
};
let a0 = kind(&arms[0])?;
let a1 = kind(&arms[1])?;
match (a0, a1) {
("Ok", "Err") => Some((&arms[0], &arms[1])),
("Err", "Ok") => Some((&arms[1], &arms[0])),
_ => None,
}
}
fn simple_payload_binding(arm: &MatchArm) -> Option<&str> {
let Pattern::EnumVariant { fields, .. } = &arm.pattern else {
return None;
};
if fields.len() != 1 {
return None;
}
match &fields[0] {
Pattern::Identifier { identifier, .. } => Some(identifier.as_str()),
Pattern::WildCard { .. } => Some("_"),
_ => None,
}
}
fn ok_arm_payload_is_omitted(arm: &MatchArm, shape: &AbiShape) -> bool {
let Pattern::EnumVariant { fields, .. } = &arm.pattern else {
return false;
};
match shape {
AbiShape::BareError => {
fields.is_empty() || matches!(fields.as_slice(), [Pattern::Unit { .. }])
}
AbiShape::ResultTuple
| AbiShape::PartialTuple
| AbiShape::CommaOk
| AbiShape::NullableReturn
| AbiShape::Tuple { .. } => false,
}
}