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, EnrichedSemanticSignature, FunctionWithBodyId,
30 GeneratedFunctionKey, LocationId, SemanticFunctionIdEx,
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: EnrichedSemanticSignature<'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: EnrichedSemanticSignature<'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 ref_args: Vec<RefArg<'db>>,
337 pub location: LocationId<'db>,
338}
339
340impl<'db> LoweredExprExternEnum<'db> {
341 pub fn as_var_usage(
342 self,
343 ctx: &mut LoweringContext<'db, '_>,
344 builder: &mut BlockBuilder<'db>,
345 ) -> LoweringResult<'db, VarUsage<'db>> {
346 let concrete_variants = ctx
347 .db
348 .concrete_enum_variants(self.concrete_enum_id)
349 .map_err(LoweringFlowError::Failed)?;
350
351 let mut arm_var_ids = vec![];
352 let (sealed_blocks, block_ids): (Vec<_>, Vec<_>) = concrete_variants
353 .clone()
354 .into_iter()
355 .map(|concrete_variant| {
356 let mut subscope = builder.child_block_builder(ctx.blocks.alloc_empty());
357 let block_id = subscope.block_id;
358
359 let mut var_ids = vec![];
360 for ref_arg in &self.ref_args {
362 let var = ctx.new_var(VarRequest { ty: ref_arg.ty(), location: self.location });
363 var_ids.push(var);
364 if let RefArg::Ref(member_path) = ref_arg {
365 subscope.update_ref(ctx, member_path, var);
366 }
367 }
368
369 let before_returns = var_ids.len();
370 var_ids.extend(
371 extern_facade_return_tys(ctx.db, &concrete_variant.ty)
372 .iter()
373 .map(|ty| ctx.new_var(VarRequest { ty: *ty, location: self.location })),
374 );
375 let returns = var_ids[before_returns..].iter().copied();
376 let maybe_input =
377 extern_facade_expr(ctx, concrete_variant.ty, returns, self.location)
378 .as_var_usage(ctx, &mut subscope);
379 arm_var_ids.push(var_ids);
380 let input = match maybe_input {
381 Ok(var_usage) => var_usage,
382 Err(err) => {
383 return handle_lowering_flow_error(ctx, subscope, err)
384 .map(|_| (None, block_id));
385 }
386 };
387 let result = generators::EnumConstruct {
388 input,
389 variant: concrete_variant,
390 location: self.location,
391 }
392 .add(ctx, &mut subscope.statements);
393 Ok((subscope.goto_callsite(Some(result)), block_id))
394 })
395 .collect::<Result<Vec<_>, _>>()
396 .map_err(LoweringFlowError::Failed)?
397 .into_iter()
398 .unzip();
399
400 let match_info = MatchInfo::Extern(MatchExternInfo {
401 function: self.function.lowered(ctx.db),
402 inputs: self.inputs,
403 arms: zip_eq(zip_eq(concrete_variants, block_ids), arm_var_ids)
404 .map(|((variant_id, block_id), var_ids)| MatchArm {
405 arm_selector: MatchArmSelector::VariantId(variant_id),
406 block_id,
407 var_ids,
408 })
409 .collect(),
410 location: self.location,
411 });
412 builder
413 .merge_and_end_with_match(ctx, match_info, sealed_blocks, self.location)?
414 .as_var_usage(ctx, builder)
415 }
416}
417
418#[derive(Clone, Debug)]
419pub enum RefArg<'db> {
420 Ref(semantic::ExprVarMemberPath<'db>),
422 Temp(semantic::TypeId<'db>),
424}
425impl<'db> RefArg<'db> {
426 pub fn ty(&self) -> semantic::TypeId<'db> {
428 match self {
429 RefArg::Ref(member_path) => member_path.ty(),
430 RefArg::Temp(ty) => *ty,
431 }
432 }
433}
434
435pub type LoweringResult<'db, T> = Result<T, LoweringFlowError<'db>>;
436
437#[derive(Debug, Clone)]
439pub enum LoweringFlowError<'db> {
440 Failed(DiagnosticAdded),
442 Panic(VarUsage<'db>, LocationId<'db>),
445 Return(VarUsage<'db>, LocationId<'db>),
448 Match(MatchInfo<'db>),
451}
452impl<'db> LoweringFlowError<'db> {
453 pub fn is_unreachable(&self) -> bool {
454 match self {
455 LoweringFlowError::Failed(_) => false,
456 LoweringFlowError::Panic(_, _)
457 | LoweringFlowError::Return(_, _)
458 | LoweringFlowError::Match(_) => true,
459 }
460 }
461}
462
463pub fn handle_lowering_flow_error<'db, 'mt>(
465 ctx: &mut LoweringContext<'db, 'mt>,
466 mut builder: BlockBuilder<'db>,
467 err: LoweringFlowError<'db>,
468) -> Maybe<()> {
469 match err {
470 LoweringFlowError::Failed(diag_added) => Err(diag_added),
471 LoweringFlowError::Return(return_var, location) => builder.ret(ctx, return_var, location),
472 LoweringFlowError::Panic(data_var, location) => {
473 let panic_instance = generators::StructConstruct {
474 inputs: vec![],
475 ty: get_ty_by_name(
476 ctx.db,
477 core_module(ctx.db),
478 SmolStrId::from(ctx.db, "Panic"),
479 vec![],
480 ),
481 location,
482 }
483 .add(ctx, &mut builder.statements);
484 let err_instance = generators::StructConstruct {
485 inputs: vec![panic_instance, data_var],
486 ty: TypeLongId::Tuple(vec![
487 ctx.variables[panic_instance.var_id].ty,
488 ctx.variables[data_var.var_id].ty,
489 ])
490 .intern(ctx.db),
491 location,
492 }
493 .add(ctx, &mut builder.statements);
494 builder.panic(ctx, err_instance)
495 }
496 LoweringFlowError::Match(info) => {
497 builder.unreachable_match(ctx, info);
498 Ok(())
499 }
500 }
501}