Skip to main content

cairo_lang_sierra/extensions/modules/
structure.rs

1//! Sierra example:
2//! ```ignore
3//! type felt252 = felt252;
4//! type Tuple<felt252, felt252> = Struct<ut@Tuple, felt252, felt252>;
5//! libfunc tuple_construct = struct_construct<Tuple<felt252, felt252>>;
6//! libfunc tuple_deconstruct = struct_deconstruct<Tuple<felt252, felt252>>;
7//! ...
8//! felt252_const<0>() -> (felt0);
9//! felt252_const<1>() -> (felt1);
10//! tuple_construct(felt0, felt1) -> (tup);
11//! tuple_deconstruct(tup) -> (felt0, felt1);
12//! ```
13
14use cairo_lang_utils::try_extract_matches;
15
16use super::snapshot::snapshot_ty;
17use super::utils::peel_snapshot;
18use crate::define_libfunc_hierarchy;
19use crate::extensions::boxing::box_ty;
20use crate::extensions::lib_func::{
21    DeferredOutputKind, LibfuncSignature, OutputVarInfo, ParamSignature, SierraApChange,
22    SignatureOnlyGenericLibfunc, SignatureSpecializationContext, SpecializationContext,
23};
24use crate::extensions::type_specialization_context::TypeSpecializationContext;
25use crate::extensions::types::TypeInfo;
26use crate::extensions::utils::ty_with_optional_snapshot;
27use crate::extensions::{
28    ConcreteType, NamedLibfunc, NamedType, OutputVarReferenceInfo, SignatureBasedConcreteLibfunc,
29    SpecializationError, args_as_single_type,
30};
31use crate::ids::{ConcreteTypeId, GenericTypeId};
32use crate::program::{ConcreteTypeLongId, GenericArg};
33
34/// Type representing a struct.
35#[derive(Default)]
36pub struct StructType {}
37impl NamedType for StructType {
38    type Concrete = StructConcreteType;
39    const ID: GenericTypeId = GenericTypeId::new_inline("Struct");
40
41    fn specialize(
42        &self,
43        context: &dyn TypeSpecializationContext,
44        args: &[GenericArg],
45    ) -> Result<Self::Concrete, SpecializationError> {
46        Self::Concrete::new(context, args)
47    }
48}
49
50pub struct StructConcreteType {
51    pub info: TypeInfo,
52    pub members: Vec<ConcreteTypeId>,
53}
54impl StructConcreteType {
55    fn new(
56        context: &dyn TypeSpecializationContext,
57        args: &[GenericArg],
58    ) -> Result<Self, SpecializationError> {
59        let mut args_iter = args.iter();
60        args_iter
61            .next()
62            .and_then(|arg| try_extract_matches!(arg, GenericArg::UserType))
63            .ok_or(SpecializationError::UnsupportedGenericArg)?;
64        let mut duplicatable = true;
65        let mut droppable = true;
66        let mut storable = true;
67        let mut members: Vec<ConcreteTypeId> = Vec::with_capacity(args_iter.len());
68        let mut zero_sized = true;
69        for arg in args_iter {
70            let ty = try_extract_matches!(arg, GenericArg::Type)
71                .ok_or(SpecializationError::UnsupportedGenericArg)?
72                .clone();
73            let info = context.get_type_info(&ty)?;
74            if !info.storable {
75                storable = false;
76            }
77            if !info.duplicatable {
78                duplicatable = false;
79            }
80            if !info.droppable {
81                droppable = false;
82            }
83            zero_sized = zero_sized && info.zero_sized;
84            members.push(ty);
85        }
86        Ok(StructConcreteType {
87            info: TypeInfo {
88                long_id: ConcreteTypeLongId {
89                    generic_id: "Struct".into(),
90                    generic_args: args.to_vec(),
91                },
92                duplicatable,
93                droppable,
94                storable,
95                zero_sized,
96            },
97            members,
98        })
99    }
100
101    /// Returns the StructConcreteType of the given long id, or a specialization error if not
102    /// possible.
103    fn try_from_long_id(
104        context: &dyn SignatureSpecializationContext,
105        long_id: &ConcreteTypeLongId,
106    ) -> Result<Self, SpecializationError> {
107        if long_id.generic_id != StructType::ID {
108            return Err(SpecializationError::UnsupportedGenericArg);
109        }
110        Self::new(context, &long_id.generic_args)
111    }
112
113    /// Returns the StructConcreteType of the given type, or a specialization error if not possible.
114    pub fn try_from_concrete_type(
115        context: &dyn SignatureSpecializationContext,
116        ty: &ConcreteTypeId,
117    ) -> Result<Self, SpecializationError> {
118        Self::try_from_long_id(context, &context.get_type_info(ty)?.long_id)
119    }
120}
121impl ConcreteType for StructConcreteType {
122    fn info(&self) -> &TypeInfo {
123        &self.info
124    }
125}
126
127define_libfunc_hierarchy! {
128    pub enum StructLibfunc {
129        Construct(StructConstructLibfunc),
130        Deconstruct(StructDeconstructLibfunc),
131        SnapshotDeconstruct(StructSnapshotDeconstructLibfunc),
132        BoxedDeconstruct(StructBoxedDeconstructLibfunc),
133    }, StructConcreteLibfunc
134}
135
136/// Libfunc for constructing a struct.
137#[derive(Default)]
138pub struct StructConstructLibfunc {}
139impl SignatureOnlyGenericLibfunc for StructConstructLibfunc {
140    const STR_ID: &'static str = "struct_construct";
141
142    fn specialize_signature(
143        &self,
144        context: &dyn SignatureSpecializationContext,
145        args: &[GenericArg],
146    ) -> Result<LibfuncSignature, SpecializationError> {
147        let struct_type = args_as_single_type(args)?;
148        let type_info = context.get_type_info(struct_type)?;
149        let member_types =
150            StructConcreteType::try_from_long_id(context, &type_info.long_id)?.members;
151
152        let mut opt_same_as_param_idx = None;
153        for (idx, ty) in member_types.iter().enumerate() {
154            if !context.get_type_info(ty)?.zero_sized {
155                if opt_same_as_param_idx.is_some() {
156                    // There are multiple non-zero sized items, can't use the same param.
157                    opt_same_as_param_idx = None;
158                    break;
159                }
160                opt_same_as_param_idx = Some(idx);
161            }
162        }
163
164        Ok(LibfuncSignature::new_non_branch_ex(
165            member_types
166                .into_iter()
167                .map(|ty| ParamSignature {
168                    ty,
169                    allow_deferred: true,
170                    allow_add_const: true,
171                    allow_const: true,
172                })
173                .collect(),
174            vec![OutputVarInfo {
175                ty: struct_type.clone(),
176                ref_info: if type_info.zero_sized {
177                    OutputVarReferenceInfo::ZeroSized
178                } else if let Some(param_idx) = opt_same_as_param_idx {
179                    OutputVarReferenceInfo::SameAsParam { param_idx }
180                } else {
181                    OutputVarReferenceInfo::Deferred(DeferredOutputKind::Generic)
182                },
183            }],
184            SierraApChange::Known { new_vars_only: true },
185        ))
186    }
187}
188
189/// Libfunc for deconstructing a struct.
190#[derive(Default)]
191pub struct StructDeconstructLibfunc {}
192impl SignatureOnlyGenericLibfunc for StructDeconstructLibfunc {
193    const STR_ID: &'static str = "struct_deconstruct";
194
195    fn specialize_signature(
196        &self,
197        context: &dyn SignatureSpecializationContext,
198        args: &[GenericArg],
199    ) -> Result<LibfuncSignature, SpecializationError> {
200        let struct_type = args_as_single_type(args)?;
201        let member_types =
202            StructConcreteType::try_from_concrete_type(context, struct_type)?.members;
203        Ok(LibfuncSignature::new_non_branch_ex(
204            vec![ParamSignature {
205                ty: struct_type.clone(),
206                allow_deferred: true,
207                allow_add_const: false,
208                allow_const: true,
209            }],
210            member_types
211                .into_iter()
212                .map(|ty| {
213                    Ok(OutputVarInfo {
214                        ref_info: if context.get_type_info(&ty)?.zero_sized {
215                            OutputVarReferenceInfo::ZeroSized
216                        } else {
217                            // All memory of the deconstruction would have the same lifetime as the
218                            // first param - as it is its deconstruction.
219                            OutputVarReferenceInfo::PartialParam { param_idx: 0 }
220                        },
221                        ty,
222                    })
223                })
224                .collect::<Result<Vec<_>, _>>()?,
225            SierraApChange::Known { new_vars_only: true },
226        ))
227    }
228}
229
230/// Libfunc for deconstructing a struct snapshot.
231#[derive(Default)]
232pub struct StructSnapshotDeconstructLibfunc {}
233impl SignatureOnlyGenericLibfunc for StructSnapshotDeconstructLibfunc {
234    const STR_ID: &'static str = "struct_snapshot_deconstruct";
235
236    fn specialize_signature(
237        &self,
238        context: &dyn SignatureSpecializationContext,
239        args: &[GenericArg],
240    ) -> Result<LibfuncSignature, SpecializationError> {
241        let struct_type = args_as_single_type(args)?;
242        let member_types =
243            StructConcreteType::try_from_concrete_type(context, struct_type)?.members;
244        Ok(LibfuncSignature::new_non_branch(
245            vec![snapshot_ty(context, struct_type.clone())?],
246            member_types
247                .into_iter()
248                .map(|ty| {
249                    Ok(OutputVarInfo {
250                        ref_info: if context.get_type_info(&ty)?.zero_sized {
251                            OutputVarReferenceInfo::ZeroSized
252                        } else {
253                            // All memory of the deconstruction would have the same lifetime as the
254                            // first param - as it is its deconstruction.
255                            OutputVarReferenceInfo::PartialParam { param_idx: 0 }
256                        },
257                        ty: snapshot_ty(context, ty)?,
258                    })
259                })
260                .collect::<Result<Vec<_>, _>>()?,
261            SierraApChange::Known { new_vars_only: true },
262        ))
263    }
264}
265
266/// Concrete implementation of the boxed struct deconstruct libfunc.
267pub struct ConcreteStructBoxedDeconstructLibfunc {
268    /// The concrete types of the struct members (no additional snapshots and boxing) that will be
269    /// extracted as boxed values.
270    pub members: Vec<ConcreteTypeId>,
271    signature: LibfuncSignature,
272}
273
274impl SignatureBasedConcreteLibfunc for ConcreteStructBoxedDeconstructLibfunc {
275    fn signature(&self) -> &LibfuncSignature {
276        &self.signature
277    }
278}
279
280/// Libfunc for deconstructing a boxed struct into boxes of its members.
281#[derive(Default)]
282pub struct StructBoxedDeconstructLibfunc {}
283
284impl StructBoxedDeconstructLibfunc {
285    /// Analyzes a struct type to extract member types and snapshot information.
286    ///
287    /// This method handles both regular structs and snapshot-wrapped structs. For snapshot-wrapped
288    /// structs (e.g., `@StructType`), it unwraps the snapshot to get the underlying struct type,
289    /// then extracts the member types and indicates the snapshot status.
290    ///
291    /// # Returns `Result` of
292    /// - `Vec<ConcreteTypeId>`: The concrete types of each struct member
293    /// - `bool`: Whether the input struct was wrapped in a snapshot
294    fn analyze_struct_type(
295        context: &dyn SignatureSpecializationContext,
296        ty: &ConcreteTypeId,
297    ) -> Result<(Vec<ConcreteTypeId>, bool), SpecializationError> {
298        let type_info = context.get_type_info(ty)?;
299        let (inner_ty, is_snapshot) = peel_snapshot(ty, type_info)?;
300        let struct_type = StructConcreteType::try_from_concrete_type(context, inner_ty)?;
301        Ok((struct_type.members, is_snapshot))
302    }
303
304    /// Creates the libfunc signature for boxed struct deconstruction.
305    ///
306    /// # Parameters
307    /// - `ty`: The concrete type ID of the struct being deconstructed
308    /// - `member_types`: The concrete types of each struct member
309    /// - `is_snapshot`: Whether the struct was originally wrapped in a snapshot
310    ///
311    /// # Returns
312    /// A libfunc signature that takes a boxed struct as input and returns boxed versions
313    /// of each member. If `is_snapshot` is true, the members are also wrapped in snapshots.
314    fn create_signature(
315        context: &dyn SignatureSpecializationContext,
316        ty: ConcreteTypeId,
317        mut member_types: impl ExactSizeIterator<Item = ConcreteTypeId>,
318        is_snapshot: bool,
319    ) -> Result<LibfuncSignature, SpecializationError> {
320        let mut outputs = Vec::with_capacity(member_types.len());
321
322        for member_ty in member_types.by_ref() {
323            let ref_info = OutputVarReferenceInfo::SameAsParam { param_idx: 0 };
324            outputs.push(OutputVarInfo {
325                ty: box_ty(
326                    context,
327                    ty_with_optional_snapshot(context, member_ty.clone(), is_snapshot)?,
328                )?,
329                ref_info,
330            });
331            if !context.get_type_info(&member_ty)?.zero_sized {
332                break;
333            }
334        }
335
336        for member_ty in member_types {
337            let ref_info = OutputVarReferenceInfo::Deferred(DeferredOutputKind::AddConst);
338            outputs.push(OutputVarInfo {
339                ty: box_ty(context, ty_with_optional_snapshot(context, member_ty, is_snapshot)?)?,
340                ref_info,
341            });
342        }
343
344        Ok(LibfuncSignature::new_non_branch_ex(
345            vec![ParamSignature::new(box_ty(context, ty)?).with_allow_add_const()],
346            outputs,
347            SierraApChange::Known { new_vars_only: true },
348        ))
349    }
350}
351
352impl NamedLibfunc for StructBoxedDeconstructLibfunc {
353    type Concrete = ConcreteStructBoxedDeconstructLibfunc;
354    const STR_ID: &'static str = "struct_boxed_deconstruct";
355
356    fn specialize_signature(
357        &self,
358        context: &dyn SignatureSpecializationContext,
359        args: &[GenericArg],
360    ) -> Result<LibfuncSignature, SpecializationError> {
361        let ty = args_as_single_type(args)?;
362        let (member_types, is_snapshot) = Self::analyze_struct_type(context, ty)?;
363        Self::create_signature(context, ty.clone(), member_types.into_iter(), is_snapshot)
364    }
365
366    fn specialize(
367        &self,
368        context: &dyn SpecializationContext,
369        args: &[GenericArg],
370    ) -> Result<Self::Concrete, SpecializationError> {
371        let ty = args_as_single_type(args)?;
372        let (members, is_snapshot) = Self::analyze_struct_type(context, ty)?;
373        let signature =
374            Self::create_signature(context, ty.clone(), members.iter().cloned(), is_snapshot)?;
375        Ok(ConcreteStructBoxedDeconstructLibfunc { members, signature })
376    }
377}