use crate::EmitEffects;
use crate::Planner;
use crate::ReturnContext;
use crate::context::expression::ExpressionContext;
use crate::is_order_sensitive;
use crate::patterns::binding_decls::pattern_has_bindings;
use crate::patterns::sites::PatternSubject;
use crate::plan::bodies::{LoopPlan, LoweredBlock, LoweredStatement};
use crate::types::native::NativeGoType;
use crate::types::shape::RangeShape;
use syntax::ast::{Binding, Expression, Pattern};
use syntax::types::Type;
impl Planner<'_> {
pub(crate) fn lower_for_statement(
&mut self,
full_expression: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> LoweredStatement {
let Expression::For {
binding,
iterable,
body,
needs_label,
..
} = full_expression
else {
unreachable!("lower_for_statement requires a For expression");
};
let iterable = iterable.as_ref();
let body = body.as_ref();
let needs_label = *needs_label;
let iterable_ty = iterable.get_type();
let is_range = matches!(iterable, Expression::Range { .. });
let stored_range = (!is_range)
.then(|| self.range_shape(&iterable_ty))
.flatten()
.filter(|rs| {
matches!(
rs,
RangeShape::Range | RangeShape::RangeInclusive | RangeShape::RangeFrom
)
});
let string_view = recognize_string_view_loop(binding, iterable);
let is_simple = self.for_loop_is_simple(binding, iterable);
let map_tuple = self.for_loop_is_map_tuple(binding, iterable);
let directive = self.maybe_line_directive(&full_expression.get_span());
self.push_loop("_");
self.set_current_loop_label_if_needed(needs_label);
let label = self.current_loop_label().map(str::to_string);
let (prologue, header, lowered_body) = if is_range {
self.lower_range_for(binding, iterable, body, return_ctx, fx)
} else if let Some(range_shape) = stored_range {
self.lower_stored_range_for(binding, iterable, range_shape, body, return_ctx, fx)
} else if let Some((kind, receiver)) = string_view {
match kind {
StringViewKind::Runes => {
self.lower_runes_for(binding, receiver, body, return_ctx, fx)
}
StringViewKind::Bytes => {
self.lower_bytes_for(binding, receiver, body, return_ctx, fx)
}
}
} else if map_tuple {
self.lower_map_tuple_for(binding, iterable, body, return_ctx, fx)
} else if is_simple {
self.lower_simple_for(binding, iterable, body, return_ctx, fx)
} else {
self.lower_pattern_site_for(binding, iterable, body, return_ctx, fx)
};
self.pop_loop();
LoweredStatement::Loop(LoopPlan {
directive,
prologue,
label,
header,
body: lowered_body,
})
}
fn lower_stored_range_for(
&mut self,
binding: &Binding,
iterable: &Expression,
range_shape: RangeShape,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
self.enter_scope();
let mut prologue = String::new();
let range_var = if self.is_unmutated_identifier(iterable) {
self.emit_operand(&mut prologue, iterable, ExpressionContext::value(), fx)
} else {
self.emit_force_capture(&mut prologue, iterable, "_range", fx)
};
let loop_var = self.bind_loop_pattern(&binding.pattern, Some("_i"));
let header = match range_shape {
RangeShape::Range => format!(
"for {} := {}.Start; {} < {}.End; {}++ {{\n",
loop_var, range_var, loop_var, range_var, loop_var
),
RangeShape::RangeInclusive => format!(
"for {} := {}.Start; {} <= {}.End; {}++ {{\n",
loop_var, range_var, loop_var, range_var, loop_var
),
RangeShape::RangeFrom => format!(
"for {} := {}.Start; ; {}++ {{\n",
loop_var, range_var, loop_var
),
RangeShape::RangeTo | RangeShape::RangeToInclusive => {
unreachable!("RangeTo/RangeToInclusive are not iterable")
}
};
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
self.exit_scope();
(prologue, header, lowered_body)
}
fn lower_runes_for(
&mut self,
binding: &Binding,
receiver: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
self.enter_scope();
let mut prologue = String::new();
let receiver_str =
self.emit_operand(&mut prologue, receiver, ExpressionContext::value(), fx);
let loop_var = self.bind_loop_pattern(&binding.pattern, None);
let header = if loop_var == "_" {
format!("for range {} {{\n", receiver_str)
} else {
format!("for _, {} := range {} {{\n", loop_var, receiver_str)
};
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
self.exit_scope();
(prologue, header, lowered_body)
}
fn lower_bytes_for(
&mut self,
binding: &Binding,
receiver: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
self.enter_scope();
let mut prologue = String::new();
let receiver_var = if self.is_unmutated_identifier(receiver) {
self.emit_operand(&mut prologue, receiver, ExpressionContext::value(), fx)
} else {
self.emit_force_capture(&mut prologue, receiver, "_s", fx)
};
let index_var = self.fresh_var(Some("_i"));
let loop_var = self.bind_loop_pattern(&binding.pattern, None);
let mut header = format!(
"for {} := 0; {} < len({}); {}++ {{\n",
index_var, index_var, receiver_var, index_var
);
if loop_var != "_" {
header.push_str(&format!(
"{} := {}[{}]\n",
loop_var, receiver_var, index_var
));
}
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
self.exit_scope();
(prologue, header, lowered_body)
}
fn capture_iterable_operand(
&mut self,
iterable: &Expression,
fx: &mut EmitEffects,
) -> (String, String, bool) {
let (prologue, iter_raw) = self.capture_emission(&mut String::new(), |this, buffer| {
this.emit_operand(buffer, iterable, ExpressionContext::value(), fx)
});
let iterable_ty = iterable.get_type();
let iter_expression = if iterable_ty.is_ref() {
format!("*{}", iter_raw)
} else {
iter_raw
};
let is_channel = self
.native_shape(&iterable_ty)
.is_some_and(|s| matches!(s.kind, NativeGoType::Channel | NativeGoType::Receiver));
(prologue, iter_expression, is_channel)
}
fn lower_simple_for(
&mut self,
binding: &Binding,
iterable: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
let (prologue, iter_expression, is_channel) = self.capture_iterable_operand(iterable, fx);
self.enter_scope();
let loop_var = self.bind_loop_pattern(&binding.pattern, None);
let header = if loop_var == "_" {
format!("for range {} {{\n", iter_expression)
} else if is_channel {
format!("for {} := range {} {{\n", loop_var, iter_expression)
} else {
format!("for _, {} := range {} {{\n", loop_var, iter_expression)
};
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
self.exit_scope();
(prologue, header, lowered_body)
}
fn lower_map_tuple_for(
&mut self,
binding: &Binding,
iterable: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
let Pattern::Tuple { elements, .. } = &binding.pattern else {
unreachable!("lower_map_tuple_for requires a tuple pattern");
};
let first = &elements[0];
let second = &elements[1];
let (prologue, iter_expression, _) = self.capture_iterable_operand(iterable, fx);
let first_is_simple =
matches!(first, Pattern::Identifier { .. } | Pattern::WildCard { .. });
let second_is_simple = matches!(
second,
Pattern::Identifier { .. } | Pattern::WildCard { .. }
);
self.enter_scope();
let (header, lowered_body) = if first_is_simple && second_is_simple {
self.lower_map_tuple_simple_body(first, second, &iter_expression, body, return_ctx, fx)
} else {
self.lower_map_tuple_compound_body(
first,
second,
&binding.ty,
&iter_expression,
body,
return_ctx,
fx,
)
};
self.exit_scope();
(prologue, header, lowered_body)
}
fn lower_map_tuple_simple_body(
&mut self,
first: &Pattern,
second: &Pattern,
iter_expression: &str,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, LoweredBlock) {
let first_is_discard =
matches!(first, Pattern::WildCard { .. }) || self.go_name_for_binding(first).is_none();
let second_is_discard = matches!(second, Pattern::WildCard { .. })
|| self.go_name_for_binding(second).is_none();
let header = if first_is_discard && second_is_discard {
format!("for range {} {{\n", iter_expression)
} else {
let key = self.bind_loop_pattern(first, None);
let value = self.bind_loop_pattern(second, None);
format!("for {}, {} := range {} {{\n", key, value, iter_expression)
};
(header, self.lower_block_as_body(body, return_ctx, fx))
}
#[allow(clippy::too_many_arguments)]
fn lower_map_tuple_compound_body(
&mut self,
first: &Pattern,
second: &Pattern,
binding_ty: &Type,
iter_expression: &str,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, LoweredBlock) {
let element_tys: &[Type] = match binding_ty {
Type::Tuple(tys) => tys.as_slice(),
_ => &[],
};
let first_ty = element_tys.first().unwrap_or(binding_ty);
let second_ty = element_tys.get(1).unwrap_or(binding_ty);
let key_var = self.fresh_var(Some("key"));
let value_var = self.fresh_var(Some("value"));
let header = format!(
"for {}, {} := range {} {{\n",
key_var, value_var, iter_expression
);
let mut bindings = String::new();
self.emit_irrefutable_pattern_site(
&mut bindings,
PatternSubject::for_value(key_var.clone()),
first,
None,
first_ty,
fx,
);
self.emit_irrefutable_pattern_site(
&mut bindings,
PatternSubject::for_value(value_var.clone()),
second,
None,
second_ty,
fx,
);
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
let mut inner = vec![LoweredStatement::RawGo(bindings)];
inner.extend(lowered_body.statements);
let body_block = LoweredBlock { statements: inner };
let mut statements = Vec::new();
if !body_block.references_var(&value_var) {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", value_var)));
}
if !body_block.references_var(&key_var) {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", key_var)));
}
statements.extend(body_block.statements);
(header, LoweredBlock { statements })
}
fn lower_pattern_site_for(
&mut self,
binding: &Binding,
iterable: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
let (prologue, iter_expression, is_channel) = self.capture_iterable_operand(iterable, fx);
self.enter_scope();
let (header, body_block) = if !pattern_has_bindings(&binding.pattern) {
let header = format!("for range {} {{\n", iter_expression);
(header, self.lower_block_as_body(body, return_ctx, fx))
} else {
let item_var = self.fresh_var(Some("item"));
let header = if is_channel {
format!("for {} := range {} {{\n", item_var, iter_expression)
} else {
format!("for _, {} := range {} {{\n", item_var, iter_expression)
};
let mut bindings = String::new();
self.emit_irrefutable_pattern_site(
&mut bindings,
PatternSubject::for_value(item_var.clone()),
&binding.pattern,
binding.typed_pattern.as_ref(),
&binding.ty,
fx,
);
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
let mut inner = vec![LoweredStatement::RawGo(bindings)];
inner.extend(lowered_body.statements);
let body_block = LoweredBlock { statements: inner };
let mut statements = Vec::new();
if !body_block.references_var(&item_var) {
statements.push(LoweredStatement::RawGo(format!("_ = {}\n", item_var)));
}
statements.extend(body_block.statements);
(header, LoweredBlock { statements })
};
self.exit_scope();
(prologue, header, body_block)
}
fn lower_range_for(
&mut self,
binding: &Binding,
iterable: &Expression,
body: &Expression,
return_ctx: &ReturnContext,
fx: &mut EmitEffects,
) -> (String, String, LoweredBlock) {
let Expression::Range {
start,
end,
inclusive,
..
} = iterable
else {
unreachable!("lower_range_for requires a Range iterable");
};
let mut prologue = String::new();
let mut start_expression = match start {
Some(s) => self.emit_operand(&mut prologue, s, ExpressionContext::value(), fx),
None => "0".to_string(),
};
let checkpoint = prologue.len();
let end_expression = end
.as_ref()
.map(|e| self.emit_force_capture(&mut prologue, e, "_bound", fx));
if prologue.len() > checkpoint && start.as_ref().is_some_and(|s| is_order_sensitive(s)) {
let var = self.fresh_var(Some("start"));
self.declare(&var);
let statement = format!("{} := {}\n", var, start_expression);
prologue.insert_str(checkpoint, &statement);
start_expression = var;
}
self.enter_scope();
let loop_var = self.bind_loop_pattern(&binding.pattern, Some("_i"));
let header = match end_expression {
Some(end_expression) => {
let operator = if *inclusive { "<=" } else { "<" };
format!(
"for {} := {}; {} {} {}; {}++ {{\n",
loop_var, start_expression, loop_var, operator, end_expression, loop_var
)
}
None => format!(
"for {} := {}; ; {}++ {{\n",
loop_var, start_expression, loop_var
),
};
let lowered_body = self.lower_block_as_body(body, return_ctx, fx);
self.exit_scope();
(prologue, header, lowered_body)
}
fn for_loop_is_simple(&self, binding: &Binding, iterable: &Expression) -> bool {
if !matches!(
&binding.pattern,
Pattern::Identifier { .. } | Pattern::WildCard { .. }
) {
return false;
}
if matches!(iterable, Expression::Range { .. }) {
return false;
}
let iterable_ty = iterable.get_type();
if let Some(range_shape) = self.range_shape(&iterable_ty)
&& matches!(
range_shape,
RangeShape::Range | RangeShape::RangeInclusive | RangeShape::RangeFrom
)
{
return false;
}
if recognize_string_view_loop(binding, iterable).is_some() {
return false;
}
true
}
fn for_loop_is_map_tuple(&self, binding: &Binding, iterable: &Expression) -> bool {
let Pattern::Tuple { elements, .. } = &binding.pattern else {
return false;
};
elements.len() == 2 && self.is_map_tuple_iterable(&iterable.get_type())
}
fn bind_loop_pattern(&mut self, pattern: &Pattern, fallback: Option<&str>) -> String {
if let Pattern::Identifier { identifier, .. } = pattern
&& let Some(mut go_name) = self.go_name_for_binding(pattern)
{
if self.scope.has_binding_for_go_name(&go_name) {
go_name = self.fresh_var(Some(&go_name));
}
return self.scope.bind(identifier, go_name);
}
match fallback {
Some(hint) => self.fresh_var(Some(hint)),
None => "_".to_string(),
}
}
fn is_map_tuple_iterable(&self, iterable_ty: &Type) -> bool {
self.native_shape(iterable_ty)
.is_some_and(|s| matches!(s.kind, NativeGoType::Map | NativeGoType::EnumeratedSlice))
}
fn is_unmutated_identifier(&self, expression: &Expression) -> bool {
if let Expression::Identifier {
binding_id: Some(id),
..
} = expression
{
!self.facts.is_mutated(*id)
} else {
false
}
}
}
#[derive(Clone, Copy)]
enum StringViewKind {
Bytes,
Runes,
}
fn recognize_string_view_loop<'a>(
binding: &'a Binding,
iterable: &'a Expression,
) -> Option<(StringViewKind, &'a Expression)> {
if !matches!(
&binding.pattern,
Pattern::Identifier { .. } | Pattern::WildCard { .. }
) {
return None;
}
let Expression::Call {
expression, args, ..
} = iterable
else {
return None;
};
if !args.is_empty() {
return None;
}
let Expression::DotAccess {
expression: receiver,
member,
..
} = expression.as_ref()
else {
return None;
};
if !receiver.get_type().has_name("string") {
return None;
}
match member.as_str() {
"bytes" => Some((StringViewKind::Bytes, receiver.as_ref())),
"runes" => Some((StringViewKind::Runes, receiver.as_ref())),
_ => None,
}
}