cairo_lang_lowering/lower/
context.rs

1use std::ops::{Deref, DerefMut, Index};
2use std::sync::Arc;
3
4use cairo_lang_defs::ids::{LanguageElementId, ModuleFileId};
5use cairo_lang_diagnostics::{DiagnosticAdded, Maybe};
6use cairo_lang_semantic::expr::fmt::ExprFormatter;
7use cairo_lang_semantic::items::enm::SemanticEnumEx;
8use cairo_lang_semantic::items::imp::ImplLookupContext;
9use cairo_lang_semantic::usage::Usages;
10use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
11use cairo_lang_utils::Intern;
12use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
13use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
14use defs::diagnostic_utils::StableLocation;
15use id_arena::Arena;
16use itertools::{Itertools, zip_eq};
17use semantic::corelib::{core_module, get_ty_by_name};
18use semantic::types::wrap_in_snapshots;
19use semantic::{ExprVarMemberPath, MatchArmSelector, TypeLongId};
20use {cairo_lang_defs as defs, cairo_lang_semantic as semantic};
21
22use super::block_builder::{BlockBuilder, SealedBlockBuilder};
23use super::generators;
24use crate::blocks::FlatBlocksBuilder;
25use crate::db::LoweringGroup;
26use crate::diagnostic::LoweringDiagnostics;
27use crate::ids::{
28    ConcreteFunctionWithBodyId, FunctionWithBodyId, GeneratedFunctionKey, LocationId,
29    SemanticFunctionIdEx, Signature,
30};
31use crate::lower::external::{extern_facade_expr, extern_facade_return_tys};
32use crate::objects::Variable;
33use crate::{FlatLowered, MatchArm, MatchExternInfo, MatchInfo, VarUsage, VariableId};
34
35pub struct VariableAllocator<'db> {
36    pub db: &'db dyn LoweringGroup,
37    /// Arena of allocated lowered variables.
38    pub variables: Arena<Variable>,
39    /// Module and file of the declared function.
40    pub module_file_id: ModuleFileId,
41    // Lookup context for impls.
42    pub lookup_context: ImplLookupContext,
43}
44impl<'db> VariableAllocator<'db> {
45    pub fn new(
46        db: &'db dyn LoweringGroup,
47        function_id: defs::ids::FunctionWithBodyId,
48        variables: Arena<Variable>,
49    ) -> Maybe<Self> {
50        let generic_params = db.function_with_body_generic_params(function_id)?;
51        let generic_param_ids = generic_params.iter().map(|p| p.id()).collect_vec();
52        Ok(Self {
53            db,
54            variables,
55            module_file_id: function_id.module_file_id(db.upcast()),
56            lookup_context: ImplLookupContext::new(
57                function_id.parent_module(db.upcast()),
58                generic_param_ids,
59            ),
60        })
61    }
62
63    /// Allocates a new variable in the context's variable arena according to the context.
64    pub fn new_var(&mut self, req: VarRequest) -> VariableId {
65        self.variables.alloc(Variable::new(
66            self.db,
67            self.lookup_context.clone(),
68            req.ty,
69            req.location,
70        ))
71    }
72
73    /// Retrieves the LocationId of a stable syntax pointer in the current function file.
74    pub fn get_location(&self, stable_ptr: SyntaxStablePtrId) -> LocationId {
75        LocationId::from_stable_location(self.db, StableLocation::new(stable_ptr))
76    }
77}
78impl Index<VariableId> for VariableAllocator<'_> {
79    type Output = Variable;
80
81    fn index(&self, index: VariableId) -> &Self::Output {
82        &self.variables[index]
83    }
84}
85
86/// Lowering context for the encapsulating semantic function.
87///
88/// Each semantic function may generate multiple lowered functions. This context is common to all
89/// the generated lowered functions of an encapsulating semantic function.
90pub struct EncapsulatingLoweringContext<'db> {
91    pub db: &'db dyn LoweringGroup,
92    /// Id for the current function being lowered.
93    pub semantic_function_id: defs::ids::FunctionWithBodyId,
94    /// Semantic model for current function body.
95    pub function_body: Arc<semantic::FunctionBody>,
96    /// Definitions encountered for semantic bindings. Since Constants are not lowered, this is
97    /// only used for variables.
98    // TODO(spapini): consider moving to semantic model.
99    pub semantic_defs: UnorderedHashMap<semantic::VarId, semantic::Binding>,
100    /// Expression formatter of the free function.
101    pub expr_formatter: ExprFormatter<'db>,
102    /// Block usages for the entire encapsulating function.
103    pub usages: Usages,
104    /// Lowerings of generated functions.
105    pub lowerings: OrderedHashMap<GeneratedFunctionKey, FlatLowered>,
106}
107impl<'db> EncapsulatingLoweringContext<'db> {
108    pub fn new(
109        db: &'db dyn LoweringGroup,
110        semantic_function_id: defs::ids::FunctionWithBodyId,
111    ) -> Maybe<Self> {
112        let function_body = db.function_body(semantic_function_id)?;
113        let usages = Usages::from_function_body(&function_body);
114        Ok(Self {
115            db,
116            semantic_function_id,
117            function_body,
118            semantic_defs: Default::default(),
119            expr_formatter: ExprFormatter { db: db.upcast(), function_id: semantic_function_id },
120            usages,
121            lowerings: Default::default(),
122        })
123    }
124}
125
126pub struct LoweringContext<'a, 'db> {
127    pub encapsulating_ctx: Option<&'a mut EncapsulatingLoweringContext<'db>>,
128    /// Variable allocator.
129    pub variables: VariableAllocator<'db>,
130    /// Current function signature.
131    pub signature: Signature,
132    /// Id for the current function being lowered.
133    pub function_id: FunctionWithBodyId,
134    /// Id for the current concrete function to be used when generating recursive calls.
135    /// This it the generic function specialized with its own generic parameters.
136    pub concrete_function_id: ConcreteFunctionWithBodyId,
137    /// Current loop expression needed for recursive calls in `continue`
138    pub current_loop_expr_id: Option<semantic::ExprId>,
139    /// Current emitted diagnostics.
140    pub diagnostics: LoweringDiagnostics,
141    /// Lowered blocks of the function.
142    pub blocks: FlatBlocksBuilder,
143}
144impl<'a, 'db> LoweringContext<'a, 'db> {
145    pub fn new(
146        global_ctx: &'a mut EncapsulatingLoweringContext<'db>,
147        function_id: FunctionWithBodyId,
148        signature: Signature,
149    ) -> Maybe<Self>
150    where
151        'db: 'a,
152    {
153        let db = global_ctx.db;
154        let concrete_function_id = function_id.to_concrete(db)?;
155        let semantic_function = function_id.base_semantic_function(db);
156        Ok(Self {
157            encapsulating_ctx: Some(global_ctx),
158            variables: VariableAllocator::new(db, semantic_function, Default::default())?,
159            signature,
160            function_id,
161            concrete_function_id,
162            current_loop_expr_id: Option::None,
163            diagnostics: LoweringDiagnostics::default(),
164            blocks: Default::default(),
165        })
166    }
167}
168impl<'db> Deref for LoweringContext<'_, 'db> {
169    type Target = EncapsulatingLoweringContext<'db>;
170
171    fn deref(&self) -> &Self::Target {
172        self.encapsulating_ctx.as_ref().unwrap()
173    }
174}
175impl DerefMut for LoweringContext<'_, '_> {
176    fn deref_mut(&mut self) -> &mut Self::Target {
177        self.encapsulating_ctx.as_mut().unwrap()
178    }
179}
180impl LoweringContext<'_, '_> {
181    /// Allocates a new variable in the context's variable arena according to the context.
182    pub fn new_var(&mut self, req: VarRequest) -> VariableId {
183        self.variables.new_var(req)
184    }
185
186    /// Same as `new_var` but returns it as a `VarUsage`.
187    /// This is useful when the variable definition and usage locations are the same.
188    pub fn new_var_usage(&mut self, req: VarRequest) -> VarUsage {
189        let location = req.location;
190
191        VarUsage { var_id: self.variables.new_var(req), location }
192    }
193
194    /// Retrieves the LocationId of a stable syntax pointer in the current function file.
195    pub fn get_location(&self, stable_ptr: SyntaxStablePtrId) -> LocationId {
196        self.variables.get_location(stable_ptr)
197    }
198}
199
200/// Request for a lowered variable allocation.
201pub struct VarRequest {
202    pub ty: semantic::TypeId,
203    pub location: LocationId,
204}
205
206/// Representation of the value of a computed expression.
207#[derive(Clone, Debug)]
208pub enum LoweredExpr {
209    /// The expression value lies in a variable.
210    AtVariable(VarUsage),
211    /// The expression value is a tuple.
212    Tuple {
213        exprs: Vec<LoweredExpr>,
214        location: LocationId,
215    },
216    /// The expression value is a fixed size array.
217    FixedSizeArray {
218        ty: semantic::TypeId,
219        exprs: Vec<LoweredExpr>,
220        location: LocationId,
221    },
222    /// The expression value is an enum result from an extern call.
223    ExternEnum(LoweredExprExternEnum),
224    Member(ExprVarMemberPath, LocationId),
225    Snapshot {
226        expr: Box<LoweredExpr>,
227        location: LocationId,
228    },
229}
230impl LoweredExpr {
231    // Returns a VarUsage corresponding to the lowered expression.
232    pub fn as_var_usage(
233        self,
234        ctx: &mut LoweringContext<'_, '_>,
235        builder: &mut BlockBuilder,
236    ) -> Result<VarUsage, LoweringFlowError> {
237        match self {
238            LoweredExpr::AtVariable(var_usage) => Ok(var_usage),
239            LoweredExpr::Tuple { exprs, location } => {
240                let inputs: Vec<_> = exprs
241                    .into_iter()
242                    .map(|expr| expr.as_var_usage(ctx, builder))
243                    .collect::<Result<Vec<_>, _>>()?;
244                let tys =
245                    inputs.iter().map(|var_usage| ctx.variables[var_usage.var_id].ty).collect();
246                let ty = semantic::TypeLongId::Tuple(tys).intern(ctx.db);
247                Ok(generators::StructConstruct { inputs, ty, location }
248                    .add(ctx, &mut builder.statements))
249            }
250            LoweredExpr::ExternEnum(extern_enum) => extern_enum.as_var_usage(ctx, builder),
251            LoweredExpr::Member(member_path, _location) => {
252                Ok(builder.get_ref(ctx, &member_path).unwrap())
253            }
254            LoweredExpr::Snapshot { expr, location } => {
255                let input = expr.clone().as_var_usage(ctx, builder)?;
256                let (original, snapshot) =
257                    generators::Snapshot { input, location }.add(ctx, &mut builder.statements);
258                if let LoweredExpr::Member(member_path, _location) = &*expr {
259                    builder.update_ref(ctx, member_path, original);
260                }
261
262                Ok(VarUsage { var_id: snapshot, location })
263            }
264            LoweredExpr::FixedSizeArray { exprs, location, ty } => {
265                let inputs = exprs
266                    .into_iter()
267                    .map(|expr| expr.as_var_usage(ctx, builder))
268                    .collect::<Result<Vec<_>, _>>()?;
269                Ok(generators::StructConstruct { inputs, ty, location }
270                    .add(ctx, &mut builder.statements))
271            }
272        }
273    }
274
275    pub fn ty(&self, ctx: &mut LoweringContext<'_, '_>) -> semantic::TypeId {
276        match self {
277            LoweredExpr::AtVariable(var_usage) => ctx.variables[var_usage.var_id].ty,
278            LoweredExpr::Tuple { exprs, .. } => {
279                semantic::TypeLongId::Tuple(exprs.iter().map(|expr| expr.ty(ctx)).collect())
280                    .intern(ctx.db)
281            }
282            LoweredExpr::ExternEnum(extern_enum) => semantic::TypeLongId::Concrete(
283                semantic::ConcreteTypeId::Enum(extern_enum.concrete_enum_id),
284            )
285            .intern(ctx.db),
286            LoweredExpr::Member(member_path, _) => member_path.ty(),
287            LoweredExpr::Snapshot { expr, .. } => {
288                wrap_in_snapshots(ctx.db.upcast(), expr.ty(ctx), 1)
289            }
290            LoweredExpr::FixedSizeArray { ty, .. } => *ty,
291        }
292    }
293    pub fn location(&self) -> LocationId {
294        match &self {
295            LoweredExpr::AtVariable(VarUsage { location, .. })
296            | LoweredExpr::Tuple { location, .. }
297            | LoweredExpr::ExternEnum(LoweredExprExternEnum { location, .. })
298            | LoweredExpr::Member(_, location)
299            | LoweredExpr::Snapshot { location, .. } => *location,
300            LoweredExpr::FixedSizeArray { location, .. } => *location,
301        }
302    }
303}
304
305/// Lazy expression value of an extern call returning an enum.
306#[derive(Clone, Debug)]
307pub struct LoweredExprExternEnum {
308    pub function: semantic::FunctionId,
309    pub concrete_enum_id: semantic::ConcreteEnumId,
310    pub inputs: Vec<VarUsage>,
311    pub member_paths: Vec<semantic::ExprVarMemberPath>,
312    pub location: LocationId,
313}
314impl LoweredExprExternEnum {
315    pub fn as_var_usage(
316        self,
317        ctx: &mut LoweringContext<'_, '_>,
318        builder: &mut BlockBuilder,
319    ) -> LoweringResult<VarUsage> {
320        let concrete_variants = ctx
321            .db
322            .concrete_enum_variants(self.concrete_enum_id)
323            .map_err(LoweringFlowError::Failed)?;
324
325        let mut arm_var_ids = vec![];
326        let (sealed_blocks, block_ids): (Vec<_>, Vec<_>) = concrete_variants
327            .clone()
328            .into_iter()
329            .map(|concrete_variant| {
330                let mut subscope = builder.child_block_builder(ctx.blocks.alloc_empty());
331                let block_id = subscope.block_id;
332
333                let mut var_ids = vec![];
334                // Bind the ref parameters.
335                for member_path in &self.member_paths {
336                    let var =
337                        ctx.new_var(VarRequest { ty: member_path.ty(), location: self.location });
338                    var_ids.push(var);
339
340                    subscope.update_ref(ctx, member_path, var);
341                }
342
343                let variant_vars = extern_facade_return_tys(ctx, concrete_variant.ty)
344                    .into_iter()
345                    .map(|ty| ctx.new_var(VarRequest { ty, location: self.location }))
346                    .collect_vec();
347                var_ids.extend(variant_vars.iter());
348
349                arm_var_ids.push(var_ids);
350                let maybe_input =
351                    extern_facade_expr(ctx, concrete_variant.ty, variant_vars, self.location)
352                        .as_var_usage(ctx, &mut subscope);
353                let input = match maybe_input {
354                    Ok(var_usage) => var_usage,
355                    Err(err) => {
356                        return lowering_flow_error_to_sealed_block(ctx, subscope, err)
357                            .map(|sb| (sb, block_id));
358                    }
359                };
360                let result = generators::EnumConstruct {
361                    input,
362                    variant: concrete_variant,
363                    location: self.location,
364                }
365                .add(ctx, &mut subscope.statements);
366                Ok((subscope.goto_callsite(Some(result)), block_id))
367            })
368            .collect::<Result<Vec<_>, _>>()
369            .map_err(LoweringFlowError::Failed)?
370            .into_iter()
371            .unzip();
372
373        let match_info = MatchInfo::Extern(MatchExternInfo {
374            function: self.function.lowered(ctx.db),
375            inputs: self.inputs,
376            arms: zip_eq(zip_eq(concrete_variants, block_ids), arm_var_ids)
377                .map(|((variant_id, block_id), var_ids)| MatchArm {
378                    arm_selector: MatchArmSelector::VariantId(variant_id),
379                    block_id,
380                    var_ids,
381                })
382                .collect(),
383            location: self.location,
384        });
385        builder
386            .merge_and_end_with_match(ctx, match_info, sealed_blocks, self.location)?
387            .as_var_usage(ctx, builder)
388    }
389}
390
391pub type LoweringResult<T> = Result<T, LoweringFlowError>;
392
393/// Cases where the flow of lowering an expression should halt.
394#[derive(Debug)]
395pub enum LoweringFlowError {
396    /// Computation failure. A corresponding diagnostic should be emitted.
397    Failed(DiagnosticAdded),
398    Panic(VarUsage, LocationId),
399    Return(VarUsage, LocationId),
400    /// Every match arm is terminating - does not flow to parent builder
401    /// e.g. returns or panics.
402    Match(MatchInfo),
403}
404impl LoweringFlowError {
405    pub fn is_unreachable(&self) -> bool {
406        match self {
407            LoweringFlowError::Failed(_) => false,
408            LoweringFlowError::Panic(_, _)
409            | LoweringFlowError::Return(_, _)
410            | LoweringFlowError::Match(_) => true,
411        }
412    }
413}
414
415/// Converts a lowering flow error to the appropriate block builder end, if possible.
416pub fn lowering_flow_error_to_sealed_block(
417    ctx: &mut LoweringContext<'_, '_>,
418    mut builder: BlockBuilder,
419    err: LoweringFlowError,
420) -> Maybe<SealedBlockBuilder> {
421    let block_id = builder.block_id;
422    match err {
423        LoweringFlowError::Failed(diag_added) => return Err(diag_added),
424        LoweringFlowError::Return(return_var, location) => {
425            builder.ret(ctx, return_var, location)?;
426        }
427        LoweringFlowError::Panic(data_var, location) => {
428            let panic_instance = generators::StructConstruct {
429                inputs: vec![],
430                ty: get_ty_by_name(
431                    ctx.db.upcast(),
432                    core_module(ctx.db.upcast()),
433                    "Panic".into(),
434                    vec![],
435                ),
436                location,
437            }
438            .add(ctx, &mut builder.statements);
439            let err_instance = generators::StructConstruct {
440                inputs: vec![panic_instance, data_var],
441                ty: TypeLongId::Tuple(vec![
442                    ctx.variables[panic_instance.var_id].ty,
443                    ctx.variables[data_var.var_id].ty,
444                ])
445                .intern(ctx.db),
446                location,
447            }
448            .add(ctx, &mut builder.statements);
449            builder.panic(ctx, err_instance)?;
450        }
451        LoweringFlowError::Match(info) => {
452            builder.unreachable_match(ctx, info);
453        }
454    }
455    Ok(SealedBlockBuilder::Ends(block_id))
456}