cairo_lang_lowering/
ids.rs

1use cairo_lang_debug::DebugWithDb;
2use cairo_lang_defs::ids::UnstableSalsaId;
3use cairo_lang_diagnostics::{DiagnosticAdded, DiagnosticNote, Maybe};
4use cairo_lang_proc_macros::{DebugWithDb, SemanticObject};
5use cairo_lang_semantic::corelib::panic_destruct_trait_fn;
6use cairo_lang_semantic::items::functions::ImplGenericFunctionId;
7use cairo_lang_semantic::items::imp::ImplLongId;
8use cairo_lang_semantic::{GenericArgumentId, TypeLongId};
9use cairo_lang_syntax::node::{TypedStablePtr, ast};
10use cairo_lang_utils::{
11    Intern, LookupIntern, define_short_id, extract_matches, try_extract_matches,
12};
13use defs::diagnostic_utils::StableLocation;
14use defs::ids::{ExternFunctionId, FreeFunctionId};
15use semantic::items::functions::GenericFunctionId;
16use semantic::substitution::{GenericSubstitution, SubstitutionRewriter};
17use semantic::{ExprVar, Mutability};
18use smol_str::SmolStr;
19use {cairo_lang_defs as defs, cairo_lang_semantic as semantic};
20
21use crate::Location;
22use crate::db::LoweringGroup;
23use crate::ids::semantic::substitution::SemanticRewriter;
24
25#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
26pub enum FunctionWithBodyLongId {
27    Semantic(defs::ids::FunctionWithBodyId),
28    Generated { parent: defs::ids::FunctionWithBodyId, key: GeneratedFunctionKey },
29}
30define_short_id!(
31    FunctionWithBodyId,
32    FunctionWithBodyLongId,
33    LoweringGroup,
34    lookup_intern_lowering_function_with_body,
35    intern_lowering_function_with_body
36);
37impl FunctionWithBodyLongId {
38    pub fn base_semantic_function(
39        &self,
40        _db: &dyn LoweringGroup,
41    ) -> cairo_lang_defs::ids::FunctionWithBodyId {
42        match *self {
43            FunctionWithBodyLongId::Semantic(id) => id,
44            FunctionWithBodyLongId::Generated { parent, .. } => parent,
45        }
46    }
47    pub fn to_concrete(&self, db: &dyn LoweringGroup) -> Maybe<ConcreteFunctionWithBodyLongId> {
48        Ok(match *self {
49            FunctionWithBodyLongId::Semantic(semantic) => ConcreteFunctionWithBodyLongId::Semantic(
50                semantic::ConcreteFunctionWithBodyId::from_generic(db.upcast(), semantic)?,
51            ),
52            FunctionWithBodyLongId::Generated { parent, key } => {
53                ConcreteFunctionWithBodyLongId::Generated(GeneratedFunction {
54                    parent: semantic::ConcreteFunctionWithBodyId::from_generic(
55                        db.upcast(),
56                        parent,
57                    )?,
58                    key,
59                })
60            }
61        })
62    }
63}
64impl FunctionWithBodyId {
65    pub fn base_semantic_function(
66        &self,
67        db: &dyn LoweringGroup,
68    ) -> cairo_lang_defs::ids::FunctionWithBodyId {
69        self.lookup_intern(db).base_semantic_function(db)
70    }
71    pub fn signature(&self, db: &dyn LoweringGroup) -> Maybe<Signature> {
72        Ok(db.priv_function_with_body_lowering(*self)?.signature.clone())
73    }
74    pub fn to_concrete(&self, db: &dyn LoweringGroup) -> Maybe<ConcreteFunctionWithBodyId> {
75        Ok(self.lookup_intern(db).to_concrete(db)?.intern(db))
76    }
77}
78pub trait SemanticFunctionWithBodyIdEx {
79    fn lowered(&self, db: &dyn LoweringGroup) -> FunctionWithBodyId;
80}
81impl SemanticFunctionWithBodyIdEx for cairo_lang_defs::ids::FunctionWithBodyId {
82    fn lowered(&self, db: &dyn LoweringGroup) -> FunctionWithBodyId {
83        FunctionWithBodyLongId::Semantic(*self).intern(db)
84    }
85}
86
87/// Concrete function with body.
88#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
89pub enum ConcreteFunctionWithBodyLongId {
90    Semantic(semantic::ConcreteFunctionWithBodyId),
91    Generated(GeneratedFunction),
92}
93define_short_id!(
94    ConcreteFunctionWithBodyId,
95    ConcreteFunctionWithBodyLongId,
96    LoweringGroup,
97    lookup_intern_lowering_concrete_function_with_body,
98    intern_lowering_concrete_function_with_body
99);
100
101impl ConcreteFunctionWithBodyId {
102    pub fn is_panic_destruct_fn(&self, db: &dyn LoweringGroup) -> Maybe<bool> {
103        match db.lookup_intern_lowering_concrete_function_with_body(*self) {
104            ConcreteFunctionWithBodyLongId::Semantic(semantic_func) => {
105                semantic_func.is_panic_destruct_fn(db.upcast())
106            }
107            ConcreteFunctionWithBodyLongId::Generated(GeneratedFunction {
108                parent: _,
109                key: GeneratedFunctionKey::TraitFunc(function, _),
110            }) => Ok(extract_matches!(
111                function.get_concrete(db.upcast()).generic_function,
112                GenericFunctionId::Impl
113            )
114            .function
115                == panic_destruct_trait_fn(db.upcast())),
116            _ => Ok(false),
117        }
118    }
119}
120
121impl UnstableSalsaId for ConcreteFunctionWithBodyId {
122    fn get_internal_id(&self) -> &salsa::InternId {
123        &self.0
124    }
125}
126impl ConcreteFunctionWithBodyLongId {
127    pub fn function_with_body_id(&self, db: &dyn LoweringGroup) -> FunctionWithBodyId {
128        let semantic_db = db.upcast();
129        let long_id = match *self {
130            ConcreteFunctionWithBodyLongId::Semantic(id) => {
131                FunctionWithBodyLongId::Semantic(id.function_with_body_id(semantic_db))
132            }
133            ConcreteFunctionWithBodyLongId::Generated(GeneratedFunction { parent, key }) => {
134                FunctionWithBodyLongId::Generated {
135                    parent: parent.function_with_body_id(semantic_db),
136                    key,
137                }
138            }
139        };
140        long_id.intern(db)
141    }
142    pub fn substitution(&self, db: &dyn LoweringGroup) -> Maybe<GenericSubstitution> {
143        let semantic_db = db.upcast();
144        match self {
145            ConcreteFunctionWithBodyLongId::Semantic(id) => id.substitution(semantic_db),
146            ConcreteFunctionWithBodyLongId::Generated(GeneratedFunction { parent, .. }) => {
147                parent.substitution(semantic_db)
148            }
149        }
150    }
151    pub fn function_id(&self, db: &dyn LoweringGroup) -> Maybe<FunctionId> {
152        let semantic_db = db.upcast();
153        let long_id = match self {
154            ConcreteFunctionWithBodyLongId::Semantic(id) => {
155                FunctionLongId::Semantic(id.function_id(semantic_db)?)
156            }
157            ConcreteFunctionWithBodyLongId::Generated(generated) => {
158                FunctionLongId::Generated(*generated)
159            }
160        };
161        Ok(long_id.intern(db))
162    }
163    pub fn base_semantic_function(
164        &self,
165        _db: &dyn LoweringGroup,
166    ) -> semantic::ConcreteFunctionWithBodyId {
167        match *self {
168            ConcreteFunctionWithBodyLongId::Semantic(id) => id,
169            ConcreteFunctionWithBodyLongId::Generated(generated) => generated.parent,
170        }
171    }
172    pub fn name(&self, db: &dyn LoweringGroup) -> SmolStr {
173        match self {
174            ConcreteFunctionWithBodyLongId::Semantic(semantic) => semantic.name(db.upcast()),
175            ConcreteFunctionWithBodyLongId::Generated(generated) => generated.name(db),
176        }
177    }
178}
179impl ConcreteFunctionWithBodyId {
180    pub fn from_semantic(
181        db: &dyn LoweringGroup,
182        semantic: semantic::ConcreteFunctionWithBodyId,
183    ) -> Self {
184        ConcreteFunctionWithBodyLongId::Semantic(semantic).intern(db)
185    }
186    pub fn function_with_body_id(&self, db: &dyn LoweringGroup) -> FunctionWithBodyId {
187        self.lookup_intern(db).function_with_body_id(db)
188    }
189    pub fn substitution(&self, db: &dyn LoweringGroup) -> Maybe<GenericSubstitution> {
190        self.lookup_intern(db).substitution(db)
191    }
192    pub fn function_id(&self, db: &dyn LoweringGroup) -> Maybe<FunctionId> {
193        self.lookup_intern(db).function_id(db)
194    }
195    pub fn name(&self, db: &dyn LoweringGroup) -> SmolStr {
196        self.lookup_intern(db).name(db)
197    }
198    pub fn signature(&self, db: &dyn LoweringGroup) -> Maybe<Signature> {
199        let generic_signature = self.function_with_body_id(db).signature(db)?;
200        let substitution = self.substitution(db)?;
201        SubstitutionRewriter { db: db.upcast(), substitution: &substitution }
202            .rewrite(generic_signature)
203    }
204    pub fn from_no_generics_free(
205        db: &dyn LoweringGroup,
206        free_function_id: FreeFunctionId,
207    ) -> Option<Self> {
208        let semantic = semantic::ConcreteFunctionWithBodyId::from_no_generics_free(
209            db.upcast(),
210            free_function_id,
211        )?;
212        Some(ConcreteFunctionWithBodyLongId::Semantic(semantic).intern(db))
213    }
214    pub fn base_semantic_function(
215        &self,
216        db: &dyn LoweringGroup,
217    ) -> semantic::ConcreteFunctionWithBodyId {
218        self.lookup_intern(db).base_semantic_function(db)
219    }
220    pub fn stable_location(&self, db: &dyn LoweringGroup) -> Maybe<StableLocation> {
221        let semantic_db = db.upcast();
222        Ok(match self.lookup_intern(db) {
223            ConcreteFunctionWithBodyLongId::Semantic(id) => id.stable_location(semantic_db),
224            ConcreteFunctionWithBodyLongId::Generated(generated) => {
225                let parent_id = generated.parent.function_with_body_id(semantic_db);
226                match generated.key {
227                    GeneratedFunctionKey::Loop(expr_id) => StableLocation::new(
228                        db.function_body(parent_id)?.arenas.exprs[expr_id].stable_ptr().untyped(),
229                    ),
230                    GeneratedFunctionKey::TraitFunc(_, stable_location) => stable_location,
231                }
232            }
233        })
234    }
235}
236
237/// Function.
238#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
239pub enum FunctionLongId {
240    /// An original function from the user code.
241    Semantic(semantic::FunctionId),
242    /// A function generated by the compiler.
243    Generated(GeneratedFunction),
244}
245define_short_id!(
246    FunctionId,
247    FunctionLongId,
248    LoweringGroup,
249    lookup_intern_lowering_function,
250    intern_lowering_function
251);
252impl FunctionLongId {
253    pub fn body(&self, db: &dyn LoweringGroup) -> Maybe<Option<ConcreteFunctionWithBodyId>> {
254        let semantic_db = db.upcast();
255        Ok(Some(match *self {
256            FunctionLongId::Semantic(id) => {
257                let concrete_function = id.get_concrete(semantic_db);
258                if let GenericFunctionId::Impl(ImplGenericFunctionId { impl_id, function }) =
259                    concrete_function.generic_function
260                {
261                    if let ImplLongId::GeneratedImpl(imp) = db.lookup_intern_impl(impl_id) {
262                        let semantic_db = db.upcast();
263                        let concrete_trait = imp.concrete_trait(semantic_db);
264
265                        assert!(
266                            [
267                                semantic::corelib::destruct_trait_fn(semantic_db),
268                                semantic::corelib::panic_destruct_trait_fn(semantic_db),
269                                semantic::corelib::fn_once_call_trait_fn(semantic_db),
270                                semantic::corelib::fn_call_trait_fn(semantic_db),
271                            ]
272                            .contains(&function)
273                        );
274
275                        let generic_args = concrete_trait.generic_args(semantic_db);
276                        let Some(GenericArgumentId::Type(ty)) = generic_args.first() else {
277                            unreachable!("Expected Generated Impl to have a type argument");
278                        };
279                        let TypeLongId::Closure(ty) = ty.lookup_intern(semantic_db) else {
280                            unreachable!("Expected Generated Impl to have a closure type argument");
281                        };
282
283                        let Some(parent) =
284                            ty.parent_function?.get_concrete(semantic_db).body(semantic_db)?
285                        else {
286                            return Ok(None);
287                        };
288                        return Ok(Some(
289                            GeneratedFunction {
290                                parent,
291                                key: GeneratedFunctionKey::TraitFunc(id, ty.wrapper_location),
292                            }
293                            .body(db),
294                        ));
295                    }
296                }
297
298                let Some(body) = concrete_function.body(semantic_db)? else {
299                    return Ok(None);
300                };
301                ConcreteFunctionWithBodyLongId::Semantic(body).intern(db)
302            }
303            FunctionLongId::Generated(generated) => generated.body(db),
304        }))
305    }
306    pub fn signature(&self, db: &dyn LoweringGroup) -> Maybe<Signature> {
307        match self {
308            FunctionLongId::Semantic(semantic) => {
309                Ok(Signature::from_semantic(db, db.concrete_function_signature(*semantic)?))
310            }
311            FunctionLongId::Generated(generated) => generated.body(db).signature(db),
312        }
313    }
314    pub fn name(&self, db: &dyn LoweringGroup) -> SmolStr {
315        match *self {
316            FunctionLongId::Semantic(semantic) => semantic.name(db.upcast()),
317            FunctionLongId::Generated(generated) => generated.name(db),
318        }
319    }
320    /// Returns the full path of the relevant semantic function:
321    /// - If the function itself is semantic (non generated), its own full path.
322    /// - If the function is generated, then its (semantic) parent's full path.
323    pub fn semantic_full_path(&self, db: &dyn LoweringGroup) -> String {
324        match self {
325            FunctionLongId::Semantic(id) => id.full_name(db.upcast()),
326            FunctionLongId::Generated(generated) => generated.parent.full_path(db.upcast()),
327        }
328    }
329}
330impl FunctionId {
331    pub fn body(&self, db: &dyn LoweringGroup) -> Maybe<Option<ConcreteFunctionWithBodyId>> {
332        self.lookup_intern(db).body(db)
333    }
334    pub fn signature(&self, db: &dyn LoweringGroup) -> Maybe<Signature> {
335        self.lookup_intern(db).signature(db)
336    }
337    pub fn name(&self, db: &dyn LoweringGroup) -> SmolStr {
338        self.lookup_intern(db).name(db)
339    }
340    pub fn semantic_full_path(&self, db: &dyn LoweringGroup) -> String {
341        self.lookup_intern(db).semantic_full_path(db)
342    }
343    /// Returns the function as an `ExternFunctionId` and its generic arguments, if it is an
344    /// `extern` functions.
345    pub fn get_extern(
346        &self,
347        db: &dyn LoweringGroup,
348    ) -> Option<(ExternFunctionId, Vec<GenericArgumentId>)> {
349        let semantic = try_extract_matches!(self.lookup_intern(db), FunctionLongId::Semantic)?;
350        let concrete = semantic.get_concrete(db.upcast());
351        Some((
352            try_extract_matches!(concrete.generic_function, GenericFunctionId::Extern)?,
353            concrete.generic_args,
354        ))
355    }
356}
357pub trait SemanticFunctionIdEx {
358    fn lowered(&self, db: &dyn LoweringGroup) -> FunctionId;
359}
360impl SemanticFunctionIdEx for semantic::FunctionId {
361    fn lowered(&self, db: &dyn LoweringGroup) -> FunctionId {
362        FunctionLongId::Semantic(*self).intern(db)
363    }
364}
365impl<'a> DebugWithDb<dyn LoweringGroup + 'a> for FunctionLongId {
366    fn fmt(
367        &self,
368        f: &mut std::fmt::Formatter<'_>,
369        db: &(dyn LoweringGroup + 'a),
370    ) -> std::fmt::Result {
371        match self {
372            FunctionLongId::Semantic(semantic) => semantic.fmt(f, db),
373            FunctionLongId::Generated(generated) => {
374                write!(f, "{}", generated.name(db))
375            }
376        }
377    }
378}
379
380/// A key for a generated functions.
381#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
382pub enum GeneratedFunctionKey {
383    /// Generated loop functions are identified by the loop expr_id.
384    Loop(semantic::ExprId),
385    TraitFunc(semantic::FunctionId, StableLocation),
386}
387
388/// Generated function.
389#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
390pub struct GeneratedFunction {
391    pub parent: semantic::ConcreteFunctionWithBodyId,
392    pub key: GeneratedFunctionKey,
393}
394impl GeneratedFunction {
395    pub fn body(&self, db: &dyn LoweringGroup) -> ConcreteFunctionWithBodyId {
396        let GeneratedFunction { parent, key } = *self;
397        let long_id = ConcreteFunctionWithBodyLongId::Generated(GeneratedFunction { parent, key });
398        long_id.intern(db)
399    }
400    pub fn name(&self, db: &dyn LoweringGroup) -> SmolStr {
401        match self.key {
402            GeneratedFunctionKey::Loop(expr_id) => {
403                format!("{}[expr{}]", self.parent.full_path(db.upcast()), expr_id.index()).into()
404            }
405            GeneratedFunctionKey::TraitFunc(trait_func, _) => {
406                format!("{:?}", trait_func.debug(db)).into()
407            }
408        }
409    }
410}
411
412/// Lowered signature of a function.
413#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, SemanticObject, Hash)]
414#[debug_db(dyn LoweringGroup + 'a)]
415pub struct Signature {
416    /// Input params.
417    pub params: Vec<semantic::ExprVarMemberPath>,
418    /// Extra returns - e.g. ref params
419    pub extra_rets: Vec<semantic::ExprVarMemberPath>,
420    /// Return type.
421    pub return_type: semantic::TypeId,
422    /// Explicit implicit requirements.
423    pub implicits: Vec<semantic::TypeId>,
424    /// Panicable.
425    #[dont_rewrite]
426    pub panicable: bool,
427    /// Location.
428    #[dont_rewrite]
429    #[hide_field_debug_with_db]
430    pub location: LocationId,
431}
432impl Signature {
433    pub fn from_semantic(db: &dyn LoweringGroup, value: semantic::Signature) -> Self {
434        let semantic::Signature { params, return_type, implicits, panicable, stable_ptr } = value;
435        let ref_params = params
436            .iter()
437            .filter(|param| param.mutability == Mutability::Reference)
438            .map(|param| parameter_as_member_path(param.clone()))
439            .collect();
440        let params: Vec<semantic::ExprVarMemberPath> =
441            params.into_iter().map(parameter_as_member_path).collect();
442        Self {
443            params,
444            extra_rets: ref_params,
445            return_type,
446            implicits,
447            panicable,
448            location: LocationId::from_stable_location(
449                db,
450                StableLocation::new(stable_ptr.untyped()),
451            ),
452        }
453    }
454    pub fn is_fully_concrete(&self, db: &dyn LoweringGroup) -> bool {
455        let semantic_db = db.upcast();
456        self.params.iter().all(|param| param.ty().is_fully_concrete(semantic_db))
457            && self.extra_rets.iter().all(|param| param.ty().is_fully_concrete(semantic_db))
458            && self.return_type.is_fully_concrete(semantic_db)
459            && self.implicits.iter().all(|ty| ty.is_fully_concrete(semantic_db))
460    }
461}
462semantic::add_rewrite!(<'a>, SubstitutionRewriter<'a>, DiagnosticAdded, Signature);
463
464/// Converts a [semantic::Parameter] to a [semantic::ExprVarMemberPath].
465pub(crate) fn parameter_as_member_path(param: semantic::Parameter) -> semantic::ExprVarMemberPath {
466    let semantic::Parameter { id, ty, stable_ptr, .. } = param;
467    semantic::ExprVarMemberPath::Var(ExprVar {
468        var: semantic::VarId::Param(id),
469        ty,
470        stable_ptr: ast::ExprPtr(stable_ptr.0),
471    })
472}
473
474define_short_id!(LocationId, Location, LoweringGroup, lookup_intern_location, intern_location);
475impl LocationId {
476    pub fn from_stable_location(
477        db: &dyn LoweringGroup,
478        stable_location: StableLocation,
479    ) -> LocationId {
480        Location::new(stable_location).intern(db)
481    }
482
483    /// Adds a note to the location.
484    pub fn with_note(&self, db: &dyn LoweringGroup, note: DiagnosticNote) -> LocationId {
485        self.lookup_intern(db).with_note(note).intern(db)
486    }
487
488    /// Adds a note that this location was generated while compiling an auto-generated function.
489    pub fn with_auto_generation_note(
490        &self,
491        db: &dyn LoweringGroup,
492        logic_name: &str,
493    ) -> LocationId {
494        self.with_note(
495            db,
496            DiagnosticNote::text_only(format!(
497                "this error originates in auto-generated {logic_name} logic."
498            )),
499        )
500    }
501}