neo_solidity/ir/expressions/dispatch/
assignments.rs

1fn try_lower_expression_assignments(
2    expr: &Expression,
3    ctx: &mut LoweringContext,
4    instructions: &mut Vec<Instruction>,
5) -> Option<bool> {
6    match expr {
7        Expression::AssignAdd(_, lhs, rhs) => Some(lower_compound_assignment(
8            lhs,
9            rhs,
10            ctx,
11            instructions,
12            BinaryOperator::Add,
13        )),
14        Expression::AssignSubtract(_, lhs, rhs) => Some(lower_compound_assignment(
15            lhs,
16            rhs,
17            ctx,
18            instructions,
19            BinaryOperator::Sub,
20        )),
21        Expression::AssignShiftLeft(_, lhs, rhs) => Some(lower_compound_assignment(
22            lhs,
23            rhs,
24            ctx,
25            instructions,
26            BinaryOperator::Shl,
27        )),
28        Expression::AssignShiftRight(_, lhs, rhs) => Some(lower_compound_assignment(
29            lhs,
30            rhs,
31            ctx,
32            instructions,
33            BinaryOperator::Shr,
34        )),
35        Expression::AssignAnd(_, lhs, rhs) => Some(lower_compound_assignment(
36            lhs,
37            rhs,
38            ctx,
39            instructions,
40            BinaryOperator::BitAnd,
41        )),
42        Expression::AssignOr(_, lhs, rhs) => Some(lower_compound_assignment(
43            lhs,
44            rhs,
45            ctx,
46            instructions,
47            BinaryOperator::BitOr,
48        )),
49        Expression::AssignXor(_, lhs, rhs) => Some(lower_compound_assignment(
50            lhs,
51            rhs,
52            ctx,
53            instructions,
54            BinaryOperator::BitXor,
55        )),
56        Expression::AssignMultiply(_, lhs, rhs) => Some(lower_compound_assignment(
57            lhs,
58            rhs,
59            ctx,
60            instructions,
61            BinaryOperator::Mul,
62        )),
63        Expression::AssignDivide(_, lhs, rhs) => Some(lower_compound_assignment(
64            lhs,
65            rhs,
66            ctx,
67            instructions,
68            BinaryOperator::Div,
69        )),
70        Expression::AssignModulo(_, lhs, rhs) => Some(lower_compound_assignment(
71            lhs,
72            rhs,
73            ctx,
74            instructions,
75            BinaryOperator::Mod,
76        )),
77        Expression::Assign(_, lhs, rhs) => {
78            lower_assignment(lhs, rhs, ctx, instructions);
79            Some(true)
80        }
81        Expression::PostIncrement(_, inner) => Some(lower_post_inc_dec(inner, ctx, instructions, true)),
82        Expression::PostDecrement(_, inner) => {
83            Some(lower_post_inc_dec(inner, ctx, instructions, false))
84        }
85        Expression::PreIncrement(_, inner) => Some(lower_pre_inc_dec(inner, ctx, instructions, true)),
86        Expression::PreDecrement(_, inner) => Some(lower_pre_inc_dec(inner, ctx, instructions, false)),
87        Expression::Delete(_, target) => {
88            Some(lower_delete(target, ctx, instructions))
89        }
90        _ => None,
91    }
92}
93
94fn lower_delete(
95    target: &Expression,
96    ctx: &mut LoweringContext,
97    instructions: &mut Vec<Instruction>,
98) -> bool {
99    // Storage delete: reset to the storage-default value.
100    if let Some(reference) = resolve_storage_reference(target, ctx) {
101        if !ctx.ensure_state_writable(reference.state_index) {
102            return false;
103        }
104
105        // Solidity allows `delete` on mappings, but it cannot clear all keys; treat as a no-op.
106        if matches!(reference.value_type, ValueType::Mapping { .. }) {
107            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
108                BigInt::zero(),
109            )));
110            return true;
111        }
112
113        push_default_for_storage_value_type(&reference.value_type, ctx, instructions);
114        if !emit_storage_store(&reference, ctx, instructions) {
115            instructions.push(Instruction::Drop(ValueType::Any));
116        }
117
118        // `delete` is a statement-only expression in Solidity; return a placeholder value so
119        // expression statements can safely DROP it without underflowing.
120        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
121            BigInt::zero(),
122        )));
123        return true;
124    }
125
126    // Local/state variable delete.
127    if let Expression::Variable(identifier) = target {
128        if let Some(local_index) = ctx.resolve_local(&identifier.name) {
129            if let Some(value_type) = ctx.local_type(local_index).cloned() {
130                push_default_for_value_type(&value_type, ctx, instructions);
131            } else {
132                instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
133                    BigInt::zero(),
134                )));
135            }
136            ctx.clear_call_data_local(local_index);
137            instructions.push(Instruction::StoreLocal(local_index));
138            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
139                BigInt::zero(),
140            )));
141            return true;
142        }
143
144        if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
145            if !ctx.ensure_state_writable(state_index) {
146                return false;
147            }
148
149            if let Some(state_type) = ctx.state_type(state_index).cloned() {
150                if matches!(state_type, ValueType::Mapping { .. }) {
151                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
152                        BigInt::zero(),
153                    )));
154                    return true;
155                }
156
157                if matches!(state_type, ValueType::Struct { .. }) {
158                    let reference = StorageReference {
159                        state_index,
160                        key_expressions: Vec::new(),
161                        key_types: Vec::new(),
162                        value_type: state_type.clone(),
163                        field_path: Vec::new(),
164                    };
165
166                    push_default_for_storage_value_type(&reference.value_type, ctx, instructions);
167                    if !emit_storage_store(&reference, ctx, instructions) {
168                        instructions.push(Instruction::Drop(ValueType::Any));
169                    }
170                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
171                        BigInt::zero(),
172                    )));
173                    return true;
174                }
175
176                push_default_for_storage_value_type(&state_type, ctx, instructions);
177                instructions.push(Instruction::StoreState(state_index));
178                instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
179                    BigInt::zero(),
180                )));
181                return true;
182            }
183        }
184    }
185
186    // Memory array element delete: `delete arr[i]`.
187    if let Expression::ArraySubscript(_, array, Some(index)) = target {
188        if let Some(ValueType::Array(element_type)) = infer_type_from_expression(array, ctx) {
189            let tmp_id = ctx.next_label();
190            let array_local = ctx.allocate_local(format!("__delete_arr_{tmp_id}"), None);
191            let index_local = ctx.allocate_local(format!("__delete_idx_{tmp_id}"), None);
192
193            if !lower_expression(array, ctx, instructions) {
194                return false;
195            }
196            instructions.push(Instruction::StoreLocal(array_local));
197
198            if !lower_expression(index, ctx, instructions) {
199                return false;
200            }
201            instructions.push(Instruction::StoreLocal(index_local));
202
203            instructions.push(Instruction::LoadLocal(array_local));
204            instructions.push(Instruction::LoadLocal(index_local));
205            push_default_for_value_type(element_type.as_ref(), ctx, instructions);
206            instructions.push(Instruction::ArraySet);
207            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
208                BigInt::zero(),
209            )));
210            return true;
211        }
212    }
213
214    // Memory struct field delete: `delete tmp.field`.
215    if let Expression::MemberAccess(_, inner, member) = target {
216        if let Expression::Variable(base) = inner.as_ref() {
217            if let Some(local_index) = ctx.resolve_local(&base.name) {
218                if let Some(ValueType::Struct { fields, .. }) = infer_type_from_expression(inner, ctx)
219                {
220                    if let Some((field_index, field)) = fields
221                        .iter()
222                        .enumerate()
223                        .find(|(_, field)| field.name == member.name)
224                    {
225                        instructions.push(Instruction::LoadLocal(local_index));
226                        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
227                            BigInt::from(field_index as u64),
228                        )));
229                        push_default_for_value_type(&field.ty, ctx, instructions);
230                        instructions.push(Instruction::ArraySet);
231                        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
232                            BigInt::zero(),
233                        )));
234                        return true;
235                    }
236                }
237            }
238        }
239    }
240
241    ctx.record_error_with_suggestion(
242        "unsupported delete target",
243        "delete is supported for state variables, mapping entries, and local variables",
244    );
245    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
246    true
247}