Skip to main content

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