use crate::Emitter;
use crate::go::names::go_name;
use crate::go::patterns::decision_tree;
use crate::go::statements::assignments::is_lvalue_chain;
use crate::go::types::emitter::Position;
use crate::go::utils::output_ends_with_diverge;
use crate::go::write_line;
use syntax::ast::{Expression, Pattern, TypedPattern};
impl Emitter<'_> {
pub(crate) fn emit_if(
&mut self,
output: &mut String,
condition: &Expression,
consequence: &Expression,
alternative: &Expression,
) {
let condition_string = self.emit_condition_operand(output, condition);
let condition_string = wrap_if_struct_literal(condition_string);
write_line!(output, "if {} {{", condition_string);
self.enter_scope();
self.emit_in_position(output, consequence);
self.exit_scope();
self.emit_else_chain(output, alternative);
}
fn emit_else_chain(&mut self, output: &mut String, alternative: &Expression) {
let is_empty_alternative = match alternative {
Expression::Unit { .. } => true,
Expression::Block { items, .. } => items.is_empty(),
_ => false,
};
if is_empty_alternative {
output.push_str("}\n");
return;
}
if let Expression::If {
condition,
consequence,
alternative: next_alternative,
..
} = alternative
{
let condition_string = self.emit_condition_operand(output, condition);
let condition_string = wrap_if_struct_literal(condition_string);
write_line!(output, "}} else if {} {{", condition_string);
self.enter_scope();
self.emit_in_position(output, consequence);
self.exit_scope();
self.emit_else_chain(output, next_alternative);
} else if output_ends_with_diverge(output) {
output.push_str("}\n");
self.emit_in_position(output, alternative);
} else {
output.push_str("} else {\n");
self.enter_scope();
self.emit_in_position(output, alternative);
self.exit_scope();
output.push_str("}\n");
}
}
pub(crate) fn emit_branch_header(
&mut self,
output: &mut String,
condition: &str,
is_catchall: bool,
is_first: bool,
) {
if is_first {
if is_catchall {
output.push_str("if true {\n");
} else {
write_line!(output, "if {} {{", condition);
}
} else {
self.exit_scope();
if is_catchall {
output.push_str("} else {\n");
} else {
write_line!(output, "}} else if {} {{", condition);
}
}
self.enter_scope();
}
pub(crate) fn emit_while_let(
&mut self,
output: &mut String,
pattern: &Pattern,
typed_pattern: Option<&TypedPattern>,
scrutinee: &Expression,
body: &Expression,
needs_label: bool,
) {
self.maybe_set_loop_label(needs_label);
if let Some(label) = self.current_loop_label() {
write_line!(output, "{}:", label);
}
output.push_str("for {\n");
let inlined = if let Expression::Identifier { value, .. } = scrutinee {
let name = value.to_string();
let has_collision = Self::pattern_binds_name(pattern, &name);
if !has_collision && !name.contains('.') {
Some(
self.scope
.bindings
.get(&name)
.map(|s| s.to_string())
.unwrap_or_else(|| go_name::escape_reserved(&name).into_owned()),
)
} else {
None
}
} else {
None
};
let subject_var = inlined.unwrap_or_else(|| {
let var = self.fresh_var(Some("subject"));
let expression = self.emit_operand(output, scrutinee);
write_line!(output, "{} := {}", var, expression);
var
});
if let Pattern::Or { patterns, .. } = pattern
&& Self::pattern_has_bindings(pattern)
{
for (i, alternative_pattern) in patterns.iter().enumerate() {
let (checks, bindings) =
decision_tree::collect_pattern_info(self, alternative_pattern, None);
let condition = decision_tree::render_condition(&checks, &subject_var);
self.emit_branch_header(output, &condition, false, i == 0);
decision_tree::emit_tree_bindings(self, output, &bindings, &subject_var);
self.emit_block(output, body);
}
self.emit_while_let_break_else(output);
return;
}
let (checks, bindings) = decision_tree::collect_pattern_info(self, pattern, typed_pattern);
let condition = decision_tree::render_condition(&checks, &subject_var);
write_line!(output, "if {} {{", condition);
self.enter_scope();
if !matches!(pattern, Pattern::Or { .. }) {
decision_tree::emit_tree_bindings(self, output, &bindings, &subject_var);
}
self.emit_block(output, body);
self.emit_while_let_break_else(output);
}
fn emit_while_let_break_else(&mut self, output: &mut String) {
self.exit_scope();
output.push_str("} else {\n");
self.enter_scope();
if let Some(label) = self.current_loop_label() {
write_line!(output, "break {}", label);
} else {
output.push_str("break\n");
}
self.exit_scope();
output.push_str("}\n");
output.push_str("}\n");
}
pub(crate) fn emit_branching_directly(&mut self, output: &mut String, expression: &Expression) {
match expression {
Expression::If {
condition,
consequence,
alternative,
..
} => {
self.emit_if(output, condition, consequence, alternative);
}
Expression::Match {
subject, arms, ty, ..
} => {
self.emit_match(output, subject, arms, ty);
}
Expression::Select { arms, .. } => {
self.emit_select(output, arms);
}
_ => unreachable!("expected if/match/select"),
}
}
pub(crate) fn emit_block(&mut self, output: &mut String, expression: &Expression) {
let Expression::Block { items, .. } = expression else {
self.emit_statement(output, expression);
return;
};
for item in items {
self.emit_statement(output, item);
}
}
pub(crate) fn emit_block_to_var_with_braces(
&mut self,
output: &mut String,
expression: &Expression,
var: &str,
has_go_braces: bool,
) {
let is_block = matches!(expression, Expression::Block { .. });
let items: &[Expression] = if let Expression::Block { items, .. } = expression {
items
} else {
std::slice::from_ref(expression)
};
if is_block {
if has_go_braces {
self.enter_scope();
} else {
self.scope.bindings.save();
}
}
let Some((last, rest)) = items.split_last() else {
if is_block {
if has_go_braces {
self.exit_scope();
} else {
self.scope.bindings.restore();
}
}
return;
};
let is_new_target = self.scope.assign_targets.insert(var.to_string());
for item in rest {
self.emit_statement(output, item);
}
if matches!(
last,
Expression::Return { .. }
| Expression::Break { .. }
| Expression::Continue { .. }
| Expression::Let { .. }
| Expression::While { .. }
| Expression::WhileLet { .. }
| Expression::For { .. }
| Expression::Const { .. }
) {
self.emit_statement(output, last);
} else if last.get_type().is_never() {
self.emit_statement(output, last);
if !Self::is_go_never(last) {
output.push_str("panic(\"unreachable\")\n");
}
} else if last.get_type().resolve().is_unit()
&& matches!(last.unwrap_parens(), Expression::Call { .. })
{
let call_str = self.emit_value(output, last);
if !call_str.is_empty() {
write_line!(output, "{call_str}");
}
write_line!(output, "{var} = struct{{}}{{}}");
} else if !self.emit_append_to_var(output, var, last) {
match last {
Expression::If { .. } | Expression::Match { .. } | Expression::Select { .. } => {
self.with_position(Position::Assign(var.to_string()), |this| {
this.emit_branching_directly(output, last);
});
}
_ => {
let expression_string = self.emit_value(output, last);
write_line!(output, "{} = {}", var, expression_string);
}
}
}
if is_new_target {
self.scope.assign_targets.remove(var);
}
if is_block {
if has_go_braces {
self.exit_scope();
} else {
self.scope.bindings.restore();
}
}
}
fn emit_append_to_var(&mut self, output: &mut String, var: &str, last: &Expression) -> bool {
let Expression::Call {
expression: func,
args,
..
} = last
else {
return false;
};
if !self.is_slice_append_or_extend(func) {
return false;
}
let Expression::DotAccess {
expression: receiver,
member,
..
} = func.as_ref()
else {
return true;
};
let is_extend = member == "extend";
let unwrapped = receiver.unwrap_parens();
let receiver_is_lvalue =
is_lvalue_chain(unwrapped) && !self.contains_newtype_access(unwrapped);
if receiver_is_lvalue {
let receiver_lv = self.emit_left_value_capturing(output, unwrapped, false);
let args_str = self.emit_append_args(output, args, is_extend);
write_line!(output, "{} = append({}, {})", var, receiver_lv, args_str);
} else {
let value_str = self.emit_value(output, last);
write_line!(output, "{} = {}", var, value_str);
}
true
}
pub(crate) fn emit_block_to_tail(&mut self, output: &mut String, expression: &Expression) {
let items: &[Expression] = if let Expression::Block { items, .. } = expression {
items
} else {
std::slice::from_ref(expression)
};
let Some((last, rest)) = items.split_last() else {
return;
};
for item in rest {
self.emit_statement(output, item);
}
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 { .. }) {
self.emit_statement(output, last);
}
return;
}
if last.get_type().is_never() {
let directive = self.maybe_line_directive(&return_span);
output.push_str(&directive);
self.emit_statement(output, last);
if !Self::is_go_never(last) {
output.push_str("panic(\"unreachable\")\n");
}
return;
}
let directive = self.maybe_line_directive(&return_span);
match last {
Expression::If { .. } | Expression::Match { .. } | Expression::Select { .. } => {
output.push_str(&directive);
self.emit_branching_directly(output, last);
}
_ => {
let expression_string = self.emit_value(output, last);
write_line!(output, "{}return {}", directive, expression_string);
}
}
}
pub(crate) fn emit_in_position(&mut self, output: &mut String, expression: &Expression) {
match &self.position {
Position::Statement | Position::Expression => {
self.emit_block(output, expression);
}
Position::Assign(var) => {
let var = var.clone();
if expression.get_type().is_result() || expression.get_type().is_option() {
self.emit_option_result_assignment(output, &var, expression);
} else {
self.emit_block_to_var_with_braces(output, expression, &var, false);
}
}
Position::Tail => self.emit_block_to_tail(output, expression),
}
}
}
impl Emitter<'_> {
pub(crate) fn maybe_set_loop_label(&mut self, needs_label: bool) {
if needs_label {
let label = self.fresh_var(Some("loop"));
if let Some(ctx) = self.scope.loop_stack.last_mut() {
ctx.label = Some(label);
}
}
}
pub(crate) fn emit_labeled_loop(
&mut self,
output: &mut String,
header: &str,
body: &Expression,
needs_label: bool,
) {
self.maybe_set_loop_label(needs_label);
if let Some(label) = self.current_loop_label() {
write_line!(output, "{}:", label);
}
output.push_str(header);
self.enter_scope();
self.emit_block(output, body);
self.exit_scope();
output.push_str("}\n");
}
}
pub(crate) fn wrap_if_struct_literal(condition: String) -> String {
if condition.contains('{') {
format!("({})", condition)
} else {
condition
}
}