use crate::EmitEffects;
use crate::Planner;
use crate::Renderer;
use crate::abi::coercion::{Coercion, CoercionDirection};
use crate::context::expression::ExpressionContext;
use crate::is_order_sensitive;
use crate::names::go_name;
use crate::plan::bodies::{AssignForm, AssignPlan, CompoundKind, LoweredBlock, LoweredStatement};
use crate::plan::values::value_plan_from_statements;
use crate::state::bindings::BindingValue;
use crate::utils::observable_after_mutation;
use syntax::ast::{BinaryOperator, Expression, FormatStringPart, Literal, UnaryOperator};
use syntax::parse::TUPLE_FIELDS;
use syntax::types::Type;
impl Planner<'_> {
pub(crate) fn build_assignment_plan(
&mut self,
target: &Expression,
value: &Expression,
compound_operator: Option<&BinaryOperator>,
directive: String,
fx: &mut EmitEffects,
) -> AssignPlan {
let raw_body = |statements: Vec<LoweredStatement>| LoweredBlock { statements };
let capture_to_vec = |buffer: String| {
if buffer.is_empty() {
Vec::new()
} else {
vec![LoweredStatement::RawGo(buffer)]
}
};
if value.get_type().is_never() {
return AssignPlan {
directive,
form: AssignForm::NeverTyped {
body: raw_body(vec![self.lower_statement(value, fx)]),
},
};
}
if let Some((op, rhs)) = detect_compound_assignment(target, value, compound_operator) {
let is_inc_dec = is_literal_one(rhs)
&& matches!(op, BinaryOperator::Addition | BinaryOperator::Subtraction);
let (kind, rhs_has_setup) = if is_inc_dec {
let kind = if *op == BinaryOperator::Addition {
CompoundKind::Increment
} else {
CompoundKind::Decrement
};
(kind, false)
} else {
let staged = self.stage_operand(rhs, ExpressionContext::value(), fx);
let rhs_has_setup =
!staged.setup.is_empty() || self.rhs_contains_effectful_call(rhs);
let kind = CompoundKind::OpAssign {
op_text: format!("{}", op),
rhs: value_plan_from_statements(staged.setup, staged.value),
};
(kind, rhs_has_setup)
};
let mut target_setup = String::new();
let target_str = if is_order_sensitive(target) {
self.emit_left_value_capturing(&mut target_setup, target, rhs_has_setup, fx)
} else {
self.emit_left_value(&mut target_setup, target, fx)
};
let target_capture = capture_to_vec(target_setup);
return AssignPlan {
directive,
form: AssignForm::Compound {
target_capture,
target_str,
kind,
},
};
}
if self.target_binds_to_discard(target) {
return AssignPlan {
directive,
form: AssignForm::Discard {
body: raw_body(self.lower_discard_value(value, fx)),
},
};
}
let go_field_ty: Option<Type> = match target {
Expression::DotAccess { expression, ty, .. }
if self.go_imported_shape(&expression.get_type()).is_some()
&& (self.is_go_nullable(ty) || ty.resolves_to_unknown()) =>
{
Some(ty.clone())
}
_ => None,
};
if let Some(ref target_ty) = go_field_ty
&& target_ty.resolves_to_unknown()
&& value.is_none_literal()
{
let mut target_setup = String::new();
let target_str = if is_order_sensitive(target) {
self.emit_left_value_capturing(&mut target_setup, target, false, fx)
} else {
self.emit_left_value(&mut target_setup, target, fx)
};
return AssignPlan {
directive,
form: AssignForm::NilClear {
target_capture: capture_to_vec(target_setup),
target_str,
},
};
}
let rhs_staged = self.stage_composite(value, ExpressionContext::value(), fx);
let rhs_has_setup = !rhs_staged.setup.is_empty() || self.rhs_contains_effectful_call(value);
let mut target_setup = String::new();
let target_str = if is_order_sensitive(target) {
self.emit_left_value_capturing(&mut target_setup, target, rhs_has_setup, fx)
} else {
self.emit_left_value(&mut target_setup, target, fx)
};
let mut value_setup = rhs_staged.setup;
let coercion = if let Some(target_ty) = go_field_ty {
Coercion::resolve(
self,
&value.get_type(),
&target_ty,
CoercionDirection::ToGoBoundary,
)
} else {
Coercion::resolve(
self,
&value.get_type(),
&target.get_type(),
CoercionDirection::Internal,
)
};
let (coercion_setup, final_value) = coercion.lower(self, rhs_staged.value, fx);
value_setup.extend(coercion_setup);
AssignPlan {
directive,
form: AssignForm::Simple {
target_capture: capture_to_vec(target_setup),
target_str,
value: value_plan_from_statements(value_setup, final_value),
},
}
}
fn rhs_contains_effectful_call(&self, expression: &Expression) -> bool {
match expression.unwrap_parens() {
Expression::Call {
expression: callee,
args,
spread,
..
} => {
if self.is_pure_constructor_callee(callee) {
args.iter().any(|a| self.rhs_contains_effectful_call(a))
|| (**spread)
.as_ref()
.is_some_and(|s| self.rhs_contains_effectful_call(s))
} else {
true
}
}
Expression::Binary { left, right, .. } => {
self.rhs_contains_effectful_call(left) || self.rhs_contains_effectful_call(right)
}
Expression::Unary { expression, .. }
| Expression::DotAccess { expression, .. }
| Expression::Cast { expression, .. }
| Expression::Reference { expression, .. } => {
self.rhs_contains_effectful_call(expression)
}
Expression::IndexedAccess {
expression, index, ..
} => {
self.rhs_contains_effectful_call(expression)
|| self.rhs_contains_effectful_call(index)
}
Expression::Tuple { elements, .. } => {
elements.iter().any(|e| self.rhs_contains_effectful_call(e))
}
Expression::StructCall {
field_assignments,
spread,
..
} => {
field_assignments
.iter()
.any(|f| self.rhs_contains_effectful_call(&f.value))
|| spread
.as_expression()
.is_some_and(|s| self.rhs_contains_effectful_call(s))
}
Expression::Literal {
literal: Literal::Slice(elements),
..
} => elements.iter().any(|e| self.rhs_contains_effectful_call(e)),
Expression::Literal {
literal: Literal::FormatString(parts),
..
} => parts.iter().any(|part| match part {
FormatStringPart::Expression(e) => self.rhs_contains_effectful_call(e),
FormatStringPart::Text(_) => false,
}),
Expression::Range { start, end, .. } => {
start
.as_deref()
.is_some_and(|e| self.rhs_contains_effectful_call(e))
|| end
.as_deref()
.is_some_and(|e| self.rhs_contains_effectful_call(e))
}
_ => false,
}
}
fn is_pure_constructor_callee(&self, callee: &Expression) -> bool {
let name = match callee.unwrap_parens() {
Expression::Identifier { value, .. } => Some(value.as_str()),
Expression::DotAccess { member, .. } => Some(member.as_str()),
_ => None,
};
if matches!(name, Some("Some" | "Ok" | "Err" | "None")) {
return true;
}
self.callee_definition(callee)
.is_some_and(|definition| definition.is_type_definition())
}
fn target_binds_to_discard(&self, target: &Expression) -> bool {
let Expression::Identifier { value, .. } = target.unwrap_parens() else {
return false;
};
match self.scope.resolve_identifier_binding(value) {
Some(BindingValue::GoName(go_name)) => go_name == "_",
Some(BindingValue::InlineExpr(_)) => false,
None => value == "_",
}
}
pub(crate) fn emit_left_value(
&mut self,
output: &mut String,
expression: &Expression,
fx: &mut EmitEffects,
) -> String {
let expression = expression.unwrap_parens();
match expression {
Expression::Identifier { value, .. } => self
.scope
.resolve_binding_go_name(value)
.unwrap_or(value)
.to_string(),
Expression::DotAccess {
expression, member, ..
} => {
let base_str = if let Some(inner) = expression.deref_inner() {
let plan = self.plan_operand(inner, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
} else {
let plan = self.plan_operand(expression, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
};
let expression_ty = expression.get_type();
self.format_dot_access_lvalue(&base_str, &expression_ty, member, fx)
}
Expression::IndexedAccess {
expression, index, ..
} => {
let expression_string = if let Some(inner) = expression.deref_inner() {
let plan = self.plan_operand(inner, ExpressionContext::value(), fx);
let inner_str = Renderer.render_value(output, &plan);
format!("(*{})", inner_str)
} else {
let plan = self.plan_operand(expression, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
};
let index_plan = self.plan_operand(index, ExpressionContext::value(), fx);
let index_str = Renderer.render_value(output, &index_plan);
format!("{}[{}]", expression_string, index_str)
}
Expression::Unary {
operator: UnaryOperator::Deref,
expression,
..
} => self.emit_deref_lvalue(output, expression, false, fx),
Expression::Call { .. } if expression.get_type().is_ref() => {
let plan = self.plan_operand(expression, ExpressionContext::value(), fx);
let call_str = Renderer.render_value(output, &plan);
self.hoist_tmp_value(output, "ref", &call_str)
}
_ => "_".to_string(),
}
}
fn emit_deref_lvalue(
&mut self,
output: &mut String,
pointee: &Expression,
rhs_has_setup: bool,
fx: &mut EmitEffects,
) -> String {
let pointee_plan = self.plan_operand(pointee, ExpressionContext::value(), fx);
let pointee_string = Renderer.render_value(output, &pointee_plan);
let needs_capture = matches!(pointee.unwrap_parens(), Expression::Call { .. })
|| (rhs_has_setup && observable_after_mutation(pointee));
if needs_capture {
let tmp = self.hoist_tmp_value(output, "ref", &pointee_string);
return format!("*{}", tmp);
}
format!("*{}", pointee_string)
}
fn format_dot_access_lvalue(
&mut self,
base_str: &str,
expression_ty: &Type,
member: &str,
fx: &mut EmitEffects,
) -> String {
if let Ok(index) = member.parse::<usize>() {
let access =
self.try_emit_tuple_struct_field_access(base_str, expression_ty, index, fx);
if let Some(access) = access {
return access;
}
let field = TUPLE_FIELDS.get(index).expect("oversize tuple arity");
return format!("{}.{}", base_str, field);
}
let field = if self.field_is_public(expression_ty, member) {
go_name::make_exported(member)
} else {
go_name::escape_keyword(member).into_owned()
};
format!("{}.{}", base_str, field)
}
pub(crate) fn emit_left_value_capturing(
&mut self,
output: &mut String,
expression: &Expression,
rhs_has_setup: bool,
fx: &mut EmitEffects,
) -> String {
let expression = expression.unwrap_parens();
match expression {
Expression::IndexedAccess {
expression: base,
index,
..
} => {
let base_str = self.emit_indexed_base_lvalue(output, base, fx);
let index_str = self.emit_index_lvalue(output, index, rhs_has_setup, fx);
format!("{}[{}]", base_str, index_str)
}
Expression::DotAccess {
expression: base,
member,
..
} => {
let base_str = if let Some(inner) = base.deref_inner() {
if rhs_has_setup {
self.emit_force_capture(output, inner, "ref", fx)
} else {
let plan = self.plan_operand(inner, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
}
} else if is_order_sensitive(base) {
self.emit_left_value_capturing(output, base, rhs_has_setup, fx)
} else if rhs_has_setup && base.get_type().is_ref() {
self.emit_force_capture(output, base, "ref", fx)
} else {
self.emit_left_value(output, base, fx)
};
let expression_ty = base.get_type();
self.format_dot_access_lvalue(&base_str, &expression_ty, member, fx)
}
Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner,
..
} => self.emit_deref_lvalue(output, inner, rhs_has_setup, fx),
_ => self.emit_left_value(output, expression, fx),
}
}
fn emit_indexed_base_lvalue(
&mut self,
output: &mut String,
base: &Expression,
fx: &mut EmitEffects,
) -> String {
let force = is_order_sensitive(base);
if let Some(inner) = base.deref_inner() {
let inner_str = self.emit_base_operand(output, inner, force, fx);
format!("(*{})", inner_str)
} else {
self.emit_base_operand(output, base, force, fx)
}
}
fn emit_base_operand(
&mut self,
output: &mut String,
expression: &Expression,
force_capture: bool,
fx: &mut EmitEffects,
) -> String {
if force_capture {
self.emit_force_capture(output, expression, "base", fx)
} else {
let plan = self.plan_operand(expression, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
}
}
fn emit_index_lvalue(
&mut self,
output: &mut String,
index: &Expression,
rhs_has_setup: bool,
fx: &mut EmitEffects,
) -> String {
let needs_capture = if rhs_has_setup {
!matches!(index.unwrap_parens(), Expression::Literal { .. })
} else {
is_order_sensitive(index)
};
if needs_capture {
self.emit_force_capture(output, index, "idx", fx)
} else {
let plan = self.plan_operand(index, ExpressionContext::value(), fx);
Renderer.render_value(output, &plan)
}
}
}
fn detect_compound_assignment<'a>(
target: &Expression,
value: &'a Expression,
compound_operator: Option<&'a BinaryOperator>,
) -> Option<(&'a BinaryOperator, &'a Expression)> {
if let Some(op) = compound_operator {
return Some((op, compound_rhs(value)));
}
let Expression::Binary {
left,
operator,
right,
..
} = value
else {
return None;
};
if !is_compound_eligible(operator) || !lvalues_match(target, left) {
return None;
}
Some((operator, right.as_ref()))
}
fn compound_rhs(value: &Expression) -> &Expression {
if let Expression::Binary { right, .. } = value {
right
} else {
value
}
}
fn is_literal_one(expression: &Expression) -> bool {
matches!(
expression.unwrap_parens(),
Expression::Literal {
literal: syntax::ast::Literal::Integer { value: 1, .. },
..
}
)
}
fn lvalues_match(a: &Expression, b: &Expression) -> bool {
let a = a.unwrap_parens();
let b = b.unwrap_parens();
match (a, b) {
(
Expression::Identifier {
binding_id: Some(id_a),
..
},
Expression::Identifier {
binding_id: Some(id_b),
..
},
) => id_a == id_b,
(
Expression::DotAccess {
expression: base_a,
member: member_a,
..
},
Expression::DotAccess {
expression: base_b,
member: member_b,
..
},
) => member_a == member_b && lvalues_match(base_a, base_b),
(
Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner_a,
..
},
Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner_b,
..
},
) => lvalues_match(inner_a, inner_b),
_ => false,
}
}
fn is_compound_eligible(op: &BinaryOperator) -> bool {
matches!(
op,
BinaryOperator::Addition
| BinaryOperator::Subtraction
| BinaryOperator::Multiplication
| BinaryOperator::Division
| BinaryOperator::Remainder
)
}
pub(crate) fn is_lvalue_chain(expression: &Expression) -> bool {
let expression = expression.unwrap_parens();
match expression {
Expression::Identifier { .. } => true,
Expression::Unary {
operator: UnaryOperator::Deref,
..
} => true,
Expression::IndexedAccess { expression, .. } => is_lvalue_chain(expression),
Expression::DotAccess { expression, .. } => is_lvalue_chain(expression),
Expression::Call { .. } if expression.get_type().is_ref() => true,
_ => false,
}
}