fn lower_assignment(
lhs: &Expression,
rhs: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
if let Expression::Variable(identifier) = lhs {
if ctx.storage_alias(&identifier.name).is_some() {
if let Some(source_reference) = resolve_storage_reference(rhs, ctx) {
ctx.set_storage_alias(identifier.name.clone(), source_reference);
return;
}
}
}
let direct_state_var_array = if let Expression::Variable(identifier) = lhs {
ctx.storage_alias(&identifier.name).is_none()
&& ctx
.state_index_map
.get(&identifier.name)
.and_then(|idx| ctx.state_type(*idx))
.map(|ty| matches!(ty, ValueType::Array(_) | ValueType::Mapping { .. }))
.unwrap_or(false)
} else {
false
};
let resolved = if direct_state_var_array {
None
} else {
resolve_storage_reference(lhs, ctx)
};
if let Some(reference) = resolved {
if !ctx.ensure_state_writable(reference.state_index) {
if lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
return;
}
if let ValueType::Struct { name, fields } = &reference.value_type {
let mut ctor_args_by_name: Option<&[solang_parser::pt::NamedArgument]> = None;
let mut ctor_args_positional: Option<&[Expression]> = None;
let mut ctor_name_matches = false;
match rhs {
Expression::NamedFunctionCall(_, func, args) => {
if let Expression::Variable(identifier) = func.as_ref() {
ctor_name_matches = identifier.name.eq_ignore_ascii_case(name);
if ctor_name_matches {
ctor_args_by_name = Some(args.as_slice());
}
}
}
Expression::FunctionCall(_, func, args) => {
if let Expression::Variable(identifier) = func.as_ref() {
ctor_name_matches = identifier.name.eq_ignore_ascii_case(name);
if ctor_name_matches {
ctor_args_positional = Some(args.as_slice());
}
}
}
_ => {}
}
if ctor_name_matches {
for (index, field) in fields.iter().enumerate() {
let mut field_reference = reference.clone();
field_reference.field_path.push(StorageReferenceField {
key: field.key,
ty: field.ty.clone(),
});
field_reference.value_type = field.ty.clone();
let success = if let Some(named_args) = ctor_args_by_name {
if let Some(arg) = named_args.iter().find(|arg| arg.name.name == field.name)
{
lower_expression(&arg.expr, ctx, instructions)
} else {
push_default_for_storage_value_type(&field.ty, ctx, instructions)
}
} else if let Some(pos_args) = ctor_args_positional {
if let Some(arg) = pos_args.get(index) {
lower_expression(arg, ctx, instructions)
} else {
push_default_for_storage_value_type(&field.ty, ctx, instructions)
}
} else {
push_default_for_storage_value_type(&field.ty, ctx, instructions)
};
if success && !emit_storage_store(&field_reference, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
}
return;
}
}
let success = lower_expression(rhs, ctx, instructions);
if success {
if !emit_storage_store(&reference, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
} else {
instructions.push(Instruction::Drop(ValueType::Any));
}
return;
}
if let Expression::List(_, params) = lhs {
#[derive(Clone)]
enum TupleTarget {
Ignore,
DeclaredLocal {
local_index: usize,
inferred_type: Option<ValueType>,
},
ExistingLocal(usize),
ExistingParameter(usize),
ExistingState(usize),
Storage(StorageReference),
Nested(Vec<TupleTarget>),
Invalid,
}
fn resolve_optional_tuple_target(
parameter: &Option<solang_parser::pt::Parameter>,
ctx: &mut LoweringContext,
) -> TupleTarget {
let Some(parameter) = parameter else {
return TupleTarget::Ignore;
};
if let Some(name) = parameter.name.as_ref() {
if ctx.is_local_in_current_scope(&name.name) {
ctx.record_error_with_suggestion(
format!("local variable '{}' redeclared", name.name),
"use a different variable name or assign to the existing variable instead of redeclaring",
);
}
let inferred_type = infer_type_from_expression(¶meter.ty, ctx);
let local_index = ctx.allocate_local(name.name.clone(), inferred_type.clone());
return TupleTarget::DeclaredLocal {
local_index,
inferred_type,
};
}
if let Expression::List(_, nested_params) = ¶meter.ty {
let children = nested_params
.iter()
.map(|(_, param)| resolve_optional_tuple_target(param, ctx))
.collect();
return TupleTarget::Nested(children);
}
if let Some(reference) = resolve_storage_reference(¶meter.ty, ctx) {
return TupleTarget::Storage(reference);
}
if let Expression::Variable(identifier) = ¶meter.ty {
if let Some(local_index) = ctx.resolve_local(&identifier.name) {
return TupleTarget::ExistingLocal(local_index);
}
if let Some(param_index) = ctx.param_index_map.get(&identifier.name).copied() {
return TupleTarget::ExistingParameter(param_index);
}
if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
return TupleTarget::ExistingState(state_index);
}
return TupleTarget::Invalid;
}
TupleTarget::Invalid
}
fn initialize_declared_tuple_targets(
target: &TupleTarget,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
match target {
TupleTarget::DeclaredLocal {
local_index,
inferred_type,
} => {
if let Some(ty) = inferred_type.as_ref() {
push_default_for_value_type(ty, ctx, instructions);
} else {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
}
instructions.push(Instruction::StoreLocal(*local_index));
}
TupleTarget::Nested(children) => {
for child in children {
initialize_declared_tuple_targets(child, ctx, instructions);
}
}
_ => {}
}
}
fn emit_tuple_element_load(
tuple_local: usize,
path: &[usize],
instructions: &mut Vec<Instruction>,
) {
instructions.push(Instruction::LoadLocal(tuple_local));
for index in path {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
*index as u64,
))));
instructions.push(Instruction::ArrayGet);
}
}
fn assign_tuple_target(
tuple_local: usize,
path: &mut Vec<usize>,
target: &TupleTarget,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
match target {
TupleTarget::Ignore => {}
TupleTarget::Nested(children) => {
for (index, child) in children.iter().enumerate() {
path.push(index);
assign_tuple_target(tuple_local, path, child, ctx, instructions);
path.pop();
}
}
TupleTarget::DeclaredLocal { local_index, .. }
| TupleTarget::ExistingLocal(local_index) => {
emit_tuple_element_load(tuple_local, path, instructions);
ctx.clear_call_data_local(*local_index);
instructions.push(Instruction::StoreLocal(*local_index));
}
TupleTarget::ExistingParameter(param_index) => {
emit_tuple_element_load(tuple_local, path, instructions);
instructions.push(Instruction::StoreParameter(*param_index));
}
TupleTarget::ExistingState(state_index) => {
emit_tuple_element_load(tuple_local, path, instructions);
if ctx.ensure_state_writable(*state_index) {
instructions.push(Instruction::StoreState(*state_index));
} else {
instructions.push(Instruction::Drop(ValueType::Any));
}
}
TupleTarget::Storage(reference) => {
emit_tuple_element_load(tuple_local, path, instructions);
if ctx.ensure_state_writable(reference.state_index) {
if !emit_storage_store(reference, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
} else {
instructions.push(Instruction::Drop(ValueType::Any));
}
}
TupleTarget::Invalid => {
emit_tuple_element_load(tuple_local, path, instructions);
instructions.push(Instruction::Drop(ValueType::Any));
}
}
}
let targets: Vec<TupleTarget> = params
.iter()
.map(|(_, parameter)| resolve_optional_tuple_target(parameter, ctx))
.collect();
let mut rhs_instrs = Vec::new();
if !lower_expression(rhs, ctx, &mut rhs_instrs) {
for target in &targets {
initialize_declared_tuple_targets(target, ctx, instructions);
}
return;
}
instructions.append(&mut rhs_instrs);
if is_this_external_tuple_call(rhs, ctx) {
let target_static_types: Option<Vec<ValueType>> = params
.iter()
.map(|(_, param)| {
let parameter = param.as_ref()?;
let ty = infer_type_from_expression(¶meter.ty, ctx)?;
if abi_static_slot_count(&ty) == Some(1) {
Some(ty)
} else {
None
}
})
.collect();
if let Some(static_types) = target_static_types {
let buffer_local = ctx.allocate_local(
"__this_tuple_abi_buf".to_string(),
Some(ValueType::ByteArray { fixed_len: None }),
);
instructions.push(Instruction::StoreLocal(buffer_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(static_types.len() as u64),
)));
instructions.push(Instruction::NewArray {
element_type: ValueType::Any,
});
let array_local = ctx.allocate_local(
"__this_tuple_array".to_string(),
Some(ValueType::Array(Box::new(ValueType::Any))),
);
instructions.push(Instruction::StoreLocal(array_local));
for (index, value_type) in static_types.iter().enumerate() {
instructions.push(Instruction::LoadLocal(array_local));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(index as u64),
)));
emit_abi_decode_static_slot(
buffer_local,
index,
value_type,
ctx,
instructions,
);
instructions.push(Instruction::ArraySet);
}
instructions.push(Instruction::LoadLocal(array_local));
}
}
let tuple_local = ctx.allocate_local("__tuple_assign".to_string(), None);
instructions.push(Instruction::StoreLocal(tuple_local));
for (index, target) in targets.iter().enumerate() {
let mut path = vec![index];
assign_tuple_target(tuple_local, &mut path, target, ctx, instructions);
}
return;
}
if matches!(lhs, Expression::ArraySubscript(_, _, Some(_))) {
lower_array_store(lhs, rhs, ctx, instructions);
return;
}
if let Expression::Variable(identifier) = lhs {
if let Some(index) = ctx.resolve_local(&identifier.name) {
match parse_low_level_call_data(rhs, ctx) {
Ok(Some((method_name, encode_args))) => {
let mut lowered = true;
for arg in &encode_args {
if !lower_expression(arg, ctx, instructions) {
lowered = false;
}
}
if lowered {
if encode_args.is_empty() {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::NewArray {
element_type: ValueType::Any,
});
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::StdLib,
method: "serialize".to_string(),
},
arg_count: 1,
});
} else {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: encode_args.len(),
});
}
instructions.push(Instruction::StoreLocal(index));
ctx.set_call_data_local(index, method_name);
}
}
Ok(None) => {
let dst_type = ctx.local_type(index).cloned();
let decoded = match dst_type.as_ref() {
Some(ty) => try_lower_this_external_dynamic_assign(
index, rhs, ty, ctx, instructions,
),
None => false,
};
if !decoded && lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::StoreLocal(index));
ctx.clear_call_data_local(index);
}
}
Err(message) => {
ctx.record_error(message);
ctx.clear_call_data_local(index);
}
}
return;
}
if let Some(index) = ctx.param_index_map.get(&identifier.name).copied() {
if lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::StoreParameter(index));
}
return;
}
if let Some(index) = ctx.state_index_map.get(&identifier.name).copied() {
if matches!(ctx.state_type(index), Some(ValueType::Array(_))) {
lower_storage_array_assign_from_memory(index, rhs, ctx, instructions);
return;
}
if lower_expression(rhs, ctx, instructions) {
if ctx.ensure_state_writable(index) {
instructions.push(Instruction::StoreState(index));
} else {
instructions.push(Instruction::Drop(ValueType::Any));
}
}
return;
}
let index = ctx.ensure_local(&identifier.name);
match parse_low_level_call_data(rhs, ctx) {
Ok(Some((method_name, encode_args))) => {
let mut lowered = true;
for arg in &encode_args {
if !lower_expression(arg, ctx, instructions) {
lowered = false;
}
}
if lowered {
if encode_args.is_empty() {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::NewArray {
element_type: ValueType::Any,
});
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::StdLib,
method: "serialize".to_string(),
},
arg_count: 1,
});
} else {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: encode_args.len(),
});
}
instructions.push(Instruction::StoreLocal(index));
ctx.set_call_data_local(index, method_name);
}
}
Ok(None) => {
if lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::StoreLocal(index));
ctx.clear_call_data_local(index);
}
}
Err(message) => {
ctx.record_error(message);
ctx.clear_call_data_local(index);
}
}
return;
}
if let Expression::MemberAccess(_, inner, member) = lhs {
if let Expression::Variable(base) = inner.as_ref() {
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 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 {
instructions.push(load_base);
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(field_index as u64),
)));
if !lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
instructions.push(Instruction::Drop(ValueType::Any));
return;
}
instructions.push(Instruction::ArraySet);
return;
}
}
}
}
}
if lower_expression(rhs, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
}
fn lower_storage_array_assign_from_memory(
state_index: usize,
rhs: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
let writable = ctx.ensure_state_writable(state_index);
let src_local = ctx.allocate_local("__arr_assign_src".to_string(), None);
if !lower_expression(rhs, ctx, instructions) {
return;
}
instructions.push(Instruction::StoreLocal(src_local));
if !writable {
return;
}
let uint256 = ValueType::Integer {
signed: false,
bits: 256,
};
let new_len_local = ctx.allocate_local("__arr_assign_new_len".to_string(), Some(uint256.clone()));
instructions.push(Instruction::LoadLocal(src_local));
instructions.push(Instruction::GetSize);
instructions.push(Instruction::StoreLocal(new_len_local));
let old_len_local = ctx.allocate_local("__arr_assign_old_len".to_string(), Some(uint256.clone()));
instructions.push(Instruction::LoadState(state_index));
instructions.push(Instruction::StoreLocal(old_len_local));
instructions.push(Instruction::LoadLocal(new_len_local));
instructions.push(Instruction::StoreState(state_index));
let element_type = match ctx.state_type(state_index).cloned() {
Some(ValueType::Array(elem)) => (*elem).clone(),
_ => ValueType::Any,
};
let copy_cond_label = ctx.next_label();
let copy_end_label = ctx.next_label();
let idx_local = ctx.allocate_local("__arr_assign_idx".to_string(), Some(uint256.clone()));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::Label(copy_cond_label));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::LoadLocal(new_len_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: copy_end_label });
instructions.push(Instruction::LoadLocal(src_local));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::ArrayGet);
instructions.push(Instruction::LoadLocal(idx_local));
if matches!(element_type, ValueType::Array(_)) {
instructions.push(Instruction::StoreArrayDeepCopy {
state_index,
key_types: vec![uint256.clone()],
});
} else {
instructions.push(Instruction::StoreMappingElement {
state_index,
key_types: vec![uint256.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: copy_cond_label });
instructions.push(Instruction::Label(copy_end_label));
let delete_cond_label = ctx.next_label();
let delete_end_label = ctx.next_label();
instructions.push(Instruction::LoadLocal(new_len_local));
instructions.push(Instruction::StoreLocal(idx_local));
instructions.push(Instruction::Label(delete_cond_label));
instructions.push(Instruction::LoadLocal(idx_local));
instructions.push(Instruction::LoadLocal(old_len_local));
instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
instructions.push(Instruction::JumpIf { target: delete_end_label });
push_default_for_value_type(&element_type, ctx, instructions);
instructions.push(Instruction::LoadLocal(idx_local));
if matches!(element_type, ValueType::Array(_)) {
instructions.push(Instruction::StoreArrayDeepCopy {
state_index,
key_types: vec![uint256.clone()],
});
} else {
instructions.push(Instruction::StoreMappingElement {
state_index,
key_types: vec![uint256.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: delete_cond_label });
instructions.push(Instruction::Label(delete_end_label));
}
fn is_this_external_tuple_call(rhs: &Expression, ctx: &mut LoweringContext) -> bool {
let Expression::FunctionCall(_, func, _) = rhs else {
return false;
};
let Expression::MemberAccess(_, inner, _member) = func.as_ref() else {
return false;
};
if matches!(inner.as_ref(), Expression::Variable(id) if id.name == "this") {
return true;
}
if let Expression::FunctionCall(_, cast_func, cast_args) = inner.as_ref() {
if cast_args.len() == 1 {
let is_contract_type = match cast_func.as_ref() {
Expression::Variable(id) => ctx.is_contract_type_name(&id.name),
Expression::MemberAccess(_, _, id) => ctx.is_contract_type_name(&id.name),
_ => false,
};
if is_contract_type {
return true;
}
}
}
matches!(
infer_type_from_expression(inner.as_ref(), ctx),
Some(ValueType::Address)
)
}
fn try_lower_this_external_dynamic_assign(
slot: usize,
rhs: &Expression,
dst_type: &ValueType,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if !abi_dynamic_decode_value_type_is_supported(dst_type) {
return false;
}
if !is_this_external_tuple_call(rhs, ctx) {
return false;
}
let pre_len = instructions.len();
if !lower_expression(rhs, ctx, instructions) {
instructions.truncate(pre_len);
return false;
}
let buffer_local = ctx.allocate_local(
"__this_dyn_abi_buf".to_string(),
Some(ValueType::ByteArray { fixed_len: None }),
);
instructions.push(Instruction::StoreLocal(buffer_local));
emit_abi_decode_dynamic_top_level(buffer_local, dst_type, ctx, instructions);
instructions.push(Instruction::StoreLocal(slot));
ctx.clear_call_data_local(slot);
true
}