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 pub variables: Arena<Variable>,
39 pub module_file_id: ModuleFileId,
41 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 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 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
86pub struct EncapsulatingLoweringContext<'db> {
91 pub db: &'db dyn LoweringGroup,
92 pub semantic_function_id: defs::ids::FunctionWithBodyId,
94 pub function_body: Arc<semantic::FunctionBody>,
96 pub semantic_defs: UnorderedHashMap<semantic::VarId, semantic::Binding>,
100 pub expr_formatter: ExprFormatter<'db>,
102 pub usages: Usages,
104 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 pub variables: VariableAllocator<'db>,
130 pub signature: Signature,
132 pub function_id: FunctionWithBodyId,
134 pub concrete_function_id: ConcreteFunctionWithBodyId,
137 pub current_loop_expr_id: Option<semantic::ExprId>,
139 pub diagnostics: LoweringDiagnostics,
141 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 pub fn new_var(&mut self, req: VarRequest) -> VariableId {
183 self.variables.new_var(req)
184 }
185
186 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 pub fn get_location(&self, stable_ptr: SyntaxStablePtrId) -> LocationId {
196 self.variables.get_location(stable_ptr)
197 }
198}
199
200pub struct VarRequest {
202 pub ty: semantic::TypeId,
203 pub location: LocationId,
204}
205
206#[derive(Clone, Debug)]
208pub enum LoweredExpr {
209 AtVariable(VarUsage),
211 Tuple {
213 exprs: Vec<LoweredExpr>,
214 location: LocationId,
215 },
216 FixedSizeArray {
218 ty: semantic::TypeId,
219 exprs: Vec<LoweredExpr>,
220 location: LocationId,
221 },
222 ExternEnum(LoweredExprExternEnum),
224 Member(ExprVarMemberPath, LocationId),
225 Snapshot {
226 expr: Box<LoweredExpr>,
227 location: LocationId,
228 },
229}
230impl LoweredExpr {
231 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#[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 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#[derive(Debug)]
395pub enum LoweringFlowError {
396 Failed(DiagnosticAdded),
398 Panic(VarUsage, LocationId),
399 Return(VarUsage, LocationId),
400 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
415pub 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}