use crate::Emitter;
use crate::go::control_flow::branching::wrap_if_struct_literal;
use crate::go::control_flow::fallible::Fallible;
use crate::go::patterns::decision_tree;
use crate::go::types::emitter::Position;
use crate::go::utils::{DiscardGuard, requires_temp_var, try_flip_comparison};
use crate::go::write_line;
use syntax::ast::{Binding, Expression, Pattern, UnaryOperator};
enum LetKind {
SimpleIdentifier,
Discard,
ComplexPattern,
MultiValueCall,
Propagate,
LetElse,
}
pub(crate) struct LetEmitter<'a, 'e> {
emitter: &'a mut Emitter<'e>,
binding: &'a Binding,
value: &'a Expression,
else_block: Option<&'a Expression>,
mutable: bool,
}
impl<'a, 'e> LetEmitter<'a, 'e> {
pub(crate) fn new(
emitter: &'a mut Emitter<'e>,
binding: &'a Binding,
value: &'a Expression,
else_block: Option<&'a Expression>,
mutable: bool,
) -> Self {
Self {
emitter,
binding,
value,
else_block,
mutable,
}
}
pub(crate) fn emit(mut self, output: &mut String) {
if self.value.get_type().is_never() {
self.emit_never_binding(output);
return;
}
match self.classify() {
LetKind::LetElse => self.emit_let_else(output),
LetKind::SimpleIdentifier => self.emit_simple_identifier(output),
LetKind::Discard => self.emit_discard(output),
LetKind::Propagate => self.emit_propagate(output),
LetKind::MultiValueCall => self.emit_multi_value_call(output),
LetKind::ComplexPattern => self.emit_complex_pattern(output),
}
}
fn emit_never_binding(&mut self, output: &mut String) {
if let Pattern::Identifier { identifier, .. } = &self.binding.pattern
&& let Some(raw_go_name) = self.emitter.go_name_for_binding(&self.binding.pattern)
{
let go_identifier = self.emitter.scope.bindings.add(identifier, &raw_go_name);
self.emitter.try_declare(&go_identifier);
let var_ty = self.emitter.go_type_as_string(&self.binding.ty);
write_line!(output, "var {} {}", go_identifier, var_ty);
}
self.emitter.emit_statement(output, self.value);
}
fn classify(&self) -> LetKind {
if self.else_block.is_some() {
return LetKind::LetElse;
}
match &self.binding.pattern {
Pattern::Identifier { .. } => {
if matches!(self.value, Expression::Propagate { .. }) {
LetKind::Propagate
} else {
LetKind::SimpleIdentifier
}
}
Pattern::WildCard { .. } => LetKind::Discard,
Pattern::Tuple { elements, .. } => {
let all_unused = elements.iter().all(|el| match el {
Pattern::WildCard { .. } => true,
Pattern::Identifier { .. } => self.emitter.ctx.unused.is_unused_binding(el),
_ => false,
});
if all_unused {
LetKind::Discard
} else if self.can_use_multi_value_optimization() {
LetKind::MultiValueCall
} else {
LetKind::ComplexPattern
}
}
_ => LetKind::ComplexPattern,
}
}
fn can_use_multi_value_optimization(&self) -> bool {
let Pattern::Tuple { .. } = &self.binding.pattern else {
return false;
};
self.emitter
.resolve_go_call_strategy(self.value)
.is_some_and(|s| s.is_multi_return())
&& !self.value.get_type().is_result()
&& extract_simple_tuple_vars(&self.binding.pattern).is_some()
}
fn emit_simple_identifier(&mut self, output: &mut String) {
let Pattern::Identifier { identifier, .. } = &self.binding.pattern else {
unreachable!("emit_simple_identifier called with non-identifier pattern");
};
if self.value.get_type().resolve().is_unit()
&& matches!(self.value.unwrap_parens(), Expression::Call { .. })
{
let value_expression = self.emitter.emit_value(output, self.value);
write_line!(output, "{}", value_expression);
if let Some(raw_go_name) = self.emitter.go_name_for_binding(&self.binding.pattern) {
let go_identifier = crate::go::escape_reserved(&raw_go_name);
if self.emitter.is_declared(&go_identifier) {
let fresh = self.emitter.fresh_var(Some(identifier));
self.emitter.declare(&fresh);
write_line!(output, "{} := struct{{}}{{}}", fresh);
self.emitter.scope.bindings.add(identifier, &fresh);
} else {
let go_identifier = self.emitter.scope.bindings.add(identifier, &raw_go_name);
self.emitter.try_declare(&go_identifier);
write_line!(output, "{} := struct{{}}{{}}", go_identifier);
}
}
return;
}
let Some(raw_go_name) = self.emitter.go_name_for_binding(&self.binding.pattern) else {
if let Pattern::Identifier { identifier, .. } = &self.binding.pattern {
self.emitter.scope.bindings.add(identifier.as_str(), "_");
}
if requires_temp_var(self.value) {
self.emit_temp_var_binding(output, "_");
} else {
self.emitter.emit_discard(output, self.value);
}
return;
};
if requires_temp_var(self.value) {
let go_identifier = crate::go::escape_reserved(&raw_go_name);
if self.emitter.is_declared(&go_identifier)
|| expression_contains_binding(self.value, identifier)
{
let fresh = self.emitter.fresh_var(Some(identifier));
self.emit_temp_var_binding(output, &fresh);
self.emitter.scope.bindings.add(identifier, &fresh);
} else {
self.emitter.scope.bindings.add(identifier, &raw_go_name);
self.emit_temp_var_binding(output, &go_identifier);
}
} else {
let value_expression = self.emitter.emit_value(output, self.value);
let value_expression = self.emitter.maybe_wrap_as_go_interface(
value_expression,
&self.value.get_type(),
&self.binding.ty,
);
let value_expression = if self.is_mutable_subslice_binding() {
self.emitter.flags.needs_slices = true;
format!("slices.Clone({})", value_expression)
} else {
value_expression
};
let go_identifier = self.emitter.scope.bindings.add(identifier, &raw_go_name);
let is_new = self.emitter.try_declare(&go_identifier);
if !is_new || self.emitter.scope.assign_targets.contains(&go_identifier) {
let fresh = self.emitter.fresh_var(Some(identifier));
self.emitter.scope.bindings.add(identifier, &fresh);
self.emitter.try_declare(&fresh);
write_line!(output, "{} := {}", fresh, value_expression);
} else if self.needs_explicit_type_declaration() {
let var_ty = self.emitter.go_type_as_string(&self.binding.ty);
write_line!(
output,
"var {} {} = {}",
go_identifier,
var_ty,
value_expression
);
} else {
write_line!(output, "{} := {}", go_identifier, value_expression);
}
}
}
fn is_mutable_subslice_binding(&self) -> bool {
if !self.mutable {
return false;
}
let value = self.value.unwrap_parens();
let Expression::IndexedAccess {
expression, index, ..
} = value
else {
return false;
};
let is_range_index = matches!(**index, Expression::Range { .. })
|| index.get_type().resolve().get_name().is_some_and(|n| {
matches!(
n,
"Range" | "RangeInclusive" | "RangeFrom" | "RangeTo" | "RangeToInclusive"
)
});
if !is_range_index {
return false;
}
let collection_ty = match expression.as_ref() {
Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner,
..
} => {
let resolved = inner.get_type().resolve();
resolved.inner().unwrap_or(resolved)
}
other => other.get_type().resolve(),
};
collection_ty.has_name("Slice")
}
fn needs_explicit_type_declaration(&self) -> bool {
let binding_ty = self.binding.ty.resolve();
if self.emitter.as_interface(&binding_ty).is_some() {
let value_ty = self.value.get_type().resolve();
if binding_ty != value_ty {
return true;
}
}
let inner_value = unwrap_unary_negation(self.value);
match inner_value {
Expression::Literal { literal, .. } => match literal {
syntax::ast::Literal::Integer { .. } => {
let type_name = binding_ty.get_name();
!matches!(type_name, Some("int") | None)
}
syntax::ast::Literal::Float { .. } => {
let type_name = binding_ty.get_name();
!matches!(type_name, Some("float64") | None)
}
_ => false,
},
_ => false,
}
}
fn emit_temp_var_binding(&mut self, output: &mut String, identifier: &str) {
if !self.emitter.is_declared(identifier) {
let value_ty = self.value.get_type();
let ty = if value_ty.is_unit() || value_ty.is_never() {
let binding_ty = self.binding.ty.resolve();
if !binding_ty.is_unit() && !binding_ty.is_variable() {
&self.binding.ty
} else {
&value_ty
}
} else {
&value_ty
};
let has_variable_ok_ty = matches!(
self.value,
Expression::TryBlock { .. } | Expression::RecoverBlock { .. }
) && !ty.is_variable()
&& ty.ok_type().is_variable();
let var_ty = if has_variable_ok_ty {
let binding_ty = self.binding.ty.resolve();
if !binding_ty.is_variable() && !binding_ty.ok_type().is_variable() {
self.emitter.go_type_as_string(&binding_ty)
} else if let Some(ctx_ty) = self.emitter.current_return_context.clone() {
if Fallible::from_type(&ctx_ty).is_some() {
self.emitter.go_type_as_string(&ctx_ty)
} else {
self.emitter.go_type_as_string(ty)
}
} else {
self.emitter.go_type_as_string(ty)
}
} else {
self.emitter.go_type_as_string(ty)
};
write_line!(output, "var {} {}", identifier, var_ty);
self.emitter.try_declare(identifier);
}
let saved_target_ty = self
.emitter
.assign_target_ty
.replace(self.binding.ty.clone());
match self.value {
Expression::If {
condition,
consequence,
alternative,
..
} => {
self.emitter
.with_position(Position::Assign(identifier.to_string()), |this| {
this.emit_if(output, condition, consequence, alternative)
});
}
Expression::IfLet { .. } => {
unreachable!("IfLet should be desugared to Match before emit")
}
Expression::Match {
subject, arms, ty, ..
} => {
self.emitter
.with_position(Position::Assign(identifier.to_string()), |this| {
this.emit_match(output, subject, arms, ty)
});
}
Expression::Block { items, .. } => {
let needs_braces = items.len() > 1;
if needs_braces {
output.push_str("{\n");
}
self.emitter.emit_block_to_var_with_braces(
output,
self.value,
identifier,
needs_braces,
);
if needs_braces {
output.push_str("}\n");
}
}
Expression::Select { arms, .. } => {
self.emitter
.with_position(Position::Assign(identifier.to_string()), |this| {
this.emit_select(output, arms)
});
}
Expression::Loop {
body, needs_label, ..
} => {
self.emitter.push_loop(identifier);
self.emitter
.emit_labeled_loop(output, "for {\n", body, *needs_label);
self.emitter.pop_loop();
}
Expression::Propagate { .. }
| Expression::TryBlock { .. }
| Expression::RecoverBlock { .. } => {
let value_expression = self.emitter.emit_value(output, self.value);
write_line!(output, "{} = {}", identifier, value_expression);
}
_ => unreachable!("requires_temp_var returned true for unexpected expression"),
}
self.emitter.assign_target_ty = saved_target_ty;
}
fn emit_discard(&mut self, output: &mut String) {
self.emitter.emit_discard(output, self.value);
}
fn emit_propagate(&mut self, output: &mut String) {
let Pattern::Identifier { identifier, .. } = &self.binding.pattern else {
unreachable!("emit_propagate called with non-identifier pattern");
};
let Some(go_name) = self.emitter.go_name_for_binding(&self.binding.pattern) else {
self.emitter.scope.bindings.add(identifier.as_str(), "_");
self.emitter.emit_propagate_to_let(output, "_", self.value);
return;
};
let go_identifier = crate::go::escape_reserved(&go_name).into_owned();
let go_identifier = if self.emitter.is_declared(&go_identifier) {
self.emitter.fresh_var(Some(identifier))
} else {
go_identifier
};
self.emitter
.emit_propagate_to_let(output, &go_identifier, self.value);
self.emitter.scope.bindings.add(identifier, &go_identifier);
self.emitter.try_declare(&go_identifier);
}
fn emit_multi_value_call(&mut self, output: &mut String) {
let Pattern::Tuple { elements, .. } = &self.binding.pattern else {
unreachable!("emit_multi_value_call called with non-tuple pattern");
};
let vars = extract_simple_tuple_vars(&self.binding.pattern)
.expect("multi-value optimization requires simple tuple vars");
let mut any_new = false;
let mut planned: Vec<Option<(&str, String)>> = Vec::new();
let go_vars: Vec<String> = vars
.iter()
.zip(elements.iter())
.map(|(var, pat)| {
if var == "_" {
planned.push(None);
"_".to_string()
} else if let Pattern::Identifier { identifier, .. } = pat
&& let Some(go_name) = self.emitter.go_name_for_binding(pat)
{
let escaped = crate::go::escape_reserved(&go_name).into_owned();
let name = if self.emitter.is_declared(&escaped) {
let fresh = self.emitter.fresh_var(Some(identifier));
any_new = true;
fresh
} else {
any_new = true;
escaped
};
planned.push(Some((identifier, name.clone())));
name
} else {
planned.push(None);
"_".to_string()
}
})
.collect();
let call_str = self.emitter.emit_call(output, self.value, None);
for (identifier, go_name) in planned.iter().flatten() {
self.emitter.scope.bindings.add(*identifier, go_name);
self.emitter.try_declare(go_name);
}
let op = if any_new { ":=" } else { "=" };
write_line!(output, "{} {} {}", go_vars.join(", "), op, call_str);
}
fn emit_complex_pattern(&mut self, output: &mut String) {
if let Expression::Identifier { value, .. } = self.value
&& !value.contains('.')
{
let go_name = self
.emitter
.scope
.bindings
.get(value)
.map(|s| s.to_string())
.unwrap_or_else(|| crate::go::escape_reserved(value).into_owned());
let (_checks, bindings) = decision_tree::collect_pattern_info(
self.emitter,
&self.binding.pattern,
self.binding.typed_pattern.as_ref(),
);
decision_tree::emit_tree_bindings(self.emitter, output, &bindings, &go_name);
return;
}
let temp_var = self.emitter.fresh_var(None);
self.emitter.declare(&temp_var);
let value_expression = self.emitter.emit_value(output, self.value);
write_line!(output, "{} := {}", temp_var, value_expression);
let guard = DiscardGuard::new(output, &temp_var);
let (_checks, bindings) = decision_tree::collect_pattern_info(
self.emitter,
&self.binding.pattern,
self.binding.typed_pattern.as_ref(),
);
decision_tree::emit_tree_bindings(self.emitter, output, &bindings, &temp_var);
guard.finish(output);
}
fn emit_let_else(&mut self, output: &mut String) {
let else_block = self
.else_block
.expect("emit_let_else called without else block");
let (subject_var, needs_guard) = if let Expression::Identifier { value, .. } = self.value {
let has_collision = Emitter::pattern_binds_name(&self.binding.pattern, value);
if !has_collision && !value.contains('.') {
let go_name = self
.emitter
.scope
.bindings
.get(value)
.map(|s| s.to_string())
.unwrap_or_else(|| crate::go::escape_reserved(value).into_owned());
(go_name, false)
} else {
let var = self.emitter.fresh_var(Some("subject"));
self.emitter.declare(&var);
let value_expression = self.emitter.emit_value(output, self.value);
write_line!(output, "{} := {}", var, value_expression);
(var, true)
}
} else {
let var = self.emitter.fresh_var(Some("subject"));
self.emitter.declare(&var);
let value_expression = self.emitter.emit_value(output, self.value);
write_line!(output, "{} := {}", var, value_expression);
(var, true)
};
let subject_guard = if needs_guard {
Some(DiscardGuard::new(output, &subject_var))
} else {
None
};
if let Pattern::Or { patterns, .. } = &self.binding.pattern {
self.emit_or_pattern_let_else(output, patterns, &subject_var, else_block);
if let Some(guard) = subject_guard {
guard.finish(output);
}
return;
}
let (checks, bindings) = decision_tree::collect_pattern_info(
self.emitter,
&self.binding.pattern,
self.binding.typed_pattern.as_ref(),
);
if checks.is_empty() {
decision_tree::emit_tree_bindings(self.emitter, output, &bindings, &subject_var);
} else {
let condition = decision_tree::render_condition(&checks, &subject_var);
let guard = if checks.len() == 1 {
negate_condition(&condition)
} else {
format!("!({})", condition)
};
let guard = wrap_if_struct_literal(guard);
write_line!(output, "if {} {{", guard);
self.emitter.emit_block(output, else_block);
output.push_str("}\n");
decision_tree::emit_tree_bindings(self.emitter, output, &bindings, &subject_var);
}
if let Some(guard) = subject_guard {
guard.finish(output);
}
}
fn emit_or_pattern_let_else(
&mut self,
output: &mut String,
patterns: &[Pattern],
subject_var: &str,
else_block: &Expression,
) {
let outer_snapshot = self.emitter.scope.bindings.snapshot();
self.emitter.emit_binding_declarations_with_type(
output,
&self.binding.pattern,
&self.binding.ty,
self.binding.typed_pattern.as_ref(),
);
let pattern_snapshot = self.emitter.scope.bindings.snapshot();
let collected: Vec<_> = patterns
.iter()
.map(|alt| decision_tree::collect_pattern_info(self.emitter, alt, None))
.collect();
if collected.iter().any(|(checks, _)| checks.is_empty()) {
return;
}
for (i, (checks, bindings)) in collected.iter().enumerate() {
let condition = decision_tree::render_condition(checks, subject_var);
if i == 0 {
write_line!(output, "if {} {{", condition);
} else {
write_line!(output, "}} else if {} {{", condition);
}
decision_tree::emit_tree_assignments(self.emitter, output, bindings, subject_var);
}
self.emitter.scope.bindings.restore_snapshot(outer_snapshot);
output.push_str("} else {\n");
self.emitter.emit_block(output, else_block);
output.push_str("}\n");
self.emitter
.scope
.bindings
.restore_snapshot(pattern_snapshot);
}
}
fn extract_simple_tuple_vars(pattern: &Pattern) -> Option<Vec<String>> {
let Pattern::Tuple { elements, .. } = pattern else {
return None;
};
let mut vars = Vec::with_capacity(elements.len());
for element in elements {
match element {
Pattern::Identifier { identifier, .. } => {
vars.push(identifier.to_string());
}
Pattern::WildCard { .. } => {
vars.push("_".to_string());
}
_ => return None,
}
}
Some(vars)
}
fn unwrap_unary_negation(expression: &Expression) -> &Expression {
match expression {
Expression::Unary {
operator: syntax::ast::UnaryOperator::Negative,
expression,
..
} => expression.as_ref(),
Expression::Paren { expression, .. } => unwrap_unary_negation(expression),
_ => expression,
}
}
fn expression_contains_binding(expression: &Expression, name: &str) -> bool {
match expression {
Expression::Match { arms, .. } => arms
.iter()
.any(|arm| pattern_contains_name(&arm.pattern, name)),
Expression::Block { items, .. } => items.iter().any(|item| match item {
Expression::Let { binding, .. } => pattern_contains_name(&binding.pattern, name),
_ => false,
}),
Expression::If {
consequence,
alternative,
..
} => {
expression_contains_binding(consequence, name)
|| expression_contains_binding(alternative, name)
}
Expression::Select { arms, .. } => arms.iter().any(|arm| {
use syntax::ast::SelectArmPattern;
match &arm.pattern {
SelectArmPattern::Receive { binding, .. } => pattern_contains_name(binding, name),
SelectArmPattern::MatchReceive { arms, .. } => {
arms.iter().any(|a| pattern_contains_name(&a.pattern, name))
}
_ => false,
}
}),
Expression::Loop { body, .. } => expression_contains_binding(body, name),
_ => false,
}
}
fn pattern_contains_name(pattern: &Pattern, name: &str) -> bool {
match pattern {
Pattern::Identifier { identifier, .. } => identifier.as_str() == name,
Pattern::EnumVariant { fields, .. } => {
fields.iter().any(|f| pattern_contains_name(f, name))
}
Pattern::Struct { fields, .. } => {
fields.iter().any(|f| pattern_contains_name(&f.value, name))
}
Pattern::Tuple { elements, .. } => elements.iter().any(|e| pattern_contains_name(e, name)),
Pattern::Slice { prefix, rest, .. } => {
prefix.iter().any(|p| pattern_contains_name(p, name))
|| matches!(rest, syntax::ast::RestPattern::Bind { name: n, .. } if n == name)
}
Pattern::Or { patterns, .. } => patterns.iter().any(|p| pattern_contains_name(p, name)),
Pattern::Literal { .. } | Pattern::Unit { .. } | Pattern::WildCard { .. } => false,
}
}
fn negate_condition(condition: &str) -> String {
try_flip_comparison(condition).unwrap_or_else(|| format!("!({})", condition))
}
impl Emitter<'_> {
pub(crate) fn emit_let(
&mut self,
output: &mut String,
binding: &Binding,
value: &Expression,
else_block: Option<&Expression>,
mutable: bool,
) {
LetEmitter::new(self, binding, value, else_block, mutable).emit(output);
}
pub(crate) fn emit_discard(&mut self, output: &mut String, value: &Expression) {
if let Expression::Propagate { expression, .. } = value.unwrap_parens() {
self.emit_propagate(output, expression, Some("_"));
return;
}
let value_ty = value.get_type().resolve();
if value_ty.is_unit() || value_ty.is_variable() {
let value_expression = self.emit_operand(output, value);
if !value_expression.is_empty() {
if matches!(value.unwrap_parens(), Expression::Call { .. }) {
write_line!(output, "{}", value_expression);
} else {
write_line!(output, "_ = {}", value_expression);
}
}
return;
}
if self
.resolve_go_call_strategy(value)
.is_some_and(|s| s.is_multi_return())
{
let call_str = self.emit_call(output, value, None);
write_line!(output, "{}", call_str);
} else {
let value_expression = self.emit_operand(output, value);
write_line!(output, "_ = {}", value_expression);
}
}
}