neo_solidity/ir/statements/assignments/
lower_assignment.rs

1fn lower_assignment(
2    lhs: &Expression,
3    rhs: &Expression,
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) {
7    if let Expression::Variable(identifier) = lhs {
8        if ctx.storage_alias(&identifier.name).is_some() {
9            if let Some(source_reference) = resolve_storage_reference(rhs, ctx) {
10                ctx.set_storage_alias(identifier.name.clone(), source_reference);
11                return;
12            }
13        }
14    }
15
16    if let Some(reference) = resolve_storage_reference(lhs, ctx) {
17        if !ctx.ensure_state_writable(reference.state_index) {
18            if lower_expression(rhs, ctx, instructions) {
19                instructions.push(Instruction::Drop(ValueType::Any));
20            }
21            return;
22        }
23
24        if let ValueType::Struct { name, fields } = &reference.value_type {
25            // Support struct construction on the RHS for storage assignments, both
26            // named and positional: `S({a:1,b:2})` or `S(1,2)`.
27            let mut ctor_args_by_name: Option<&[solang_parser::pt::NamedArgument]> = None;
28            let mut ctor_args_positional: Option<&[Expression]> = None;
29            let mut ctor_name_matches = false;
30
31            match rhs {
32                Expression::NamedFunctionCall(_, func, args) => {
33                    if let Expression::Variable(identifier) = func.as_ref() {
34                        ctor_name_matches = identifier.name.eq_ignore_ascii_case(name);
35                        if ctor_name_matches {
36                            ctor_args_by_name = Some(args.as_slice());
37                        }
38                    }
39                }
40                Expression::FunctionCall(_, func, args) => {
41                    if let Expression::Variable(identifier) = func.as_ref() {
42                        ctor_name_matches = identifier.name.eq_ignore_ascii_case(name);
43                        if ctor_name_matches {
44                            ctor_args_positional = Some(args.as_slice());
45                        }
46                    }
47                }
48                _ => {}
49            }
50
51            if ctor_name_matches {
52                for (index, field) in fields.iter().enumerate() {
53                    let mut field_reference = reference.clone();
54                    field_reference.field_path.push(StorageReferenceField {
55                        key: field.key,
56                        ty: field.ty.clone(),
57                    });
58                    field_reference.value_type = field.ty.clone();
59
60                    let success = if let Some(named_args) = ctor_args_by_name {
61                        if let Some(arg) = named_args.iter().find(|arg| arg.name.name == field.name)
62                        {
63                            lower_expression(&arg.expr, ctx, instructions)
64                        } else {
65                            push_default_for_storage_value_type(&field.ty, ctx, instructions)
66                        }
67                    } else if let Some(pos_args) = ctor_args_positional {
68                        if let Some(arg) = pos_args.get(index) {
69                            lower_expression(arg, ctx, instructions)
70                        } else {
71                            push_default_for_storage_value_type(&field.ty, ctx, instructions)
72                        }
73                    } else {
74                        push_default_for_storage_value_type(&field.ty, ctx, instructions)
75                    };
76
77                    if success && !emit_storage_store(&field_reference, ctx, instructions) {
78                        instructions.push(Instruction::Drop(ValueType::Any));
79                    }
80                }
81
82                return;
83            }
84        }
85
86        let success = lower_expression(rhs, ctx, instructions);
87        if success {
88            if !emit_storage_store(&reference, ctx, instructions) {
89                instructions.push(Instruction::Drop(ValueType::Any));
90            }
91        } else {
92            instructions.push(Instruction::Drop(ValueType::Any));
93        }
94        return;
95    }
96
97    if let Expression::List(_, params) = lhs {
98        // Tuple destructuring assignment: `(a, b) = expr;`
99        // We represent tuples as arrays and assign by index.
100
101        #[derive(Clone)]
102        enum TupleTarget {
103            Ignore,
104            DeclaredLocal {
105                local_index: usize,
106                inferred_type: Option<ValueType>,
107            },
108            ExistingLocal(usize),
109            ExistingState(usize),
110            Storage(StorageReference),
111            Nested(Vec<TupleTarget>),
112            Invalid,
113        }
114
115        fn resolve_optional_tuple_target(
116            parameter: &Option<solang_parser::pt::Parameter>,
117            ctx: &mut LoweringContext,
118        ) -> TupleTarget {
119            let Some(parameter) = parameter else {
120                return TupleTarget::Ignore;
121            };
122
123            if let Some(name) = parameter.name.as_ref() {
124                // Declaration: `(bool ok, uint v) = ...`
125                if ctx.is_local_in_current_scope(&name.name) {
126                    ctx.record_error_with_suggestion(
127                        format!("local variable '{}' redeclared", name.name),
128                        "use a different variable name or assign to the existing variable instead of redeclaring",
129                    );
130                }
131
132                let inferred_type = infer_type_from_expression(&parameter.ty, ctx);
133                let local_index = ctx.allocate_local(name.name.clone(), inferred_type.clone());
134                return TupleTarget::DeclaredLocal {
135                    local_index,
136                    inferred_type,
137                };
138            }
139
140            if let Expression::List(_, nested_params) = &parameter.ty {
141                let children = nested_params
142                    .iter()
143                    .map(|(_, param)| resolve_optional_tuple_target(param, ctx))
144                    .collect();
145                return TupleTarget::Nested(children);
146            }
147
148            // Assignment to an existing lvalue: `(ok, v) = ...`
149            if let Some(reference) = resolve_storage_reference(&parameter.ty, ctx) {
150                return TupleTarget::Storage(reference);
151            }
152
153            if let Expression::Variable(identifier) = &parameter.ty {
154                if let Some(local_index) = ctx.resolve_local(&identifier.name) {
155                    return TupleTarget::ExistingLocal(local_index);
156                }
157
158                if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
159                    return TupleTarget::ExistingState(state_index);
160                }
161
162                ctx.record_error(format!(
163                    "unknown identifier '{}' in tuple assignment",
164                    identifier.name
165                ));
166                return TupleTarget::Invalid;
167            }
168
169            ctx.record_error_with_suggestion(
170                "unsupported tuple assignment target",
171                "Neo N3 supports single-value assignments; destructure tuple returns into separate statements",
172            );
173            TupleTarget::Invalid
174        }
175
176        fn initialize_declared_tuple_targets(
177            target: &TupleTarget,
178            ctx: &mut LoweringContext,
179            instructions: &mut Vec<Instruction>,
180        ) {
181            match target {
182                TupleTarget::DeclaredLocal {
183                    local_index,
184                    inferred_type,
185                } => {
186                    if let Some(ty) = inferred_type.as_ref() {
187                        push_default_for_value_type(ty, ctx, instructions);
188                    } else {
189                        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
190                            BigInt::zero(),
191                        )));
192                    }
193                    instructions.push(Instruction::StoreLocal(*local_index));
194                }
195                TupleTarget::Nested(children) => {
196                    for child in children {
197                        initialize_declared_tuple_targets(child, ctx, instructions);
198                    }
199                }
200                _ => {}
201            }
202        }
203
204        fn emit_tuple_element_load(
205            tuple_local: usize,
206            path: &[usize],
207            instructions: &mut Vec<Instruction>,
208        ) {
209            instructions.push(Instruction::LoadLocal(tuple_local));
210            for index in path {
211                instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
212                    *index as u64,
213                ))));
214                instructions.push(Instruction::ArrayGet);
215            }
216        }
217
218        fn assign_tuple_target(
219            tuple_local: usize,
220            path: &mut Vec<usize>,
221            target: &TupleTarget,
222            ctx: &mut LoweringContext,
223            instructions: &mut Vec<Instruction>,
224        ) {
225            match target {
226                TupleTarget::Ignore => {}
227                TupleTarget::Nested(children) => {
228                    for (index, child) in children.iter().enumerate() {
229                        path.push(index);
230                        assign_tuple_target(tuple_local, path, child, ctx, instructions);
231                        path.pop();
232                    }
233                }
234                TupleTarget::DeclaredLocal { local_index, .. }
235                | TupleTarget::ExistingLocal(local_index) => {
236                    emit_tuple_element_load(tuple_local, path, instructions);
237                    ctx.clear_call_data_local(*local_index);
238                    instructions.push(Instruction::StoreLocal(*local_index));
239                }
240                TupleTarget::ExistingState(state_index) => {
241                    emit_tuple_element_load(tuple_local, path, instructions);
242                    if ctx.ensure_state_writable(*state_index) {
243                        instructions.push(Instruction::StoreState(*state_index));
244                    } else {
245                        instructions.push(Instruction::Drop(ValueType::Any));
246                    }
247                }
248                TupleTarget::Storage(reference) => {
249                    emit_tuple_element_load(tuple_local, path, instructions);
250                    if ctx.ensure_state_writable(reference.state_index) {
251                        if !emit_storage_store(reference, ctx, instructions) {
252                            instructions.push(Instruction::Drop(ValueType::Any));
253                        }
254                    } else {
255                        instructions.push(Instruction::Drop(ValueType::Any));
256                    }
257                }
258                TupleTarget::Invalid => {
259                    emit_tuple_element_load(tuple_local, path, instructions);
260                    instructions.push(Instruction::Drop(ValueType::Any));
261                }
262            }
263        }
264
265        let targets: Vec<TupleTarget> = params
266            .iter()
267            .map(|(_, parameter)| resolve_optional_tuple_target(parameter, ctx))
268            .collect();
269
270        // Lower RHS into a temporary buffer so failures don't leave partial stack state.
271        let mut rhs_instrs = Vec::new();
272        if !lower_expression(rhs, ctx, &mut rhs_instrs) {
273            // Ensure declared locals exist with default values to avoid cascading errors.
274            for target in &targets {
275                initialize_declared_tuple_targets(target, ctx, instructions);
276            }
277            return;
278        }
279        instructions.append(&mut rhs_instrs);
280
281        let tuple_local = ctx.allocate_local("__tuple_assign".to_string(), None);
282        instructions.push(Instruction::StoreLocal(tuple_local));
283
284        for (index, target) in targets.iter().enumerate() {
285            let mut path = vec![index];
286            assign_tuple_target(tuple_local, &mut path, target, ctx, instructions);
287        }
288
289        return;
290    }
291
292    if matches!(lhs, Expression::ArraySubscript(_, _, Some(_))) {
293        lower_array_store(lhs, rhs, ctx, instructions);
294        return;
295    }
296
297    if let Expression::Variable(identifier) = lhs {
298        if let Some(index) = ctx.resolve_local(&identifier.name) {
299            match parse_low_level_call_data(rhs, ctx) {
300                Ok(Some((method_name, encode_args))) => {
301                    let mut lowered = true;
302                    for arg in &encode_args {
303                        if !lower_expression(arg, ctx, instructions) {
304                            lowered = false;
305                        }
306                    }
307
308                    if lowered {
309                        instructions.push(Instruction::CallBuiltin {
310                            builtin: BuiltinCall::AbiEncode,
311                            arg_count: encode_args.len(),
312                        });
313                        instructions.push(Instruction::StoreLocal(index));
314                        ctx.set_call_data_local(index, method_name);
315                    }
316                }
317                Ok(None) => {
318                    if lower_expression(rhs, ctx, instructions) {
319                        instructions.push(Instruction::StoreLocal(index));
320                        ctx.clear_call_data_local(index);
321                    }
322                }
323                Err(message) => {
324                    ctx.record_error(message);
325                    ctx.clear_call_data_local(index);
326                }
327            }
328            return;
329        }
330        if let Some(index) = ctx.state_index_map.get(&identifier.name) {
331            if lower_expression(rhs, ctx, instructions) {
332                if ctx.ensure_state_writable(*index) {
333                    instructions.push(Instruction::StoreState(*index));
334                } else {
335                    instructions.push(Instruction::Drop(ValueType::Any));
336                }
337            }
338            return;
339        }
340
341        let index = ctx.ensure_local(&identifier.name);
342        match parse_low_level_call_data(rhs, ctx) {
343            Ok(Some((method_name, encode_args))) => {
344                let mut lowered = true;
345                for arg in &encode_args {
346                    if !lower_expression(arg, ctx, instructions) {
347                        lowered = false;
348                    }
349                }
350
351                if lowered {
352                    instructions.push(Instruction::CallBuiltin {
353                        builtin: BuiltinCall::AbiEncode,
354                        arg_count: encode_args.len(),
355                    });
356                    instructions.push(Instruction::StoreLocal(index));
357                    ctx.set_call_data_local(index, method_name);
358                }
359            }
360            Ok(None) => {
361                if lower_expression(rhs, ctx, instructions) {
362                    instructions.push(Instruction::StoreLocal(index));
363                    ctx.clear_call_data_local(index);
364                }
365            }
366            Err(message) => {
367                ctx.record_error(message);
368                ctx.clear_call_data_local(index);
369            }
370        }
371        return;
372    }
373
374    // Fallback: evaluate RHS (if possible) and drop to allow compilation to continue.
375    if lower_expression(rhs, ctx, instructions) {
376        instructions.push(Instruction::Drop(ValueType::Any));
377    }
378}