fn lower_array_subscript_expression(
expr: &Expression,
array: &Expression,
index: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if let Some(mapping) = resolve_mapping_access(expr, ctx) {
let array_subscript_terminal = mapping_terminal_hits_array(&mapping, ctx);
if let Some(array_head) = array_subscript_terminal {
return emit_storage_array_subscript_with_bounds(
&mapping,
array_head,
ctx,
instructions,
);
}
let reference = mapping.to_storage_reference();
emit_storage_load(&reference, ctx, instructions)
} else if let Some(reference) = resolve_storage_reference(expr, ctx) {
if !emit_struct_field_array_bounds_guard(array, index, ctx, instructions) {
return false;
}
emit_storage_load(&reference, ctx, instructions)
} else if lower_expression(array, ctx, instructions) && lower_expression(index, ctx, instructions) {
let tmp_id = ctx.next_label();
let idx_local = ctx.allocate_local(
format!("__aidx_{tmp_id}"),
Some(ValueType::Integer { signed: false, bits: 256 }),
);
let arr_local = ctx.allocate_local(format!("__aarr_{tmp_id}"), None);
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::StoreLocal(arr_local));
let after_neg_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: after_neg_label });
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(after_neg_label));
let ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::LoadLocal(arr_local));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: ok_label });
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(ok_label));
instructions.push(Instruction::LoadLocal(arr_local));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::ArrayGet);
true
} else {
false
}
}
enum StorageArrayBound {
DynamicStateVar {
state_index: usize,
},
MappingOfDynamicArray {
state_index: usize,
head_key_types: Vec<ValueType>,
head_key_expr_indices: Vec<usize>,
},
FixedSizeKnown(u64),
}
fn mapping_terminal_hits_array(
mapping: &MappingAccess<'_>,
ctx: &LoweringContext,
) -> Option<StorageArrayBound> {
let mut current = ctx.state_type(mapping.state_index)?.clone();
let mut head_key_types: Vec<ValueType> = Vec::new();
let mut head_key_expr_indices: Vec<usize> = Vec::new();
let total_keys = mapping.key_expressions.len();
if total_keys == 0 {
return None;
}
let state_ty = ctx
.state_metadata(mapping.state_index)
.map(|m| m.ty.as_str())
.unwrap_or("");
for step in 0..total_keys {
let is_terminal = step + 1 == total_keys;
match ¤t {
ValueType::Array(element) => {
if is_terminal {
if let Some(fixed_n) = extract_fixed_array_bound_at_depth(state_ty, step) {
return Some(StorageArrayBound::FixedSizeKnown(fixed_n));
}
if head_key_types.is_empty() {
return Some(StorageArrayBound::DynamicStateVar {
state_index: mapping.state_index,
});
}
return Some(StorageArrayBound::MappingOfDynamicArray {
state_index: mapping.state_index,
head_key_types,
head_key_expr_indices,
});
}
current = (**element).clone();
}
ValueType::Mapping { key: _, value } => {
if is_terminal {
return None;
}
if let Some(key_type) = mapping.key_types.get(step).cloned() {
head_key_types.push(key_type);
head_key_expr_indices.push(step);
} else {
return None;
}
current = (**value).clone();
}
_ => return None,
}
}
None
}
fn extract_fixed_array_bound_at_depth(ty: &str, _depth: usize) -> Option<u64> {
let trimmed = ty.trim_end();
if !trimmed.ends_with(']') {
return None;
}
let open = trimmed.rfind('[')?;
let inner = &trimmed[open + 1..trimmed.len() - 1];
if inner.is_empty() {
return None;
}
inner.parse::<u64>().ok()
}
fn emit_storage_array_subscript_with_bounds(
mapping: &MappingAccess<'_>,
bound: StorageArrayBound,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let tmp_id = ctx.next_label();
let last_index_expr = match mapping.key_expressions.last() {
Some(expr) => *expr,
None => return false,
};
let idx_local = ctx.allocate_local(
format!("__storage_aidx_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
if !lower_expression(last_index_expr, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(idx_local));
let after_neg_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf {
target: after_neg_label,
});
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(after_neg_label));
let ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
match &bound {
StorageArrayBound::DynamicStateVar { state_index } => {
instructions.push(Instruction::LoadState(*state_index));
}
StorageArrayBound::MappingOfDynamicArray {
state_index,
head_key_types,
head_key_expr_indices,
} => {
for expr_idx in head_key_expr_indices {
if let Some(expr) = mapping.key_expressions.get(*expr_idx) {
if !lower_expression(expr, ctx, instructions) {
return false;
}
} else {
return false;
}
}
instructions.push(Instruction::LoadMappingElement {
state_index: *state_index,
key_types: head_key_types.clone(),
});
}
StorageArrayBound::FixedSizeKnown(n) => {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
*n,
))));
}
}
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: ok_label });
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(ok_label));
let reference = mapping.to_storage_reference();
emit_storage_load(&reference, ctx, instructions)
}
fn emit_struct_field_array_bounds_guard(
array: &Expression,
index: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let Expression::MemberAccess(_, struct_expr, field_ident) = array else {
return true;
};
let Some(collection) = resolve_storage_reference(array, ctx) else {
return true;
};
if !matches!(collection.value_type, ValueType::Array(_))
|| collection.field_path.is_empty()
|| !collection.trailing_key_expressions.is_empty()
{
return true;
}
let struct_name = match resolve_storage_reference(struct_expr, ctx)
.map(|base| base.value_type)
.or_else(|| infer_type_from_expression(struct_expr, ctx))
{
Some(ValueType::Struct { name, .. }) => name,
_ => return true,
};
let fixed_bound = ctx.struct_fixed_array_bound(&struct_name, &field_ident.name);
let tmp_id = ctx.next_label();
let idx_local = ctx.allocate_local(
format!("__struct_aidx_{tmp_id}"),
Some(ValueType::Integer {
signed: false,
bits: 256,
}),
);
if !lower_expression(index, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(idx_local));
let after_neg_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf {
target: after_neg_label,
});
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(after_neg_label));
let ok_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(idx_local));
match fixed_bound {
Some(bound) => {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
bound,
))));
}
None => {
if !emit_storage_load(&collection, ctx, instructions) {
return false;
}
}
}
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: ok_label });
emit_panic(0x32, instructions);
instructions.push(Instruction::Label(ok_label));
true
}
fn lower_array_slice_expression(
array: &Expression,
start: Option<&Expression>,
end: Option<&Expression>,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if is_bytes_slice_target(array, ctx) {
return lower_bytes_slice_expression(array, start, end, ctx, instructions);
}
let array_local = ctx.allocate_local("__slice_array".to_string(), None);
if !lower_expression(array, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(array_local));
let start_local = ctx.allocate_local("__slice_start".to_string(), None);
if let Some(start_expr) = start {
if !lower_expression(start_expr, ctx, instructions) {
return false;
}
} else {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
}
instructions.push(Instruction::StoreLocal(start_local));
let end_local = ctx.allocate_local("__slice_end".to_string(), None);
if let Some(end_expr) = end {
if !lower_expression(end_expr, ctx, instructions) {
return false;
}
} else {
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::GetSize);
}
instructions.push(Instruction::StoreLocal(end_local));
let clamp_start_label = ctx.next_label();
let clamp_start_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf {
target: clamp_start_label,
});
instructions.push(Instruction::Jump {
target: clamp_start_done,
});
instructions.push(Instruction::Label(clamp_start_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(start_local));
instructions.push(Instruction::Label(clamp_start_done));
let size_local = ctx.allocate_local("__slice_size".to_string(), None);
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::StoreLocal(size_local));
let clamp_end_label = ctx.next_label();
let clamp_end_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(end_local));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Le));
instructions.push(Instruction::JumpIf {
target: clamp_end_label,
});
instructions.push(Instruction::Jump {
target: clamp_end_done,
});
instructions.push(Instruction::Label(clamp_end_label));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::StoreLocal(end_local));
instructions.push(Instruction::Label(clamp_end_done));
let len_local = ctx.allocate_local("__slice_len".to_string(), None);
instructions.push(Instruction::LoadLocal(end_local));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Sub));
instructions.push(Instruction::StoreLocal(len_local));
let clamp_label = ctx.next_label();
let clamp_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(len_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: clamp_label });
instructions.push(Instruction::Jump { target: clamp_done });
instructions.push(Instruction::Label(clamp_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(len_local));
instructions.push(Instruction::Label(clamp_done));
let element_type = infer_array_element_type(array, ctx).unwrap_or(ValueType::Any);
let slice_array_type = ValueType::Array(Box::new(element_type.clone()));
let out_local = ctx.allocate_local("__slice_out".to_string(), Some(slice_array_type));
instructions.push(Instruction::LoadLocal(len_local));
instructions.push(Instruction::NewArray { element_type });
instructions.push(Instruction::StoreLocal(out_local));
let idx_local = ctx.allocate_local("__slice_index".to_string(), None);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(idx_local));
let loop_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::Label(loop_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 });
instructions.push(Instruction::LoadLocal(out_local));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::ArraySet);
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(1u8))));
instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::Jump { target: loop_label });
instructions.push(Instruction::Label(end_label));
instructions.push(Instruction::LoadLocal(out_local));
true
}
fn is_bytes_slice_target(array: &Expression, ctx: &LoweringContext) -> bool {
matches!(
infer_type_from_expression(array, ctx),
Some(ValueType::ByteArray { .. })
)
}
fn lower_bytes_slice_expression(
array: &Expression,
start: Option<&Expression>,
end: Option<&Expression>,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let bytes_local = ctx.allocate_local(
"__bytes_slice_src".to_string(),
Some(ValueType::ByteArray { fixed_len: None }),
);
if !lower_expression(array, ctx, instructions) {
return false;
}
instructions.push(Instruction::StoreLocal(bytes_local));
let size_local = ctx.allocate_local("__bytes_slice_size".to_string(), None);
instructions.push(Instruction::LoadLocal(bytes_local));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::StoreLocal(size_local));
let start_local = ctx.allocate_local("__bytes_slice_start".to_string(), None);
if let Some(start_expr) = start {
if !lower_expression(start_expr, ctx, instructions) {
return false;
}
} else {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
}
instructions.push(Instruction::StoreLocal(start_local));
let start_clamp_lo = ctx.next_label();
let start_clamp_lo_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: start_clamp_lo });
instructions.push(Instruction::Jump { target: start_clamp_lo_done });
instructions.push(Instruction::Label(start_clamp_lo));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(start_local));
instructions.push(Instruction::Label(start_clamp_lo_done));
let start_clamp_hi = ctx.next_label();
let start_clamp_hi_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Le));
instructions.push(Instruction::JumpIf { target: start_clamp_hi });
instructions.push(Instruction::Jump { target: start_clamp_hi_done });
instructions.push(Instruction::Label(start_clamp_hi));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::StoreLocal(start_local));
instructions.push(Instruction::Label(start_clamp_hi_done));
let end_local = ctx.allocate_local("__bytes_slice_end".to_string(), None);
if let Some(end_expr) = end {
if !lower_expression(end_expr, ctx, instructions) {
return false;
}
} else {
instructions.push(Instruction::LoadLocal(size_local));
}
instructions.push(Instruction::StoreLocal(end_local));
let end_clamp_hi = ctx.next_label();
let end_clamp_hi_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(end_local));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Le));
instructions.push(Instruction::JumpIf { target: end_clamp_hi });
instructions.push(Instruction::Jump { target: end_clamp_hi_done });
instructions.push(Instruction::Label(end_clamp_hi));
instructions.push(Instruction::LoadLocal(size_local));
instructions.push(Instruction::StoreLocal(end_local));
instructions.push(Instruction::Label(end_clamp_hi_done));
let end_clamp_lo = ctx.next_label();
let end_clamp_lo_done = ctx.next_label();
instructions.push(Instruction::LoadLocal(end_local));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Ge));
instructions.push(Instruction::JumpIf { target: end_clamp_lo });
instructions.push(Instruction::Jump { target: end_clamp_lo_done });
instructions.push(Instruction::Label(end_clamp_lo));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::StoreLocal(end_local));
instructions.push(Instruction::Label(end_clamp_lo_done));
instructions.push(Instruction::LoadLocal(bytes_local));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::LoadLocal(end_local));
instructions.push(Instruction::LoadLocal(start_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Sub));
instructions.push(Instruction::Substr);
instructions.push(Instruction::Convert {
target: ConvertTarget::ByteArray,
});
true
}
fn lower_array_literal_expression(
elements: &[Expression],
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let element_type = infer_literal_array_element_type(elements);
let array_local = ctx.allocate_local(
"__array_literal".to_string(),
Some(ValueType::Array(Box::new(element_type.clone()))),
);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
elements.len(),
))));
instructions.push(Instruction::NewArray { element_type });
instructions.push(Instruction::StoreLocal(array_local));
for (index, element) in elements.iter().enumerate() {
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
index as u64,
))));
if !lower_expression(element, ctx, instructions) {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
}
instructions.push(Instruction::ArraySet);
}
instructions.push(Instruction::LoadLocal(array_local));
true
}