neo_solidity/ir/expressions/calls/
builtins.rs

1include!("builtins/helpers.rs");
2include!("builtins/member_access.rs");
3include!("builtins/member_runtime.rs");
4include!("builtins/member_syscalls.rs");
5include!("builtins/member_storage.rs");
6include!("builtins/member_neo.rs");
7include!("builtins/member_nativecalls.rs");
8include!("builtins/resolved.rs");
9
10fn try_lower_builtin_call(
11    func: &Expression,
12    args: &[Expression],
13    ctx: &mut LoweringContext,
14    instructions: &mut Vec<Instruction>,
15) -> Option<bool> {
16    // Solidity 0.8.x type-level concat: `bytes.concat(a, b, ...)` / `string.concat(a, b, ...)`
17    // solang-parser represents these as MemberAccess(Type(DynamicBytes|String), "concat").
18    if let Some(result) = try_lower_type_concat(func, args, ctx, instructions) {
19        return Some(result);
20    }
21
22    if let Some(result) = try_lower_member_builtin(func, args, ctx, instructions) {
23        return Some(result);
24    }
25
26    try_lower_resolved_builtin_call(func, args, ctx, instructions)
27}
28
29/// Handle `bytes.concat(a, b, ...)` and `string.concat(a, b, ...)`.
30///
31/// These are Solidity 0.8.x type-level functions. Each argument is lowered onto
32/// the stack, then chained with NeoVM CAT opcodes. Zero arguments produce an
33/// empty byte string; one argument is a pass-through.
34fn try_lower_type_concat(
35    func: &Expression,
36    args: &[Expression],
37    ctx: &mut LoweringContext,
38    instructions: &mut Vec<Instruction>,
39) -> Option<bool> {
40    let Expression::MemberAccess(_, inner, member) = func else {
41        return None;
42    };
43    if member.name != "concat" {
44        return None;
45    }
46    // Match `bytes.concat(...)` or `string.concat(...)` where inner is a Type node.
47    let is_bytes_or_string = matches!(
48        inner.as_ref(),
49        Expression::Type(_, PtType::DynamicBytes | PtType::String)
50    );
51    if !is_bytes_or_string {
52        return None;
53    }
54
55    // Zero args: push empty byte array.
56    if args.is_empty() {
57        instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(vec![])));
58        return Some(true);
59    }
60
61    // Lower all arguments, then emit BytesConcat builtin (CAT chain).
62    let mut success = true;
63    for arg in args {
64        if !lower_expression(arg, ctx, instructions) {
65            success = false;
66        }
67    }
68
69    if success {
70        instructions.push(Instruction::CallBuiltin {
71            builtin: BuiltinCall::BytesConcat,
72            arg_count: args.len(),
73        });
74    }
75
76    Some(success)
77}