neo_solidity/ir/expressions/calls/
struct_constructors.rs

1fn resolve_struct_type_by_name(ctx: &LoweringContext<'_>, name: &str) -> Option<ValueType> {
2    ctx.defined_struct_types
3        .iter()
4        .chain(ctx.state_types.iter())
5        .chain(ctx.param_types.iter())
6        .chain(ctx.return_types.iter())
7        .chain(ctx.local_types.values())
8        .find_map(|ty| find_named_struct_type(ty, name))
9}
10
11fn try_lower_struct_constructor_call(
12    func: &Expression,
13    args: &[Expression],
14    ctx: &mut LoweringContext,
15    instructions: &mut Vec<Instruction>,
16) -> Option<bool> {
17    let Expression::Variable(identifier) = func else {
18        return None;
19    };
20
21    let struct_type = resolve_struct_type_by_name(ctx, &identifier.name)?;
22    let ValueType::Struct { fields, .. } = &struct_type else {
23        return None;
24    };
25
26    Some(lower_struct_constructor_positional(
27        &identifier.name,
28        &struct_type,
29        fields,
30        args,
31        ctx,
32        instructions,
33    ))
34}
35
36fn try_lower_struct_constructor_named_call(
37    func: &Expression,
38    args: &[solang_parser::pt::NamedArgument],
39    ctx: &mut LoweringContext,
40    instructions: &mut Vec<Instruction>,
41) -> Option<bool> {
42    let Expression::Variable(identifier) = func else {
43        return None;
44    };
45
46    let struct_type = resolve_struct_type_by_name(ctx, &identifier.name)?;
47    let ValueType::Struct { fields, .. } = &struct_type else {
48        return None;
49    };
50
51    Some(lower_struct_constructor_named(
52        &identifier.name,
53        &struct_type,
54        fields,
55        args,
56        ctx,
57        instructions,
58    ))
59}
60
61fn lower_struct_constructor_positional(
62    call_name: &str,
63    struct_type: &ValueType,
64    fields: &[StructField],
65    args: &[Expression],
66    ctx: &mut LoweringContext,
67    instructions: &mut Vec<Instruction>,
68) -> bool {
69    let tmp_id = ctx.next_label();
70    let struct_local = ctx.allocate_local(format!("__struct_ctor_{tmp_id}"), Some(struct_type.clone()));
71
72    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
73        fields.len() as u64,
74    ))));
75    instructions.push(Instruction::NewArray {
76        element_type: ValueType::Any,
77    });
78    instructions.push(Instruction::StoreLocal(struct_local));
79
80    let mut success = true;
81
82    for (index, field) in fields.iter().enumerate() {
83        instructions.push(Instruction::LoadLocal(struct_local));
84        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
85            index as u64,
86        ))));
87        if let Some(arg) = args.get(index) {
88            if !lower_expression(arg, ctx, instructions) {
89                push_default_for_value_type(&field.ty, ctx, instructions);
90                success = false;
91            }
92        } else {
93            push_default_for_value_type(&field.ty, ctx, instructions);
94        }
95        instructions.push(Instruction::ArraySet);
96    }
97
98    if args.len() > fields.len() {
99        ctx.record_error(format!(
100            "struct constructor '{}' expects {} argument(s), got {}",
101            call_name,
102            fields.len(),
103            args.len()
104        ));
105        for arg in &args[fields.len()..] {
106            if lower_expression(arg, ctx, instructions) {
107                instructions.push(Instruction::Drop(ValueType::Any));
108            }
109        }
110        success = false;
111    }
112
113    instructions.push(Instruction::LoadLocal(struct_local));
114    success
115}
116
117fn lower_struct_constructor_named(
118    call_name: &str,
119    struct_type: &ValueType,
120    fields: &[StructField],
121    args: &[solang_parser::pt::NamedArgument],
122    ctx: &mut LoweringContext,
123    instructions: &mut Vec<Instruction>,
124) -> bool {
125    let tmp_id = ctx.next_label();
126    let struct_local = ctx.allocate_local(format!("__struct_ctor_{tmp_id}"), Some(struct_type.clone()));
127
128    let mut field_indexes = HashMap::with_capacity(fields.len());
129    for (index, field) in fields.iter().enumerate() {
130        field_indexes.insert(field.name.clone(), index);
131    }
132
133    let mut arg_locals: Vec<Option<usize>> = vec![None; fields.len()];
134    let mut success = true;
135
136    for arg in args {
137        let Some(&field_index) = field_indexes.get(&arg.name.name) else {
138            ctx.record_error(format!(
139                "unknown field '{}' for struct '{}'",
140                arg.name.name, call_name
141            ));
142            if lower_expression(&arg.expr, ctx, instructions) {
143                instructions.push(Instruction::Drop(ValueType::Any));
144            }
145            success = false;
146            continue;
147        };
148
149        if arg_locals[field_index].is_some() {
150            ctx.record_error(format!(
151                "duplicate field '{}' for struct '{}'",
152                arg.name.name, call_name
153            ));
154            if lower_expression(&arg.expr, ctx, instructions) {
155                instructions.push(Instruction::Drop(ValueType::Any));
156            }
157            success = false;
158            continue;
159        }
160
161        let field_ty = &fields[field_index].ty;
162        let arg_local = ctx.allocate_local(
163            format!("__struct_arg_{tmp_id}_{field_index}"),
164            Some(field_ty.clone()),
165        );
166
167        if !lower_expression(&arg.expr, ctx, instructions) {
168            push_default_for_value_type(field_ty, ctx, instructions);
169            success = false;
170        }
171
172        instructions.push(Instruction::StoreLocal(arg_local));
173        arg_locals[field_index] = Some(arg_local);
174    }
175
176    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
177        fields.len() as u64,
178    ))));
179    instructions.push(Instruction::NewArray {
180        element_type: ValueType::Any,
181    });
182    instructions.push(Instruction::StoreLocal(struct_local));
183
184    for (index, field) in fields.iter().enumerate() {
185        instructions.push(Instruction::LoadLocal(struct_local));
186        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
187            index as u64,
188        ))));
189        if let Some(arg_local) = arg_locals[index] {
190            instructions.push(Instruction::LoadLocal(arg_local));
191        } else {
192            push_default_for_value_type(&field.ty, ctx, instructions);
193        }
194        instructions.push(Instruction::ArraySet);
195    }
196
197    instructions.push(Instruction::LoadLocal(struct_local));
198    success
199}