neo_solidity/ir/expressions/dispatch/
calls.rs

1fn try_lower_expression_calls(
2    expr: &Expression,
3    ctx: &mut LoweringContext,
4    instructions: &mut Vec<Instruction>,
5) -> Option<bool> {
6    match expr {
7        Expression::FunctionCallBlock(_, call, block) => {
8            let _ = (call, block);
9            ctx.record_error_with_suggestion(
10                "function call options (`{...}`) are not supported; Neo N3 requires explicit NEP-17 transfers (`NativeCalls.gasTransfer` / `NativeCalls.neoTransfer`) + `onNEP17Payment`",
11                "replace {value: x} with an explicit NativeCalls.gasTransfer() or NativeCalls.neoTransfer() before the call",
12            );
13            Some(false)
14        }
15        Expression::NamedFunctionCall(_, func, args) => {
16            // Try struct constructor first (e.g., `MyStruct({x: 1, y: 2})`).
17            if let Some(result) =
18                try_lower_struct_constructor_named_call(func.as_ref(), args, ctx, instructions)
19            {
20                return Some(result);
21            }
22
23            // Try reordering named args into positional order for known functions.
24            if let Some(result) =
25                try_lower_named_function_call(func.as_ref(), args, ctx, instructions)
26            {
27                return Some(result);
28            }
29
30            ctx.record_error_with_suggestion(
31                "named argument calls are not supported for this callee",
32                "use positional arguments instead: f(arg1, arg2) rather than f({x: arg1, y: arg2})",
33            );
34            Some(false)
35        }
36        Expression::FunctionCall(_, func, args) => {
37            Some(lower_function_call_expression(func.as_ref(), args, ctx, instructions))
38        }
39        Expression::New(_, expr) => {
40            Some(lower_new_expression(expr.as_ref(), ctx, instructions))
41        }
42        Expression::Type(_, ty) => {
43            push_default_for_type(ty, instructions);
44            Some(true)
45        }
46        Expression::Parenthesis(_, inner) => Some(lower_expression(inner, ctx, instructions)),
47        Expression::MemberAccess(_, inner, member) => Some(lower_member_access_expression(
48            expr,
49            inner.as_ref(),
50            member,
51            ctx,
52            instructions,
53        )),
54        _ => None,
55    }
56}
57
58fn lower_new_expression(
59    expr: &Expression,
60    ctx: &mut LoweringContext,
61    instructions: &mut Vec<Instruction>,
62) -> bool {
63    match expr {
64        Expression::FunctionCall(_, func, args) => {
65            // `new bytes(n)` / `new string(n)`
66            if matches!(
67                func.as_ref(),
68                Expression::Type(_, PtType::DynamicBytes | PtType::String)
69            ) {
70                if args.len() != 1 {
71                    ctx.record_error_with_suggestion(
72                        "new bytes/string expects exactly one length argument",
73                        "usage: new bytes(length) or new string(length)",
74                    );
75                    for arg in args {
76                        if lower_expression(arg, ctx, instructions) {
77                            instructions.push(Instruction::Drop(ValueType::Any));
78                        }
79                    }
80                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
81                        BigInt::zero(),
82                    )));
83                } else if !lower_expression(&args[0], ctx, instructions) {
84                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
85                        BigInt::zero(),
86                    )));
87                }
88
89                instructions.push(Instruction::NewBuffer);
90                return true;
91            }
92            // `new T[](n)` dynamic arrays and `new T[N]()` fixed-size arrays.
93            if let Expression::ArraySubscript(_, array_type_expr, index) = func.as_ref() {
94                if let Some(fixed_len_expr) = index.as_ref() {
95                    if !args.is_empty() {
96                        ctx.record_error("new fixed-size array constructor does not accept runtime arguments");
97                        for arg in args {
98                            if lower_expression(arg, ctx, instructions) {
99                                instructions.push(Instruction::Drop(ValueType::Any));
100                            }
101                        }
102                    }
103
104                    return lower_new_array_allocation(
105                        array_type_expr.as_ref(),
106                        fixed_len_expr.as_ref(),
107                        ctx,
108                        instructions,
109                    );
110                }
111
112                if args.len() != 1 {
113                    ctx.record_error("new array expects exactly one length argument");
114                    for arg in args {
115                        if lower_expression(arg, ctx, instructions) {
116                            instructions.push(Instruction::Drop(ValueType::Any));
117                        }
118                    }
119
120                    let zero = Expression::NumberLiteral(
121                        Default::default(),
122                        "0".to_string(),
123                        "".to_string(),
124                        None,
125                    );
126                    return lower_new_array_allocation(
127                        array_type_expr.as_ref(),
128                        &zero,
129                        ctx,
130                        instructions,
131                    );
132                }
133
134                return lower_new_array_allocation(
135                    array_type_expr.as_ref(),
136                    &args[0],
137                    ctx,
138                    instructions,
139                );
140            }
141
142            // `new Contract(...)` isn't supported on Neo N3 (no contract creation from within contracts).
143            if let Expression::Variable(identifier) = func.as_ref() {
144                if ctx.is_contract_type_name(&identifier.name) {
145                    ctx.record_error(format!(
146                        "contract creation via `new {}` is not supported on Neo N3; use ContractManagement.deploy from an admin/entry contract instead",
147                        identifier.name
148                    ));
149                    for arg in args {
150                        if lower_expression(arg, ctx, instructions) {
151                            instructions.push(Instruction::Drop(ValueType::Any));
152                        }
153                    }
154                    instructions.push(Instruction::PushLiteral(LiteralValue::Address(vec![
155                        0u8;
156                        20
157                    ])));
158                    return true;
159                }
160            }
161
162            ctx.record_error_with_suggestion(
163                "unsupported `new` expression",
164                "Neo N3 supports `new bytes(n)`, `new string(n)`, `new T[](n)`, and `new T[N]`; use ContractManagement for contract deployment",
165            );
166            for arg in args {
167                if lower_expression(arg, ctx, instructions) {
168                    instructions.push(Instruction::Drop(ValueType::Any));
169                }
170            }
171            instructions.push(Instruction::PushLiteral(LiteralValue::Null));
172            true
173        }
174        Expression::ArraySubscript(_, array_type_expr, Some(length_expr)) => {
175            lower_new_array_allocation(array_type_expr.as_ref(), length_expr.as_ref(), ctx, instructions)
176        }
177        Expression::FunctionCallBlock(_, _, _) => {
178            ctx.record_error_with_suggestion(
179                "function call blocks on `new` are not supported",
180                "Neo N3 does not support value transfers via call options; use explicit NEP-17 transfers",
181            );
182            instructions.push(Instruction::PushLiteral(LiteralValue::Null));
183            true
184        }
185        _ => {
186            ctx.record_error_with_suggestion(
187                "unsupported `new` expression",
188                "Neo N3 supports `new bytes(n)`, `new string(n)`, `new T[](n)`, and `new T[N]`",
189            );
190            instructions.push(Instruction::PushLiteral(LiteralValue::Null));
191            true
192        }
193    }
194}
195
196fn lower_new_array_allocation(
197    array_type_expr: &Expression,
198    length_expr: &Expression,
199    ctx: &mut LoweringContext,
200    instructions: &mut Vec<Instruction>,
201) -> bool {
202    let Some(element_type) = infer_type_from_expression(array_type_expr, ctx) else {
203        ctx.record_error(format!(
204            "unable to infer element type for new array allocation (`new {}`)",
205            array_type_expr
206        ));
207        if lower_expression(length_expr, ctx, instructions) {
208            instructions.push(Instruction::Drop(ValueType::Any));
209        }
210        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
211            BigInt::zero(),
212        )));
213        instructions.push(Instruction::NewArray {
214            element_type: ValueType::Any,
215        });
216        return true;
217    };
218
219    let tmp_id = ctx.next_label();
220    let len_local = ctx.allocate_local(format!("__new_array_len_{tmp_id}"), None);
221    if !lower_expression(length_expr, ctx, instructions) {
222        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
223            BigInt::zero(),
224        )));
225    }
226    instructions.push(Instruction::StoreLocal(len_local));
227
228    let array_type = ValueType::Array(Box::new(element_type.clone()));
229    let array_local = ctx.allocate_local(format!("__new_array_{tmp_id}"), Some(array_type));
230
231    instructions.push(Instruction::LoadLocal(len_local));
232    instructions.push(Instruction::NewArray {
233        element_type: element_type.clone(),
234    });
235    instructions.push(Instruction::StoreLocal(array_local));
236
237    // Solidity initializes new memory arrays with element defaults. NeoVM NEWARRAY
238    // fills with nulls, so explicitly write default values for value types.
239    let idx_local = ctx.allocate_local(format!("__new_array_idx_{tmp_id}"), None);
240    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
241        BigInt::zero(),
242    )));
243    instructions.push(Instruction::StoreLocal(idx_local));
244
245    let loop_label = ctx.next_label();
246    let end_label = ctx.next_label();
247
248    instructions.push(Instruction::Label(loop_label));
249    instructions.push(Instruction::LoadLocal(idx_local));
250    instructions.push(Instruction::LoadLocal(len_local));
251    instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
252    instructions.push(Instruction::JumpIf { target: end_label });
253
254    instructions.push(Instruction::LoadLocal(array_local));
255    instructions.push(Instruction::LoadLocal(idx_local));
256    push_default_for_value_type(&element_type, ctx, instructions);
257    instructions.push(Instruction::ArraySet);
258
259    instructions.push(Instruction::LoadLocal(idx_local));
260    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
261    instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
262    instructions.push(Instruction::StoreLocal(idx_local));
263
264    instructions.push(Instruction::Jump { target: loop_label });
265    instructions.push(Instruction::Label(end_label));
266
267    instructions.push(Instruction::LoadLocal(array_local));
268    true
269}
270
271/// Reorder named function call arguments into positional order and delegate
272/// to the standard function call lowering path.
273///
274/// Returns `None` if the callee is not a known function (caller should fall
275/// through to the error path). Returns `Some(bool)` on success or error.
276fn try_lower_named_function_call(
277    func: &Expression,
278    named_args: &[solang_parser::pt::NamedArgument],
279    ctx: &mut LoweringContext,
280    instructions: &mut Vec<Instruction>,
281) -> Option<bool> {
282    let Expression::Variable(identifier) = func else {
283        return None;
284    };
285
286    // Look up parameter names for this function with matching arg count.
287    // Clone to release the immutable borrow on `ctx` so we can call
288    // `ctx.record_error()` later.
289    let param_names: Vec<String> =
290        ctx.get_function_param_names(&identifier.name, named_args.len())?.to_vec();
291
292    // Build name→index mapping from the function's parameter list.
293    let name_to_index: HashMap<&str, usize> = param_names
294        .iter()
295        .enumerate()
296        .map(|(i, name)| (name.as_str(), i))
297        .collect();
298
299    // Reorder named arguments into positional order.
300    let mut positional: Vec<Option<&Expression>> = vec![None; named_args.len()];
301    let mut has_error = false;
302
303    for arg in named_args {
304        if let Some(&index) = name_to_index.get(arg.name.name.as_str()) {
305            if positional[index].is_some() {
306                ctx.record_error(format!(
307                    "duplicate named argument '{}' in call to '{}'",
308                    arg.name.name, identifier.name
309                ));
310                has_error = true;
311            } else {
312                positional[index] = Some(&arg.expr);
313            }
314        } else {
315            ctx.record_error(format!(
316                "unknown parameter '{}' in call to '{}'; expected one of: {}",
317                arg.name.name,
318                identifier.name,
319                param_names.join(", ")
320            ));
321            has_error = true;
322        }
323    }
324
325    if has_error {
326        // Evaluate all args for side effects, then return false.
327        for arg in named_args {
328            if lower_expression(&arg.expr, ctx, instructions) {
329                instructions.push(Instruction::Drop(ValueType::Any));
330            }
331        }
332        return Some(false);
333    }
334
335    // Build positional args vector and delegate to normal call path.
336    let ordered_exprs: Vec<&Expression> = positional
337        .into_iter()
338        .map(|opt| opt.expect("all positions filled"))
339        .collect();
340
341    // Lower each argument in positional order, then emit the call.
342    let mut success = true;
343    for expr in &ordered_exprs {
344        if !lower_expression(expr, ctx, instructions) {
345            success = false;
346        }
347    }
348
349    if success {
350        if let Some(neo_name) = ctx.neo_function_name(&identifier.name, named_args.len()) {
351            instructions.push(Instruction::CallFunction {
352                name: neo_name,
353                arg_count: named_args.len(),
354            });
355        } else {
356            ctx.record_error(format!(
357                "no overload of '{}' with {} argument(s)",
358                identifier.name, named_args.len()
359            ));
360            success = false;
361        }
362    }
363
364    if ctx.is_void_function(&identifier.name) {
365        return Some(false);
366    }
367    Some(success)
368}