fn emit_compound_binary_op_for_lhs(
lhs: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
op: BinaryOperator,
) {
emit_arith_with_overflow_ladder(lhs, lhs, ctx, instructions, op, true);
}
fn lower_compound_rhs(
rhs: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if !lower_expression(rhs, ctx, instructions) {
return false;
}
true
}
fn lower_compound_assignment(
lhs: &Expression,
rhs: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
op: BinaryOperator,
) -> bool {
if let Some(mapping) = resolve_mapping_access(lhs, ctx) {
if !ctx.ensure_state_writable(mapping.state_index) {
return false;
}
let tmp_id = ctx.next_label();
let mut key_locals: Vec<usize> = Vec::new();
for (index, key_expr) in mapping.key_expressions.iter().enumerate() {
let local = ctx.allocate_local(
format!("__compound_key_{tmp_id}_{index}"),
mapping.key_types.get(index).cloned(),
);
if !lower_expression(key_expr, ctx, instructions) {
ctx.record_error_with_suggestion(
"failed to lower mapping key in compound assignment",
"ensure the mapping key expression is a supported type (integer, string, bytes, or address)",
);
return false;
}
instructions.push(Instruction::StoreLocal(local));
key_locals.push(local);
}
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
instructions.push(Instruction::LoadMappingElement {
state_index: mapping.state_index,
key_types: mapping.key_types.clone(),
});
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
let result_local = ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
instructions.push(Instruction::StoreMappingElement {
state_index: mapping.state_index,
key_types: mapping.key_types.clone(),
});
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
if let Some(reference) = resolve_storage_reference(lhs, ctx) {
if !ctx.ensure_state_writable(reference.state_index) {
return false;
}
if !reference.trailing_key_expressions.is_empty() {
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
if !emit_storage_load(&reference, ctx, instructions) {
return false;
}
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
if !emit_storage_store(&reference, ctx, instructions) {
return false;
}
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
if let Some(field) = reference.field_path.last() {
let field_keys: Vec<[u8; 32]> = reference.field_path.iter().map(|field| field.key).collect();
let tmp_id = ctx.next_label();
let mut key_locals: Vec<usize> = Vec::new();
for (index, key_expr) in reference.key_expressions.iter().enumerate() {
let local = ctx.allocate_local(
format!("__compound_key_{tmp_id}_{index}"),
reference.key_types.get(index).cloned(),
);
if !lower_expression(key_expr, ctx, instructions) {
ctx.record_error("failed to lower storage key in compound assignment");
return false;
}
instructions.push(Instruction::StoreLocal(local));
key_locals.push(local);
}
let push_keys_for_slot = |instructions: &mut Vec<Instruction>| {
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
};
push_keys_for_slot(instructions);
instructions.push(Instruction::LoadStructField {
state_index: reference.state_index,
key_types: reference.key_types.clone(),
field_keys: field_keys.clone(),
field_type: field.ty.clone(),
});
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
let result_local = ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
push_keys_for_slot(instructions);
instructions.push(Instruction::StoreStructField {
state_index: reference.state_index,
key_types: reference.key_types.clone(),
field_keys,
field_type: field.ty.clone(),
});
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
}
if let Expression::ArraySubscript(_, array, Some(index)) = lhs {
let tmp_id = ctx.next_label();
let array_local = ctx.allocate_local(format!("__compound_arr_{tmp_id}"), None);
let index_local = ctx.allocate_local(format!("__compound_idx_{tmp_id}"), None);
if !lower_expression(array, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(array_local));
if !lower_expression(index, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(index_local));
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::LoadLocal(index_local));
instructions.push(Instruction::ArrayGet);
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
let result_local = ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::LoadLocal(index_local));
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::ArraySet);
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
if let Expression::MemberAccess(_, inner, member) = lhs {
if let Expression::Variable(base) = inner.as_ref() {
let load_base = ctx
.resolve_local(&base.name)
.map(Instruction::LoadLocal)
.or_else(|| {
ctx.param_index_map
.get(&base.name)
.copied()
.map(Instruction::LoadParameter)
});
if let Some(load_base) = load_base {
if let Some(ValueType::Struct { fields, .. }) =
infer_type_from_expression(inner, ctx)
{
if let Some((field_index, _field)) = fields
.iter()
.enumerate()
.find(|(_, field)| field.name == member.name)
{
let tmp_id = ctx.next_label();
let result_local =
ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
instructions.push(load_base.clone());
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(field_index as u64),
)));
instructions.push(Instruction::ArrayGet);
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(load_base);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(field_index as u64),
)));
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(Instruction::ArraySet);
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
}
}
}
}
if let Expression::Variable(identifier) = lhs {
let store_instr = if let Some(local) = ctx.resolve_local(&identifier.name) {
Instruction::StoreLocal(local)
} else if let Some(param_index) = ctx.param_index_map.get(&identifier.name).copied() {
Instruction::StoreParameter(param_index)
} else if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
if !ctx.ensure_state_writable(state_index) {
return false;
}
Instruction::StoreState(state_index)
} else {
let local = ctx.ensure_local(&identifier.name);
Instruction::StoreLocal(local)
};
let tmp_id = ctx.next_label();
let result_local = ctx.allocate_local(format!("__compound_value_{tmp_id}"), None);
if !lower_expression(lhs, ctx, instructions) {
return false;
}
if !lower_compound_rhs(rhs, ctx, instructions) {
return false;
}
emit_compound_binary_op_for_lhs(lhs, ctx, instructions, op);
instructions.push(Instruction::StoreLocal(result_local));
instructions.push(Instruction::LoadLocal(result_local));
instructions.push(store_instr);
instructions.push(Instruction::LoadLocal(result_local));
return true;
}
let mut success = true;
if lower_expression(lhs, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
} else {
success = false;
}
if lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
} else {
success = false;
}
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
success
}