use syntax::ast::{Expression, MatchArm};
use syntax::types::Type;
use crate::EmitEffects;
use crate::Planner;
use crate::analyze::inline_uses::{InlineDecision, analyze_inline_candidate, region_blocks_inline};
use crate::context::expression::ExpressionContext;
use crate::control_flow::branching::wrap_if_struct_literal;
use crate::patterns::binding_emit::drop_inline_overlays;
use crate::patterns::decision_tree::{Decision, compile_expanded_arms, expand_or_patterns};
use crate::patterns::emit_plan::{
ChainPlan, EmitBinding, EmitCase, EmitChainTest, EmitDecision, MatchEmitPlan, RetryLoopPlan,
SingleCatchallPlan, is_empty_emit_decision, lower_match,
};
use crate::plan::bodies::{
ElseArm, IfPlan, LoopPlan, LoweredBlock, LoweredStatement, PlacePlan, SwitchCasePlan,
SwitchKind, SwitchStatementPlan,
};
use crate::plan::placement::emit_unreachable_panic_if_needed;
use crate::state::bindings::{BindingValue, InlineExpr};
#[derive(Clone, Copy, PartialEq, Eq)]
enum WalkRole {
SwitchCase,
ChainBody,
RetryLoopTop,
RetryLoopNested,
}
#[derive(Clone, Copy)]
struct WalkCtx<'a> {
arm_place: &'a PlacePlan<'a>,
role: WalkRole,
break_label: Option<&'a str>,
}
impl<'a> WalkCtx<'a> {
fn switch_case(arm_place: &'a PlacePlan<'a>) -> Self {
Self {
arm_place,
role: WalkRole::SwitchCase,
break_label: None,
}
}
fn chain_test(arm_place: &'a PlacePlan<'a>) -> Self {
Self {
arm_place,
role: WalkRole::ChainBody,
break_label: None,
}
}
fn retry_loop(arm_place: &'a PlacePlan<'a>, break_label: Option<&'a str>) -> Self {
Self {
arm_place,
role: WalkRole::RetryLoopTop,
break_label,
}
}
fn nested(self) -> Self {
let role = match self.role {
WalkRole::SwitchCase | WalkRole::ChainBody => WalkRole::ChainBody,
WalkRole::RetryLoopTop | WalkRole::RetryLoopNested => WalkRole::RetryLoopNested,
};
Self { role, ..self }
}
fn is_grouped_retry(&self) -> bool {
matches!(
self.role,
WalkRole::RetryLoopTop | WalkRole::RetryLoopNested
)
}
fn leaf_scope_explicit(&self) -> bool {
matches!(self.role, WalkRole::RetryLoopTop)
}
}
pub(crate) struct TreePlanner<'a, 'e> {
planner: &'a mut Planner<'e>,
arms: &'a [MatchArm],
subject_var: String,
subject_ty: Type,
fx: &'a mut EmitEffects,
}
impl<'a, 'e> TreePlanner<'a, 'e> {
pub(crate) fn new(
planner: &'a mut Planner<'e>,
arms: &'a [MatchArm],
subject_var: String,
subject_ty: Type,
fx: &'a mut EmitEffects,
) -> Self {
Self {
planner,
arms,
subject_var,
subject_ty,
fx,
}
}
pub(crate) fn lower(mut self, place: &PlacePlan) -> LoweredBlock {
let expanded = expand_or_patterns(self.arms);
let compiled = compile_expanded_arms(self.planner, &expanded, &self.subject_ty);
self.fx.extend(&compiled.effects);
let tree = compiled.decision;
let single_catchall_has_collisions = match &tree {
Decision::Success { arm_index, .. } => self
.planner
.pattern_has_binding_collisions(&self.arms[*arm_index].pattern),
_ => false,
};
let lowered = lower_match(
self.arms,
self.subject_var.clone(),
&tree,
single_catchall_has_collisions,
);
if lowered.effects.needs_stdlib {
self.fx.require_stdlib();
}
let mut statements: Vec<LoweredStatement> = Vec::new();
match lowered.plan {
MatchEmitPlan::Switch { tree } => {
let ctx = WalkCtx::switch_case(place);
self.walk(&mut statements, &tree, &ctx);
}
MatchEmitPlan::SingleCatchall(plan) => {
self.render_single_catchall(&mut statements, &plan, place);
}
MatchEmitPlan::Chain(plan) => {
self.render_chain_root(&mut statements, &plan, place);
}
MatchEmitPlan::RetryLoop(plan) => {
self.render_retry_loop(&mut statements, &plan, place);
}
}
LoweredBlock { statements }
}
fn render_single_catchall(
&mut self,
statements: &mut Vec<LoweredStatement>,
plan: &SingleCatchallPlan,
place: &PlacePlan,
) {
let arm_body = &*self.arms[plan.arm_index].expression;
self.planner.enter_scope();
let mut inner: Vec<LoweredStatement> = Vec::new();
let inlines = self.emit_bindings(&mut inner, &plan.bindings, &[arm_body], None);
self.emit_arm_body(&mut inner, plan.arm_index, place);
self.drop_inline_bindings(&inlines);
let needs_block =
self.planner.scope.current_block_declared_nonempty() || plan.pattern_has_collisions;
self.planner.exit_scope();
if needs_block {
statements.push(LoweredStatement::Block(LoweredBlock { statements: inner }));
} else {
statements.extend(inner);
}
}
fn render_chain_root(
&mut self,
statements: &mut Vec<LoweredStatement>,
plan: &ChainPlan,
place: &PlacePlan,
) {
self.emit_chain_root_decision(statements, &plan.tree, place);
let mut tail_buffer = String::new();
emit_unreachable_panic_if_needed(&mut tail_buffer, place, plan.chain_tail_is_exhaustive);
if !tail_buffer.is_empty() {
statements.push(LoweredStatement::RawGo(tail_buffer));
}
}
fn emit_chain_root_decision(
&mut self,
statements: &mut Vec<LoweredStatement>,
tree: &EmitDecision,
place: &PlacePlan,
) {
match tree {
EmitDecision::Success {
arm_index,
bindings,
..
} => {
let arm_body = &*self.arms[*arm_index].expression;
let inlines = self.emit_bindings(statements, bindings, &[arm_body], None);
self.emit_arm_body(statements, *arm_index, place);
self.drop_inline_bindings(&inlines);
}
EmitDecision::Chain {
tests,
fallback,
last_is_catchall,
} => {
self.lower_chain_branch(statements, tests, fallback, *last_is_catchall, place);
}
EmitDecision::Unreachable => {}
EmitDecision::Guard { .. } => {
self.walk(statements, tree, &WalkCtx::chain_test(place));
}
_ => {
self.walk(statements, tree, &WalkCtx::switch_case(place));
}
}
}
fn lower_chain_branch(
&mut self,
statements: &mut Vec<LoweredStatement>,
tests: &[EmitChainTest],
fallback: &EmitDecision,
last_is_catchall: bool,
place: &PlacePlan,
) {
let regular = if last_is_catchall {
&tests[..tests.len() - 1]
} else {
tests
};
let guard_ctx = WalkCtx::switch_case(place);
let chain_ctx = WalkCtx::chain_test(place);
let mut branches: Vec<ChainBranch> = Vec::with_capacity(regular.len());
let mut last_diverges = false;
for test in regular {
let condition = test.condition.as_deref().unwrap_or("true").to_string();
let walk_ctx = if matches!(test.decision.as_ref(), EmitDecision::Guard { .. }) {
&guard_ctx
} else {
&chain_ctx
};
self.planner.enter_scope();
let mut body: Vec<LoweredStatement> = Vec::new();
self.walk(&mut body, &test.decision, walk_ctx);
self.planner.exit_scope();
let body = LoweredBlock { statements: body };
last_diverges = body.ends_with_diverge();
branches.push(ChainBranch { condition, body });
}
let trailing = if last_is_catchall {
let last_test = tests.last().unwrap();
self.lower_else_or_flat(&last_test.decision, &chain_ctx, last_diverges)
} else if matches!(fallback, EmitDecision::Unreachable) {
ElseArm::None
} else {
self.lower_else_or_flat(fallback, &chain_ctx, last_diverges)
};
if branches.is_empty() {
match trailing {
ElseArm::Else { body, .. } => statements.extend(body.statements),
ElseArm::ElseIf(plan) => statements.push(LoweredStatement::If(*plan)),
ElseArm::None => {}
}
return;
}
statements.push(LoweredStatement::If(build_chain_plan(branches, trailing)));
}
fn render_retry_loop(
&mut self,
statements: &mut Vec<LoweredStatement>,
plan: &RetryLoopPlan,
place: &PlacePlan,
) {
let use_direct_return = place.is_return();
let unguarded_exit = plan.root_has_unguarded_terminal || plan.last_arm_is_any_catchall;
let skip_wrapper = !use_direct_return && unguarded_exit && plan.all_arms_diverge;
if use_direct_return || skip_wrapper {
let ctx = WalkCtx::retry_loop(place, None);
self.walk(statements, &plan.tree, &ctx);
if use_direct_return && !plan.root_has_unguarded_terminal {
statements.push(LoweredStatement::RawGo(
"panic(\"unreachable\")\n".to_string(),
));
}
return;
}
let label = self.planner.fresh_var(Some("match"));
let ctx = WalkCtx::retry_loop(place, Some(label.as_str()));
let mut body: Vec<LoweredStatement> = Vec::new();
self.walk(&mut body, &plan.tree, &ctx);
if !unguarded_exit {
body.push(LoweredStatement::Break {
directive: String::new(),
label: Some(label.clone()),
});
}
statements.push(LoweredStatement::Loop(LoopPlan {
directive: String::new(),
prologue: String::new(),
label: Some(label),
header: "for {\n".to_string(),
body: LoweredBlock { statements: body },
}));
}
fn walk(
&mut self,
statements: &mut Vec<LoweredStatement>,
decision: &EmitDecision,
ctx: &WalkCtx,
) {
match decision {
EmitDecision::Success {
arm_index,
bindings,
..
} => {
let wrap = ctx.leaf_scope_explicit();
let arm_body = &*self.arms[*arm_index].expression;
if wrap {
self.planner.enter_scope();
}
let mut leaf: Vec<LoweredStatement> = Vec::new();
let inlines = self.emit_bindings(&mut leaf, bindings, &[arm_body], None);
let mut body_statements: Vec<LoweredStatement> = Vec::new();
self.emit_arm_body(&mut body_statements, *arm_index, ctx.arm_place);
let body_diverges = capture_diverge(body_statements, &mut leaf);
apply_leaf_terminator(&mut leaf, ctx, body_diverges);
self.drop_inline_bindings(&inlines);
if wrap {
self.planner.exit_scope();
statements.push(LoweredStatement::Block(LoweredBlock { statements: leaf }));
} else {
statements.extend(leaf);
}
}
EmitDecision::Guard {
arm_index,
bindings,
success,
failure,
} => self.walk_guard(statements, *arm_index, bindings, success, failure, ctx),
EmitDecision::IfElse {
condition,
then_branch,
else_branch,
} => {
let plan =
self.lower_if_else_block(condition, then_branch, else_branch.as_deref(), ctx);
let body_diverges = capture_diverge(vec![LoweredStatement::If(plan)], statements);
apply_leaf_terminator(statements, ctx, body_diverges);
}
EmitDecision::InlineBranch { branch } => {
let inner = WalkCtx::switch_case(ctx.arm_place);
let mut branch_statements: Vec<LoweredStatement> = Vec::new();
self.walk(&mut branch_statements, branch, &inner);
let body_diverges = capture_diverge(branch_statements, statements);
apply_leaf_terminator(statements, ctx, body_diverges);
}
EmitDecision::Switch {
expr,
cases,
default,
..
} => {
let plan = self.lower_value_switch(expr, cases, default.as_deref(), ctx.arm_place);
let body_diverges =
capture_diverge(vec![LoweredStatement::Switch(plan)], statements);
apply_leaf_terminator(statements, ctx, body_diverges);
}
EmitDecision::TypeSwitch {
base,
cases,
default,
} => {
let plan = self.lower_type_switch(base, cases, default.as_deref(), ctx.arm_place);
let body_diverges =
capture_diverge(vec![LoweredStatement::Switch(plan)], statements);
apply_leaf_terminator(statements, ctx, body_diverges);
}
EmitDecision::Chain {
tests,
fallback,
last_is_catchall,
} => {
if ctx.is_grouped_retry() {
self.emit_chain_grouped(statements, tests, fallback, *last_is_catchall, ctx);
} else {
self.lower_chain_branch(
statements,
tests,
fallback,
*last_is_catchall,
ctx.arm_place,
);
}
}
EmitDecision::Unreachable => {}
}
}
fn walk_guard(
&mut self,
statements: &mut Vec<LoweredStatement>,
arm_index: usize,
bindings: &[EmitBinding],
success: &EmitDecision,
failure: &EmitDecision,
ctx: &WalkCtx,
) {
let needs_pre_scope = ctx.leaf_scope_explicit() && !bindings.is_empty();
if needs_pre_scope {
self.planner.enter_scope();
}
let arm = &self.arms[arm_index];
let arm_body = &*arm.expression;
let mut guard_consumers: Vec<&Expression> = Vec::with_capacity(2);
if let Some(g) = arm.guard.as_deref() {
guard_consumers.push(g);
}
guard_consumers.push(arm_body);
let mut guard_statements: Vec<LoweredStatement> = Vec::new();
let inlines = self.emit_bindings(
&mut guard_statements,
bindings,
&guard_consumers,
Some(failure),
);
if let Some((condition_setup, condition)) = self.lower_guard_condition(arm_index) {
self.planner.enter_scope();
let mut success_statements: Vec<LoweredStatement> = Vec::new();
self.walk(&mut success_statements, success, &ctx.nested());
let then_body = LoweredBlock {
statements: success_statements,
};
let success_diverges = then_body.ends_with_diverge();
self.planner.exit_scope();
self.drop_inline_bindings(&inlines);
let else_arm = if ctx.role == WalkRole::SwitchCase {
self.lower_else_or_flat(failure, ctx, success_diverges)
} else {
ElseArm::None
};
guard_statements.push(LoweredStatement::If(IfPlan {
directive: String::new(),
condition_setup,
condition,
then_body,
else_arm,
}));
} else {
self.drop_inline_bindings(&inlines);
}
if needs_pre_scope {
self.planner.exit_scope();
statements.push(LoweredStatement::Block(LoweredBlock {
statements: guard_statements,
}));
} else {
statements.extend(guard_statements);
}
if ctx.role == WalkRole::RetryLoopTop {
self.walk(statements, failure, ctx);
}
}
fn lower_else_or_flat(
&mut self,
decision: &EmitDecision,
ctx: &WalkCtx,
preceding_diverges: bool,
) -> ElseArm {
if is_empty_emit_decision(decision) {
return ElseArm::None;
}
if preceding_diverges {
let mut body: Vec<LoweredStatement> = Vec::new();
self.walk(&mut body, decision, ctx);
return ElseArm::Else {
body: LoweredBlock { statements: body },
inline: true,
};
}
self.planner.enter_scope();
let mut body: Vec<LoweredStatement> = Vec::new();
self.walk(&mut body, decision, ctx);
self.planner.exit_scope();
ElseArm::Else {
body: LoweredBlock { statements: body },
inline: false,
}
}
fn lower_if_else_block(
&mut self,
condition: &str,
then_branch: &EmitDecision,
else_branch: Option<&EmitDecision>,
ctx: &WalkCtx,
) -> IfPlan {
let inner = WalkCtx::switch_case(ctx.arm_place);
self.planner.enter_scope();
let mut then_statements: Vec<LoweredStatement> = Vec::new();
self.walk(&mut then_statements, then_branch, &inner);
self.planner.exit_scope();
let then_body = LoweredBlock {
statements: then_statements,
};
let then_diverges = then_body.ends_with_diverge();
let else_arm = match else_branch {
Some(decision) => self.lower_else_or_flat(decision, &inner, then_diverges),
None => ElseArm::None,
};
IfPlan {
directive: String::new(),
condition_setup: String::new(),
condition: condition.to_string(),
then_body,
else_arm,
}
}
fn lower_value_switch(
&mut self,
expr: &str,
cases: &[EmitCase],
default: Option<&EmitDecision>,
place: &PlacePlan,
) -> SwitchStatementPlan {
let case_plans = self.lower_switch_cases(cases, place);
let default_block = self.lower_switch_default(default, place);
SwitchStatementPlan {
directive: String::new(),
kind: SwitchKind::Value {
subject: expr.to_string(),
},
cases: case_plans,
default: default_block,
postlude: switch_postlude(place, default.is_some()),
}
}
fn lower_type_switch(
&mut self,
base: &str,
cases: &[EmitCase],
default: Option<&EmitDecision>,
place: &PlacePlan,
) -> SwitchStatementPlan {
let case_plans = self.lower_switch_cases(cases, place);
let default_block = self.lower_switch_default(default, place);
let references_base = case_plans.iter().any(|case| case.references_var(base))
|| default_block
.as_ref()
.is_some_and(|body| body.references_var(base));
let binding = references_base.then(|| base.to_string());
SwitchStatementPlan {
directive: String::new(),
kind: SwitchKind::Type {
subject: base.to_string(),
binding,
},
cases: case_plans,
default: default_block,
postlude: switch_postlude(place, default.is_some()),
}
}
fn lower_switch_cases(&mut self, cases: &[EmitCase], place: &PlacePlan) -> Vec<SwitchCasePlan> {
let ctx = WalkCtx::switch_case(place);
let mut case_plans = Vec::with_capacity(cases.len());
for case in cases {
let mut body: Vec<LoweredStatement> = Vec::new();
self.planner.enter_scope();
self.walk(&mut body, &case.decision, &ctx);
self.planner.exit_scope();
case_plans.push(SwitchCasePlan {
labels: case.case_label.clone(),
body: LoweredBlock { statements: body },
});
}
case_plans
}
fn lower_switch_default(
&mut self,
default: Option<&EmitDecision>,
place: &PlacePlan,
) -> Option<LoweredBlock> {
let default_decision = default?;
let ctx = WalkCtx::switch_case(place);
let mut body: Vec<LoweredStatement> = Vec::new();
self.planner.enter_scope();
self.walk(&mut body, default_decision, &ctx);
self.planner.exit_scope();
(!body.is_empty()).then_some(LoweredBlock { statements: body })
}
fn emit_chain_grouped(
&mut self,
statements: &mut Vec<LoweredStatement>,
tests: &[EmitChainTest],
fallback: &EmitDecision,
last_is_catchall: bool,
ctx: &WalkCtx,
) {
let inner_ctx = ctx.nested();
let groups = group_chain_tests_by_condition(tests);
let group_count = groups.len();
for (g, (_condition, indices)) in groups.iter().enumerate() {
let is_last_group = g == group_count - 1;
let collapse_as_catchall = is_last_group && last_is_catchall && indices.len() == 1;
self.emit_chain_group(statements, indices, tests, &inner_ctx, collapse_as_catchall);
}
if !matches!(fallback, EmitDecision::Unreachable) {
self.walk(statements, fallback, ctx);
}
}
fn emit_chain_group(
&mut self,
statements: &mut Vec<LoweredStatement>,
indices: &[usize],
tests: &[EmitChainTest],
ctx: &WalkCtx,
collapse_as_catchall: bool,
) {
if collapse_as_catchall {
self.walk(statements, &tests[indices[0]].decision, ctx);
return;
}
let first = &tests[indices[0]];
self.planner.enter_scope();
let mut body: Vec<LoweredStatement> = Vec::new();
if bindings_are_hoistable(tests, indices) {
self.emit_chain_group_hoisted(&mut body, indices, tests, ctx);
} else {
self.emit_chain_group_per_test(&mut body, indices, tests, ctx);
}
self.planner.exit_scope();
let body = LoweredBlock { statements: body };
match &first.condition {
Some(condition) => statements.push(LoweredStatement::If(IfPlan {
directive: String::new(),
condition_setup: String::new(),
condition: condition.clone(),
then_body: body,
else_arm: ElseArm::None,
})),
None => statements.push(LoweredStatement::Block(body)),
}
}
fn emit_chain_group_hoisted(
&mut self,
statements: &mut Vec<LoweredStatement>,
indices: &[usize],
tests: &[EmitChainTest],
ctx: &WalkCtx,
) {
let mut inlines: Vec<(String, Option<BindingValue>)> = Vec::new();
if let Some(&ref_index) = indices
.iter()
.find(|&&index| !decision_top_bindings(&tests[index].decision).is_empty())
{
let mut consumers: Vec<&Expression> = Vec::new();
for &index in indices {
let decision = &*tests[index].decision;
let arm_index = match decision {
EmitDecision::Success { arm_index, .. }
| EmitDecision::Guard { arm_index, .. } => Some(*arm_index),
_ => None,
};
if let Some(arm_index) = arm_index {
let arm = &self.arms[arm_index];
if let Some(g) = arm.guard.as_deref() {
consumers.push(g);
}
consumers.push(&arm.expression);
}
}
inlines = self.emit_bindings(
statements,
decision_top_bindings(&tests[ref_index].decision),
&consumers,
None,
);
}
for &test_index in indices {
match &*tests[test_index].decision {
EmitDecision::Success { arm_index, .. } => {
let mut body_statements: Vec<LoweredStatement> = Vec::new();
self.emit_arm_body(&mut body_statements, *arm_index, ctx.arm_place);
let body_diverges = capture_diverge(body_statements, statements);
apply_leaf_terminator(statements, ctx, body_diverges);
}
EmitDecision::Guard { arm_index, .. } => {
if let Some((condition_setup, condition)) =
self.lower_guard_condition(*arm_index)
{
self.planner.enter_scope();
let mut arm_body: Vec<LoweredStatement> = Vec::new();
self.emit_arm_body(&mut arm_body, *arm_index, ctx.arm_place);
let mut then_body: Vec<LoweredStatement> = Vec::new();
let body_diverges = capture_diverge(arm_body, &mut then_body);
apply_leaf_terminator(&mut then_body, ctx, body_diverges);
self.planner.exit_scope();
statements.push(LoweredStatement::If(IfPlan {
directive: String::new(),
condition_setup,
condition,
then_body: LoweredBlock {
statements: then_body,
},
else_arm: ElseArm::None,
}));
}
}
_ => self.walk(statements, &tests[test_index].decision, ctx),
}
}
self.drop_inline_bindings(&inlines);
}
fn emit_chain_group_per_test(
&mut self,
statements: &mut Vec<LoweredStatement>,
indices: &[usize],
tests: &[EmitChainTest],
ctx: &WalkCtx,
) {
for (j, &test_index) in indices.iter().enumerate() {
let is_last_in_group = j == indices.len() - 1;
let needs_wrapper =
!is_last_in_group && decision_has_bindings(&tests[test_index].decision);
if needs_wrapper {
self.planner.enter_scope();
let mut wrapped: Vec<LoweredStatement> = Vec::new();
self.walk(&mut wrapped, &tests[test_index].decision, ctx);
self.planner.exit_scope();
statements.push(LoweredStatement::Block(LoweredBlock {
statements: wrapped,
}));
} else {
self.walk(statements, &tests[test_index].decision, ctx);
}
}
}
fn emit_bindings(
&mut self,
statements: &mut Vec<LoweredStatement>,
bindings: &[EmitBinding],
consumers: &[&Expression],
failure_blocker: Option<&EmitDecision>,
) -> Vec<(String, Option<BindingValue>)> {
let failure_trees: Vec<&Expression> = match failure_blocker {
Some(failure) => {
let mut reached: Vec<usize> = Vec::new();
collect_reachable_arms(failure, &mut reached);
let mut trees: Vec<&Expression> = Vec::with_capacity(reached.len() * 2);
for index in reached {
let arm = &self.arms[index];
if let Some(guard) = arm.guard.as_ref() {
trees.push(guard);
}
trees.push(&arm.expression);
}
trees
}
None => Vec::new(),
};
let mut installed_inlines: Vec<(String, Option<BindingValue>)> = Vec::new();
for binding in bindings {
let Some(ref go_name) = binding.go_name else {
self.planner.scope.bind(&binding.lisette_name, "");
continue;
};
let access_expression = &binding.rendered_access;
let previous = self
.planner
.scope
.resolve_identifier_binding(&binding.lisette_name)
.cloned();
if self.try_inline_binding(
&binding.lisette_name,
&binding.composable_access,
consumers,
&failure_trees,
) {
installed_inlines.push((binding.lisette_name.clone(), previous));
continue;
}
if self.planner.scope.has_binding_for_go_name(go_name) {
let fresh = self.planner.fresh_var(Some(&binding.lisette_name));
self.planner.scope.bind(&binding.lisette_name, &fresh);
self.planner.try_declare(&fresh);
statements.push(LoweredStatement::RawGo(format!(
"{} := {}\n",
fresh, access_expression
)));
} else {
let name = self
.planner
.scope
.bind(&binding.lisette_name, go_name.clone());
if self.planner.try_declare(&name) {
statements.push(LoweredStatement::RawGo(format!(
"{} := {}\n",
name, access_expression
)));
} else {
let fresh = self.planner.fresh_var(Some(&binding.lisette_name));
self.planner.scope.bind(&binding.lisette_name, &fresh);
self.planner.try_declare(&fresh);
statements.push(LoweredStatement::RawGo(format!(
"{} := {}\n",
fresh, access_expression
)));
}
}
}
installed_inlines
}
fn drop_inline_bindings(&mut self, installed: &[(String, Option<BindingValue>)]) {
drop_inline_overlays(self.planner, installed);
}
fn try_inline_binding(
&mut self,
lisette_name: &str,
composable_access: &str,
consumers: &[&Expression],
failure_trees: &[&Expression],
) -> bool {
if consumers.is_empty() {
return false;
}
if analyze_inline_candidate(lisette_name, consumers) != InlineDecision::Inline {
return false;
}
if !failure_trees.is_empty()
&& region_blocks_inline(failure_trees.iter().copied(), lisette_name)
{
return false;
}
self.planner
.scope
.bind_inline_expr(lisette_name, InlineExpr::new(composable_access));
true
}
fn emit_arm_body(
&mut self,
statements: &mut Vec<LoweredStatement>,
arm_index: usize,
place: &PlacePlan,
) {
let arm = &self.arms[arm_index];
let block = self
.planner
.lower_block_to_place(&arm.expression, place, self.fx);
statements.extend(block.statements);
}
fn lower_guard_condition(&mut self, arm_index: usize) -> Option<(String, String)> {
let guard_expression = self.arms[arm_index].guard.as_deref()?;
let mut condition_setup = String::new();
let guard_str = self.planner.emit_operand(
&mut condition_setup,
guard_expression,
ExpressionContext::value().condition(),
self.fx,
);
Some((condition_setup, wrap_if_struct_literal(guard_str)))
}
}
fn decision_top_bindings(decision: &EmitDecision) -> &[EmitBinding] {
match decision {
EmitDecision::Guard { bindings, .. } | EmitDecision::Success { bindings, .. } => bindings,
_ => &[],
}
}
fn collect_reachable_arms(decision: &EmitDecision, out: &mut Vec<usize>) {
match decision {
EmitDecision::Success { arm_index, .. } => {
if !out.contains(arm_index) {
out.push(*arm_index);
}
}
EmitDecision::Guard {
arm_index,
success,
failure,
..
} => {
if !out.contains(arm_index) {
out.push(*arm_index);
}
collect_reachable_arms(success, out);
collect_reachable_arms(failure, out);
}
EmitDecision::IfElse {
then_branch,
else_branch,
..
} => {
collect_reachable_arms(then_branch, out);
if let Some(else_b) = else_branch.as_deref() {
collect_reachable_arms(else_b, out);
}
}
EmitDecision::InlineBranch { branch } => collect_reachable_arms(branch, out),
EmitDecision::Switch { cases, default, .. }
| EmitDecision::TypeSwitch { cases, default, .. } => {
for c in cases {
collect_reachable_arms(&c.decision, out);
}
if let Some(d) = default.as_deref() {
collect_reachable_arms(d, out);
}
}
EmitDecision::Chain {
tests, fallback, ..
} => {
for t in tests {
collect_reachable_arms(&t.decision, out);
}
collect_reachable_arms(fallback, out);
}
EmitDecision::Unreachable => {}
}
}
fn decision_has_bindings(decision: &EmitDecision) -> bool {
!decision_top_bindings(decision).is_empty()
}
fn bindings_are_hoistable(tests: &[EmitChainTest], indices: &[usize]) -> bool {
if indices.len() <= 1 {
return false;
}
let reference = indices.iter().find_map(|&index| {
let b = decision_top_bindings(&tests[index].decision);
if !b.is_empty() { Some(b) } else { None }
});
let Some(reference) = reference else {
return false;
};
indices.iter().all(|&index| {
let b = decision_top_bindings(&tests[index].decision);
b.is_empty()
|| (b.len() == reference.len()
&& b.iter().zip(reference.iter()).all(|(a, r)| {
a.lisette_name == r.lisette_name
&& a.go_name == r.go_name
&& a.rendered_access == r.rendered_access
}))
})
}
fn group_chain_tests_by_condition<'a>(tests: &'a [EmitChainTest]) -> Vec<(&'a str, Vec<usize>)> {
let mut groups: Vec<(&'a str, Vec<usize>)> = Vec::new();
for (i, test) in tests.iter().enumerate() {
let key = test.condition.as_deref().unwrap_or("");
if let Some((last_key, indices)) = groups.last_mut()
&& *last_key == key
{
indices.push(i);
continue;
}
groups.push((key, vec![i]));
}
groups
}
struct ChainBranch {
condition: String,
body: LoweredBlock,
}
fn build_chain_plan(branches: Vec<ChainBranch>, trailing: ElseArm) -> IfPlan {
let mut branches = branches;
let head = branches.remove(0);
let mut else_arm = trailing;
for branch in branches.into_iter().rev() {
else_arm = ElseArm::ElseIf(Box::new(IfPlan {
directive: String::new(),
condition_setup: String::new(),
condition: branch.condition,
then_body: branch.body,
else_arm,
}));
}
IfPlan {
directive: String::new(),
condition_setup: String::new(),
condition: head.condition,
then_body: head.body,
else_arm,
}
}
fn switch_postlude(place: &PlacePlan, has_default: bool) -> Vec<LoweredStatement> {
let mut panic_buffer = String::new();
emit_unreachable_panic_if_needed(&mut panic_buffer, place, has_default);
if panic_buffer.is_empty() {
Vec::new()
} else {
vec![LoweredStatement::RawGo(panic_buffer)]
}
}
fn capture_diverge(
body_statements: Vec<LoweredStatement>,
statements: &mut Vec<LoweredStatement>,
) -> bool {
let block = LoweredBlock {
statements: body_statements,
};
let diverges = block.ends_with_diverge();
statements.extend(block.statements);
diverges
}
fn apply_leaf_terminator(
statements: &mut Vec<LoweredStatement>,
ctx: &WalkCtx,
body_diverges: bool,
) {
if let Some(label) = ctx.break_label
&& !body_diverges
{
statements.push(LoweredStatement::Break {
directive: String::new(),
label: Some(label.to_string()),
});
}
}