neo_solidity/ir/statements/assignments/
lower_assignment.rs1fn 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 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 #[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 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(¶meter.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) = ¶meter.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 if let Some(reference) = resolve_storage_reference(¶meter.ty, ctx) {
150 return TupleTarget::Storage(reference);
151 }
152
153 if let Expression::Variable(identifier) = ¶meter.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 let mut rhs_instrs = Vec::new();
272 if !lower_expression(rhs, ctx, &mut rhs_instrs) {
273 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 if lower_expression(rhs, ctx, instructions) {
376 instructions.push(Instruction::Drop(ValueType::Any));
377 }
378}