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 pub variables: Arena<Variable>,
40 pub module_file_id: ModuleFileId,
42 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 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 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
87pub struct EncapsulatingLoweringContext<'db> {
92 pub db: &'db dyn LoweringGroup,
93 pub semantic_function_id: defs::ids::FunctionWithBodyId,
95 pub function_body: Arc<semantic::FunctionBody>,
97 pub semantic_defs: UnorderedHashMap<semantic::VarId, semantic::Binding>,
101 pub expr_formatter: ExprFormatter<'db>,
103 pub usages: Usages,
105 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#[derive(Clone)]
129pub struct LoopEarlyReturnInfo {
130 pub normal_return_variant: ConcreteVariant,
131 pub early_return_variant: ConcreteVariant,
132}
133
134pub struct LoopContext {
136 pub loop_expr_id: semantic::ExprId,
138 pub early_return_info: Option<LoopEarlyReturnInfo>,
140}
141
142pub struct LoweringContext<'a, 'db> {
143 pub encapsulating_ctx: Option<&'a mut EncapsulatingLoweringContext<'db>>,
144 pub variables: VariableAllocator<'db>,
146 pub signature: Signature,
148 pub function_id: FunctionWithBodyId,
150 pub concrete_function_id: ConcreteFunctionWithBodyId,
153 pub current_loop_ctx: Option<LoopContext>,
155 pub diagnostics: LoweringDiagnostics,
157 pub blocks: BlocksBuilder,
159 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 pub fn new_var(&mut self, req: VarRequest) -> VariableId {
203 self.variables.new_var(req)
204 }
205
206 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 pub fn get_location(&self, stable_ptr: SyntaxStablePtrId) -> LocationId {
216 self.variables.get_location(stable_ptr)
217 }
218}
219
220pub struct VarRequest {
222 pub ty: semantic::TypeId,
223 pub location: LocationId,
224}
225
226#[derive(Clone, Debug)]
228pub enum LoweredExpr {
229 AtVariable(VarUsage),
231 Tuple {
233 exprs: Vec<LoweredExpr>,
234 location: LocationId,
235 },
236 FixedSizeArray {
238 ty: semantic::TypeId,
239 exprs: Vec<LoweredExpr>,
240 location: LocationId,
241 },
242 ExternEnum(LoweredExprExternEnum),
244 Member(ExprVarMemberPath, LocationId),
245 Snapshot {
246 expr: Box<LoweredExpr>,
247 location: LocationId,
248 },
249}
250impl LoweredExpr {
251 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#[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 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#[derive(Debug, Clone)]
419pub enum LoweringFlowError {
420 Failed(DiagnosticAdded),
422 Panic(VarUsage, LocationId),
423 Return(VarUsage, LocationId),
424 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
439pub 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}