fn try_lower_expression_assignments(
expr: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
match expr {
Expression::AssignAdd(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Add,
)),
Expression::AssignSubtract(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Sub,
)),
Expression::AssignShiftLeft(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Shl,
)),
Expression::AssignShiftRight(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Shr,
)),
Expression::AssignAnd(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::BitAnd,
)),
Expression::AssignOr(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::BitOr,
)),
Expression::AssignXor(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::BitXor,
)),
Expression::AssignMultiply(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Mul,
)),
Expression::AssignDivide(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Div,
)),
Expression::AssignModulo(_, lhs, rhs) => Some(lower_compound_assignment(
lhs,
rhs,
ctx,
instructions,
BinaryOperator::Mod,
)),
Expression::Assign(_, lhs, rhs) => {
lower_assignment(lhs, rhs, ctx, instructions);
Some(true)
}
Expression::PostIncrement(_, inner) => Some(lower_post_inc_dec(inner, ctx, instructions, true)),
Expression::PostDecrement(_, inner) => {
Some(lower_post_inc_dec(inner, ctx, instructions, false))
}
Expression::PreIncrement(_, inner) => Some(lower_pre_inc_dec(inner, ctx, instructions, true)),
Expression::PreDecrement(_, inner) => Some(lower_pre_inc_dec(inner, ctx, instructions, false)),
Expression::Delete(_, target) => {
Some(lower_delete(target, ctx, instructions))
}
_ => None,
}
}
fn lower_delete(
target: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if let Some(reference) = resolve_storage_reference(target, ctx) {
if !ctx.ensure_state_writable(reference.state_index) {
return false;
}
if matches!(reference.value_type, ValueType::Mapping { .. }) {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
if matches!(reference.value_type, ValueType::Array(_))
&& reference.field_path.is_empty()
&& reference.trailing_key_expressions.is_empty()
&& lower_delete_storage_array(&reference, ctx, instructions)
{
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
push_default_for_storage_value_type(&reference.value_type, ctx, instructions);
if !emit_storage_store(&reference, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
if let Expression::Variable(identifier) = target {
if let Some(local_index) = ctx.resolve_local(&identifier.name) {
if let Some(value_type) = ctx.local_type(local_index).cloned() {
push_default_for_value_type(&value_type, ctx, instructions);
} else {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
}
ctx.clear_call_data_local(local_index);
instructions.push(Instruction::StoreLocal(local_index));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
if !ctx.ensure_state_writable(state_index) {
return false;
}
if let Some(state_type) = ctx.state_type(state_index).cloned() {
if matches!(state_type, ValueType::Mapping { .. }) {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
if matches!(state_type, ValueType::Struct { .. }) {
let reference = StorageReference {
state_index,
key_expressions: Vec::new(),
key_types: Vec::new(),
value_type: state_type.clone(),
field_path: Vec::new(),
trailing_key_expressions: Vec::new(),
trailing_key_types: Vec::new(),
};
push_default_for_storage_value_type(&reference.value_type, ctx, instructions);
if !emit_storage_store(&reference, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
push_default_for_storage_value_type(&state_type, ctx, instructions);
instructions.push(Instruction::StoreState(state_index));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
}
}
if let Expression::ArraySubscript(_, array, Some(index)) = target {
if let Some(ValueType::Array(element_type)) = infer_type_from_expression(array, ctx) {
let tmp_id = ctx.next_label();
let array_local = ctx.allocate_local(format!("__delete_arr_{tmp_id}"), None);
let index_local = ctx.allocate_local(format!("__delete_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));
push_default_for_value_type(element_type.as_ref(), ctx, instructions);
instructions.push(Instruction::ArraySet);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
}
if let Expression::MemberAccess(_, inner, member) = target {
if let Expression::Variable(base) = inner.as_ref() {
if let Some(local_index) = ctx.resolve_local(&base.name) {
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)
{
instructions.push(Instruction::LoadLocal(local_index));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(field_index as u64),
)));
push_default_for_value_type(&field.ty, ctx, instructions);
instructions.push(Instruction::ArraySet);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
return true;
}
}
}
}
}
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
true
}
fn lower_delete_storage_array(
reference: &StorageReference,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let element_type = match &reference.value_type {
ValueType::Array(element) => (**element).clone(),
_ => return false,
};
if matches!(
element_type,
ValueType::Struct { .. } | ValueType::Mapping { .. } | ValueType::Any
) {
return false;
}
let uint256 = ValueType::Integer {
signed: false,
bits: 256,
};
let tmp_id = ctx.next_label();
let mut key_locals: Vec<usize> = Vec::new();
for (index, expr) in reference.key_expressions.iter().enumerate() {
let local = ctx.allocate_local(
format!("__delete_sarr_key_{tmp_id}_{index}"),
reference.key_types.get(index).cloned(),
);
if !lower_expression(expr, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(local));
key_locals.push(local);
}
let is_fixed_size = reference.key_expressions.is_empty()
&& ctx
.state_metadata(reference.state_index)
.map(|meta| meta.ty.clone())
.and_then(|ty| extract_fixed_array_bound_at_depth(&ty, 0))
.is_some();
let len_local = ctx.allocate_local(
format!("__delete_sarr_len_{tmp_id}"),
Some(uint256.clone()),
);
if is_fixed_size {
let bound = ctx
.state_metadata(reference.state_index)
.map(|meta| meta.ty.clone())
.and_then(|ty| extract_fixed_array_bound_at_depth(&ty, 0))
.unwrap_or(0);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(bound),
)));
} else if reference.key_expressions.is_empty() {
instructions.push(Instruction::LoadState(reference.state_index));
} else {
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
instructions.push(Instruction::LoadMappingElement {
state_index: reference.state_index,
key_types: reference.key_types.clone(),
});
}
instructions.push(Instruction::StoreLocal(len_local));
let mut element_key_types = reference.key_types.clone();
element_key_types.push(uint256.clone());
let cond_label = ctx.next_label();
let end_label = ctx.next_label();
let idx_local = ctx.allocate_local(
format!("__delete_sarr_idx_{tmp_id}"),
Some(uint256.clone()),
);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::Label(cond_label));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::LoadLocal(len_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: end_label });
push_default_for_value_type(&element_type, ctx, instructions);
instructions.push(Instruction::LoadLocal(idx_local));
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
if matches!(element_type, ValueType::Array(_)) {
instructions.push(Instruction::StoreArrayDeepCopy {
state_index: reference.state_index,
key_types: element_key_types.clone(),
});
} else {
instructions.push(Instruction::StoreMappingElement {
state_index: reference.state_index,
key_types: element_key_types.clone(),
});
}
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::Jump { target: cond_label });
instructions.push(Instruction::Label(end_label));
if !is_fixed_size {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
for local in key_locals.iter().rev() {
instructions.push(Instruction::LoadLocal(*local));
}
instructions.push(Instruction::StoreMappingElement {
state_index: reference.state_index,
key_types: reference.key_types.clone(),
});
}
true
}