cairo_lang_lowering/lower/
context.rs

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