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 pub variables: VariableArena<'db>,
40 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 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 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
81pub struct EncapsulatingLoweringContext<'db> {
86 pub db: &'db dyn Database,
87 pub semantic_function_id: defs::ids::FunctionWithBodyId<'db>,
89 pub function_body: &'db semantic::FunctionBody<'db>,
91 pub semantic_defs: UnorderedHashMap<semantic::VarId<'db>, semantic::Binding<'db>>,
95 pub expr_formatter: ExprFormatter<'db>,
97 pub usages: Usages<'db>,
99 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#[derive(Clone)]
123pub struct LoopEarlyReturnInfo<'db> {
124 pub normal_return_variant: ConcreteVariant<'db>,
125 pub early_return_variant: ConcreteVariant<'db>,
126}
127
128pub struct LoopContext<'db> {
130 pub loop_expr_id: semantic::ExprId,
132 pub early_return_info: Option<LoopEarlyReturnInfo<'db>>,
134}
135
136pub struct LoweringContext<'db, 'mt> {
137 pub encapsulating_ctx: Option<&'mt mut EncapsulatingLoweringContext<'db>>,
138 pub variables: VariableAllocator<'db>,
140 pub signature: Signature<'db>,
142 pub function_id: FunctionWithBodyId<'db>,
144 pub concrete_function_id: ConcreteFunctionWithBodyId<'db>,
147 pub current_loop_ctx: Option<LoopContext<'db>>,
149 pub diagnostics: LoweringDiagnostics<'db>,
151 pub blocks: BlocksBuilder<'db>,
153 pub return_type: semantic::TypeId<'db>,
155 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 pub fn new_var(&mut self, req: VarRequest<'db>) -> VariableId {
203 self.variables.new_var(req)
204 }
205
206 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 pub fn get_location(&self, stable_ptr: SyntaxStablePtrId<'db>) -> LocationId<'db> {
216 self.variables.get_location(stable_ptr)
217 }
218}
219
220pub struct VarRequest<'db> {
222 pub ty: semantic::TypeId<'db>,
223 pub location: LocationId<'db>,
224}
225
226#[derive(Clone, Debug)]
228pub enum LoweredExpr<'db> {
229 AtVariable(VarUsage<'db>),
231 Tuple {
233 exprs: Vec<LoweredExpr<'db>>,
234 location: LocationId<'db>,
235 },
236 FixedSizeArray {
238 ty: semantic::TypeId<'db>,
239 exprs: Vec<LoweredExpr<'db>>,
240 location: LocationId<'db>,
241 },
242 ExternEnum(LoweredExprExternEnum<'db>),
244 MemberPath(ExprVarMemberPath<'db>, LocationId<'db>),
246 Snapshot {
247 expr: Box<LoweredExpr<'db>>,
248 location: LocationId<'db>,
249 },
250}
251impl<'db> LoweredExpr<'db> {
252 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#[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 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#[derive(Debug, Clone)]
420pub enum LoweringFlowError<'db> {
421 Failed(DiagnosticAdded),
423 Panic(VarUsage<'db>, LocationId<'db>),
426 Return(VarUsage<'db>, LocationId<'db>),
429 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
444pub 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}