use crate::EmitEffects;
use crate::Planner;
use crate::Renderer;
use crate::abi::transition::try_emit_lowered_tail_return;
use crate::context::expression::ExpressionContext;
use crate::control_flow::branching::wrap_if_struct_literal;
use crate::control_flow::propagation::plain_return;
use crate::definitions::functions::is_go_never;
use crate::expressions::emission::StagedExpression;
use crate::plan::bodies::{
ElseArm, ExpressionStatementForm, ExpressionStatementPlan, IfPlan, LoopPlan, LoweredBlock,
LoweredStatement, MatchStatementPlan, PlacePlan, WhileLetPlan,
};
use crate::plan::placement::{requires_temp_var, try_elide_tail_let};
use crate::plan::values::setup_from_string;
use syntax::ast::{Expression, Literal};
use syntax::types::Type;
impl Planner<'_> {
fn operand_temp_declaration(
&mut self,
ty: &Type,
fx: &mut EmitEffects,
) -> (String, LoweredStatement) {
let result_var = self.fresh_var(None);
let declaration = LoweredStatement::VarDecl {
name: result_var.clone(),
go_type: self.go_type_string(ty, fx),
value: None,
};
self.declare(&result_var);
(result_var, declaration)
}
pub(crate) fn plan_if_as_operand_temp(
&mut self,
expression: &Expression,
ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
let Expression::If {
condition,
consequence,
alternative,
..
} = expression
else {
unreachable!("plan_if_as_operand_temp called on non-If expression");
};
let (result_var, declaration) = self.operand_temp_declaration(ty, fx);
let plan = self.lower_if(
String::new(),
condition,
consequence,
alternative,
&PlacePlan::Assign {
local: &result_var,
target_ty: Some(ty),
},
fx,
);
(vec![declaration, LoweredStatement::If(plan)], result_var)
}
pub(crate) fn plan_branching_as_operand_temp(
&mut self,
expression: &Expression,
ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
let (result_var, declaration) = self.operand_temp_declaration(ty, fx);
let block = self.lower_branching_to_block(
expression,
&PlacePlan::Assign {
local: &result_var,
target_ty: Some(ty),
},
fx,
);
let mut setup = vec![declaration];
setup.extend(block.statements);
(setup, result_var)
}
pub(crate) fn plan_loop_as_operand_temp(
&mut self,
expression: &Expression,
ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
let Expression::Loop {
body, needs_label, ..
} = expression
else {
unreachable!("plan_loop_as_operand_temp called on non-Loop expression");
};
let (result_var, declaration) = self.operand_temp_declaration(ty, fx);
self.push_loop(result_var.clone());
let plan = self.lower_loop_with_header(
String::new(),
"for {\n".to_string(),
body,
*needs_label,
fx,
);
self.pop_loop();
(vec![declaration, LoweredStatement::Loop(plan)], result_var)
}
fn lower_body_until_diverge(
&mut self,
rest: &[Expression],
last: &Expression,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, bool) {
let mut statements: Vec<LoweredStatement> = Vec::with_capacity(rest.len() + 1);
for item in rest {
let statement = self.lower_statement(item, fx);
let diverged = statement.blocks_fallthrough();
statements.push(statement);
if diverged {
return (statements, true);
}
}
statements.extend(self.lower_return_tail(last, fx));
(statements, false)
}
pub(crate) fn lower_function_body(
&mut self,
body: &Expression,
should_return: bool,
fx: &mut EmitEffects,
) -> LoweredBlock {
if !should_return {
return self.lower_block_as_body(body, fx);
}
let items: &[Expression] = if let Expression::Block { items, .. } = body {
items
} else {
std::slice::from_ref(body)
};
let Some((last, rest)) = items.split_last() else {
return LoweredBlock {
statements: Vec::new(),
};
};
let (mut statements, diverged) = self.lower_body_until_diverge(rest, last, fx);
if diverged {
return LoweredBlock { statements };
}
let is_statement_only = matches!(
last,
Expression::Assignment { .. } | Expression::Let { .. } | Expression::Const { .. }
);
let is_unit_tail = !is_statement_only
&& !matches!(last, Expression::Return { .. })
&& last.get_type().is_unit();
let return_ctx = self.return_ctx();
if (is_statement_only || is_unit_tail)
&& let Some(return_ty) = return_ctx.ty().filter(|ty| !ty.is_unit())
{
let return_ty = return_ty.clone();
let (zero, effects) = self.zero_value(&return_ty);
fx.extend(&effects);
statements.push(plain_return(zero));
}
LoweredBlock { statements }
}
pub(crate) fn lower_statement(
&mut self,
expression: &Expression,
fx: &mut EmitEffects,
) -> LoweredStatement {
match expression {
Expression::If {
condition,
consequence,
alternative,
..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.lower_if(
directive,
condition,
consequence,
alternative,
&PlacePlan::Statement,
fx,
);
LoweredStatement::If(plan)
}
Expression::Loop {
body, needs_label, ..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
LoweredStatement::Loop(self.lower_infinite_loop(directive, body, *needs_label, fx))
}
Expression::While {
condition,
body,
needs_label,
..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
LoweredStatement::Loop(self.lower_while(
directive,
condition,
body,
*needs_label,
fx,
))
}
Expression::Block { .. } => {
self.enter_scope();
let body = self.lower_block_as_body(expression, fx);
self.exit_scope();
LoweredStatement::Block(body)
}
Expression::For { .. } => self.lower_for_statement(expression, fx),
Expression::Continue { .. } => {
let directive = self.maybe_line_directive(&expression.get_span());
LoweredStatement::Continue {
directive,
label: self.current_loop_label().map(str::to_string),
}
}
Expression::Break { value: None, .. } => {
let directive = self.maybe_line_directive(&expression.get_span());
LoweredStatement::Break {
directive,
label: self.current_loop_label().map(str::to_string),
}
}
Expression::Break {
value: Some(value), ..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.build_break_value_plan(value, directive, fx);
LoweredStatement::BreakValue(plan)
}
Expression::Const {
identifier,
expression: value,
ty,
..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.build_const_plan(identifier, value, ty, directive, fx);
LoweredStatement::Const(plan)
}
Expression::Return {
expression: value, ..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.build_return_plan(value, directive, fx);
LoweredStatement::Return(plan)
}
Expression::Let {
binding,
value,
mutable,
else_block,
..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.build_let_plan(
binding,
value,
else_block.as_deref(),
*mutable,
directive,
fx,
);
LoweredStatement::Let(plan)
}
Expression::Assignment {
target,
value,
compound_operator,
..
} => {
let directive = self.maybe_line_directive(&expression.get_span());
let plan = self.build_assignment_plan(
target,
value,
compound_operator.as_ref(),
directive,
fx,
);
LoweredStatement::Assign(plan)
}
Expression::Match { subject, arms, .. } => {
let directive = self.maybe_line_directive(&expression.get_span());
let body = self.lower_match_to_block(subject, arms, &PlacePlan::Statement, fx);
LoweredStatement::Match(MatchStatementPlan { directive, body })
}
Expression::Select { arms, .. } => {
let directive = self.maybe_line_directive(&expression.get_span());
let mut plan = self.lower_select(arms, &PlacePlan::Statement, fx);
plan.directive = directive;
LoweredStatement::Select(plan)
}
Expression::WhileLet { .. } => self.lower_while_let_statement(expression, fx),
Expression::Struct { .. }
| Expression::Enum { .. }
| Expression::TypeAlias { .. }
| Expression::Interface { .. }
| Expression::ImplBlock { .. } => {
let directive = self.maybe_line_directive(&expression.get_span());
let code = self.emit_top_item(expression, fx);
let mut buffer = directive;
if !code.is_empty() {
buffer.push_str(&code);
buffer.push('\n');
}
LoweredStatement::RawGo(buffer)
}
_ => self.lower_expression_statement(expression, fx),
}
}
fn lower_expression_statement(
&mut self,
expression: &Expression,
fx: &mut EmitEffects,
) -> LoweredStatement {
let unwrapped = expression.unwrap_parens();
let directive = self.maybe_line_directive(&expression.get_span());
let form = if matches!(
unwrapped,
Expression::Task { .. } | Expression::Defer { .. }
) {
let value = self.plan_operand(unwrapped, ExpressionContext::value(), fx);
ExpressionStatementForm::Async { value }
} else if let Expression::Propagate {
expression: inner, ..
} = unwrapped
{
ExpressionStatementForm::Propagate {
body: LoweredBlock {
statements: self.lower_propagate_statement(inner, fx),
},
}
} else {
ExpressionStatementForm::Discard {
body: LoweredBlock {
statements: self.lower_discard_value(unwrapped, fx),
},
}
};
LoweredStatement::Expression(ExpressionStatementPlan { directive, form })
}
fn lower_while_let_statement(
&mut self,
expression: &Expression,
fx: &mut EmitEffects,
) -> LoweredStatement {
let Expression::WhileLet {
pattern,
typed_pattern,
scrutinee,
body,
needs_label,
..
} = expression
else {
unreachable!("lower_while_let_statement requires a WhileLet expression");
};
let directive = self.maybe_line_directive(&expression.get_span());
self.push_loop("_");
let body = self.lower_while_let(
pattern,
typed_pattern.as_ref(),
scrutinee,
body,
*needs_label,
fx,
);
self.pop_loop();
LoweredStatement::WhileLet(WhileLetPlan { directive, body })
}
fn lower_infinite_loop(
&mut self,
directive: String,
body: &Expression,
needs_label: bool,
fx: &mut EmitEffects,
) -> LoopPlan {
self.push_loop("_");
let plan =
self.lower_loop_with_header(directive, "for {\n".to_string(), body, needs_label, fx);
self.pop_loop();
plan
}
fn lower_condition(
&mut self,
condition: &Expression,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
let plan = self.plan_operand(condition, ExpressionContext::value().condition(), fx);
let staged = StagedExpression::from_plan(plan, condition);
(staged.setup, staged.value)
}
fn lower_while(
&mut self,
directive: String,
condition: &Expression,
body: &Expression,
needs_label: bool,
fx: &mut EmitEffects,
) -> LoopPlan {
self.push_loop("_");
let (setup, rendered) = self.lower_condition(condition, fx);
let header = if !setup.is_empty() {
let setup_text = Renderer.render_setup(&setup);
format!("for {{\n{}if !({}) {{ break }}\n", setup_text, rendered)
} else if matches!(
condition.unwrap_parens(),
Expression::Literal {
literal: Literal::Boolean(true),
..
}
) {
"for {\n".to_string()
} else {
format!("for {} {{\n", wrap_if_struct_literal(rendered))
};
let plan = self.lower_loop_with_header(directive, header, body, needs_label, fx);
self.pop_loop();
plan
}
pub(crate) fn lower_loop_with_header(
&mut self,
directive: String,
header: String,
body: &Expression,
needs_label: bool,
fx: &mut EmitEffects,
) -> LoopPlan {
self.set_current_loop_label_if_needed(needs_label);
let label = self.current_loop_label().map(str::to_string);
self.enter_scope();
let lowered_body = self.lower_block_as_body(body, fx);
self.exit_scope();
LoopPlan {
directive,
prologue: Vec::new(),
label,
header,
body: lowered_body,
}
}
pub(crate) fn lower_block_as_body(
&mut self,
expression: &Expression,
fx: &mut EmitEffects,
) -> LoweredBlock {
let items: &[Expression] = if let Expression::Block { items, .. } = expression {
items
} else {
std::slice::from_ref(expression)
};
let statements = items
.iter()
.map(|item| self.lower_statement(item, fx))
.collect();
LoweredBlock { statements }
}
pub(crate) fn lower_branching_to_block(
&mut self,
expression: &Expression,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> LoweredBlock {
match expression {
Expression::If {
condition,
consequence,
alternative,
..
} => {
let plan = self.lower_if(
String::new(),
condition,
consequence,
alternative,
place,
fx,
);
LoweredBlock {
statements: vec![LoweredStatement::If(plan)],
}
}
Expression::Match { subject, arms, .. } => {
self.lower_match_to_block(subject, arms, place, fx)
}
Expression::Select { arms, .. } => LoweredBlock {
statements: vec![LoweredStatement::Select(self.lower_select(arms, place, fx))],
},
_ => unreachable!("lower_branching_to_block: expected if/match/select"),
}
}
pub(crate) fn lower_block_to_place(
&mut self,
expression: &Expression,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> LoweredBlock {
match place {
PlacePlan::Statement => self.lower_block_as_body(expression, fx),
PlacePlan::Return => self.lower_block_to_return(expression, fx),
PlacePlan::Assign { local, target_ty } => {
self.lower_block_to_assign(expression, local, *target_ty, fx)
}
}
}
fn lower_block_to_assign(
&mut self,
expression: &Expression,
local: &str,
target_ty: Option<&Type>,
fx: &mut EmitEffects,
) -> LoweredBlock {
if expression.get_type().is_result() || expression.get_type().is_option() {
return LoweredBlock {
statements: self.lower_option_result_assignment(local, target_ty, expression, fx),
};
}
LoweredBlock {
statements: self.lower_block_to_var(expression, local, target_ty, false, fx),
}
}
fn lower_block_to_return(
&mut self,
expression: &Expression,
fx: &mut EmitEffects,
) -> LoweredBlock {
let items: &[Expression] = if let Expression::Block { items, .. } = expression {
items
} else {
std::slice::from_ref(expression)
};
let Some((last, rest)) = try_elide_tail_let(items).or_else(|| items.split_last()) else {
return LoweredBlock {
statements: Vec::new(),
};
};
let (statements, _) = self.lower_body_until_diverge(rest, last, fx);
LoweredBlock { statements }
}
pub(crate) fn lower_return_tail(
&mut self,
last: &Expression,
fx: &mut EmitEffects,
) -> Vec<LoweredStatement> {
let mut statements = Vec::new();
let return_span = last.get_span();
let last = if let Expression::Return { expression, .. } = last {
expression.as_ref()
} else {
last
};
if last.get_type().is_unit() {
if !matches!(last, Expression::Unit { .. }) {
statements.push(self.lower_statement(last, fx));
}
return statements;
}
if last.get_type().is_never() {
return self.lower_never_return_tail(last, &return_span, fx);
}
let directive = self.maybe_line_directive(&return_span);
match last {
Expression::If {
condition,
consequence,
alternative,
..
} => {
let plan = self.lower_if(
directive,
condition,
consequence,
alternative,
&PlacePlan::Return,
fx,
);
statements.push(LoweredStatement::If(plan));
}
Expression::Match { subject, arms, .. } => {
if !directive.is_empty() {
statements.push(LoweredStatement::RawGo(directive));
}
let block = self.lower_match_to_block(subject, arms, &PlacePlan::Return, fx);
statements.extend(block.statements);
}
Expression::Select { arms, .. } => {
let mut plan = self.lower_select(arms, &PlacePlan::Return, fx);
plan.directive = directive;
statements.push(LoweredStatement::Select(plan));
}
_ => {
if !directive.is_empty() {
statements.push(LoweredStatement::RawGo(directive));
}
if let Some(tail) = try_emit_lowered_tail_return(self, last, fx) {
statements.extend(tail);
} else if let Some(wrapped) = self.lower_wrapped_return(last, fx) {
statements.extend(wrapped);
} else {
statements.extend(self.lower_plain_return_tail(last, fx));
}
}
}
statements
}
fn lower_never_return_tail(
&mut self,
last: &Expression,
return_span: &syntax::ast::Span,
fx: &mut EmitEffects,
) -> Vec<LoweredStatement> {
let mut statements = setup_from_string(self.maybe_line_directive(return_span));
statements.push(self.lower_statement(last, fx));
if !is_go_never(last) {
statements.push(LoweredStatement::UnreachablePanic);
}
statements
}
fn lower_plain_return_tail(
&mut self,
last: &Expression,
fx: &mut EmitEffects,
) -> Vec<LoweredStatement> {
if requires_temp_var(last) {
let staged = self.stage_operand(last, ExpressionContext::value(), fx);
let mut statements = staged.setup;
if !staged.value.is_empty() {
statements.push(plain_return(staged.value));
}
statements
} else {
let (mut statements, expression_string) = self.lower_tail_value(last, fx);
let return_ctx = self.return_ctx();
let mut coercion = String::new();
let expression_string = self.apply_type_coercion(
&mut coercion,
return_ctx.ty(),
last,
expression_string,
fx,
);
if !coercion.is_empty() {
statements.push(LoweredStatement::RawGo(coercion));
}
statements.push(plain_return(expression_string));
statements
}
}
pub(crate) fn lower_if(
&mut self,
directive: String,
condition: &Expression,
consequence: &Expression,
alternative: &Expression,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> IfPlan {
let (condition_setup, condition_string) = self.lower_condition(condition, fx);
let condition = wrap_if_struct_literal(condition_string);
self.enter_scope();
let then_body = self.lower_block_to_place(consequence, place, fx);
self.exit_scope();
let preceding_diverges = then_body.ends_with_diverge();
let else_arm = self.lower_else_chain(alternative, preceding_diverges, place, fx);
IfPlan {
directive,
condition_setup,
condition,
then_body,
else_arm,
}
}
fn lower_else_chain(
&mut self,
alternative: &Expression,
preceding_diverges: bool,
place: &PlacePlan,
fx: &mut EmitEffects,
) -> ElseArm {
let is_empty_alternative = match alternative {
Expression::Unit { .. } => true,
Expression::Block { items, .. } => items.is_empty(),
_ => false,
};
if is_empty_alternative {
return ElseArm::None;
}
if let Expression::If {
condition,
consequence,
alternative: next_alternative,
..
} = alternative
{
let (condition_setup, condition_string) = self.lower_condition(condition, fx);
let condition = wrap_if_struct_literal(condition_string);
if !condition_setup.is_empty() {
self.enter_scope();
self.enter_scope();
let then_body = self.lower_block_to_place(consequence, place, fx);
self.exit_scope();
let inner = self.lower_else_chain(
next_alternative,
then_body.ends_with_diverge(),
place,
fx,
);
self.exit_scope();
ElseArm::ElseIf(Box::new(IfPlan {
directive: String::new(),
condition_setup,
condition,
then_body,
else_arm: inner,
}))
} else {
self.enter_scope();
let then_body = self.lower_block_to_place(consequence, place, fx);
self.exit_scope();
let inner = self.lower_else_chain(
next_alternative,
then_body.ends_with_diverge(),
place,
fx,
);
ElseArm::ElseIf(Box::new(IfPlan {
directive: String::new(),
condition_setup,
condition,
then_body,
else_arm: inner,
}))
}
} else if preceding_diverges {
let body = self.lower_block_to_place(alternative, place, fx);
ElseArm::Else { body, inline: true }
} else {
self.enter_scope();
let body = self.lower_block_to_place(alternative, place, fx);
self.exit_scope();
ElseArm::Else {
body,
inline: false,
}
}
}
}