cairo_lang_semantic/items/
constant.rs

1use std::sync::Arc;
2
3use cairo_lang_debug::DebugWithDb;
4use cairo_lang_defs::ids::{
5    ConstantId, GenericParamId, LanguageElementId, LookupItemId, ModuleItemId,
6    NamedLanguageElementId, TraitConstantId,
7};
8use cairo_lang_diagnostics::{DiagnosticAdded, Diagnostics, Maybe, ToMaybe, skip_diagnostic};
9use cairo_lang_proc_macros::{DebugWithDb, SemanticObject};
10use cairo_lang_syntax::node::ast::ItemConstant;
11use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
12use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode};
13use cairo_lang_utils::{
14    Intern, LookupIntern, define_short_id, extract_matches, try_extract_matches,
15};
16use id_arena::Arena;
17use itertools::Itertools;
18use num_bigint::BigInt;
19use num_traits::{Num, ToPrimitive, Zero};
20use smol_str::SmolStr;
21
22use super::functions::{GenericFunctionId, GenericFunctionWithBodyId};
23use super::imp::ImplId;
24use crate::corelib::{
25    CoreTraitContext, LiteralError, core_box_ty, core_nonzero_ty, get_core_trait,
26    get_core_ty_by_name, try_extract_nz_wrapped_type, validate_literal,
27};
28use crate::db::SemanticGroup;
29use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
30use crate::expr::compute::{
31    ComputationContext, ContextFunction, Environment, ExprAndId, compute_expr_semantic,
32};
33use crate::expr::inference::conform::InferenceConform;
34use crate::expr::inference::{ConstVar, InferenceId};
35use crate::literals::try_extract_minus_literal;
36use crate::resolve::{Resolver, ResolverData};
37use crate::substitution::SemanticRewriter;
38use crate::types::resolve_type;
39use crate::{
40    ConcreteTypeId, ConcreteVariant, Expr, ExprBlock, ExprConstant, ExprFunctionCall,
41    ExprFunctionCallArg, ExprId, ExprMemberAccess, ExprStructCtor, FunctionId, GenericParam,
42    SemanticDiagnostic, TypeId, TypeLongId, semantic_object_for_id,
43};
44
45#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
46#[debug_db(dyn SemanticGroup + 'static)]
47pub struct Constant {
48    /// The actual id of the const expression value.
49    pub value: ExprId,
50    /// The arena of all the expressions for the const calculation.
51    pub exprs: Arc<Arena<Expr>>,
52}
53
54impl Constant {
55    pub fn ty(&self) -> TypeId {
56        self.exprs[self.value].ty()
57    }
58}
59
60/// Information about a constant definition.
61///
62/// Helper struct for the data returned by [SemanticGroup::priv_constant_semantic_data].
63#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
64#[debug_db(dyn SemanticGroup + 'static)]
65pub struct ConstantData {
66    pub diagnostics: Diagnostics<SemanticDiagnostic>,
67    pub constant: Maybe<Constant>,
68    pub const_value: ConstValueId,
69    pub resolver_data: Arc<ResolverData>,
70}
71
72define_short_id!(
73    ConstValueId,
74    ConstValue,
75    SemanticGroup,
76    lookup_intern_const_value,
77    intern_const_value
78);
79semantic_object_for_id!(ConstValueId, lookup_intern_const_value, intern_const_value, ConstValue);
80impl ConstValueId {
81    pub fn format(&self, db: &dyn SemanticGroup) -> String {
82        format!("{:?}", self.lookup_intern(db).debug(db.elongate()))
83    }
84
85    /// Returns true if the const does not depend on any generics.
86    pub fn is_fully_concrete(&self, db: &dyn SemanticGroup) -> bool {
87        self.lookup_intern(db).is_fully_concrete(db)
88    }
89
90    /// Returns true if the const does not contain any inference variables.
91    pub fn is_var_free(&self, db: &dyn SemanticGroup) -> bool {
92        self.lookup_intern(db).is_var_free(db)
93    }
94
95    /// Returns the type of the const.
96    pub fn ty(&self, db: &dyn SemanticGroup) -> Maybe<TypeId> {
97        self.lookup_intern(db).ty(db)
98    }
99}
100
101/// A constant value.
102#[derive(Clone, Debug, Hash, PartialEq, Eq, SemanticObject)]
103pub enum ConstValue {
104    Int(#[dont_rewrite] BigInt, TypeId),
105    Struct(Vec<ConstValue>, TypeId),
106    Enum(ConcreteVariant, Box<ConstValue>),
107    NonZero(Box<ConstValue>),
108    Boxed(Box<ConstValue>),
109    Generic(#[dont_rewrite] GenericParamId),
110    ImplConstant(ImplConstantId),
111    TraitConstant(TraitConstantId),
112    Var(ConstVar, TypeId),
113    /// A missing value, used in cases where the value is not known due to diagnostics.
114    Missing(#[dont_rewrite] DiagnosticAdded),
115}
116impl ConstValue {
117    /// Returns true if the const does not depend on any generics.
118    pub fn is_fully_concrete(&self, db: &dyn SemanticGroup) -> bool {
119        self.ty(db).unwrap().is_fully_concrete(db)
120            && match self {
121                ConstValue::Int(_, _) => true,
122                ConstValue::Struct(members, _) => {
123                    members.iter().all(|member: &ConstValue| member.is_fully_concrete(db))
124                }
125                ConstValue::Enum(_, value)
126                | ConstValue::NonZero(value)
127                | ConstValue::Boxed(value) => value.is_fully_concrete(db),
128                ConstValue::Generic(_)
129                | ConstValue::Var(_, _)
130                | ConstValue::Missing(_)
131                | ConstValue::ImplConstant(_)
132                | ConstValue::TraitConstant(_) => false,
133            }
134    }
135
136    /// Returns true if the const does not contain any inference variables.
137    pub fn is_var_free(&self, db: &dyn SemanticGroup) -> bool {
138        self.ty(db).unwrap().is_var_free(db)
139            && match self {
140                ConstValue::Int(_, _)
141                | ConstValue::Generic(_)
142                | ConstValue::Missing(_)
143                | ConstValue::TraitConstant(_) => true,
144                ConstValue::Struct(members, _) => {
145                    members.iter().all(|member| member.is_var_free(db))
146                }
147                ConstValue::Enum(_, value)
148                | ConstValue::NonZero(value)
149                | ConstValue::Boxed(value) => value.is_var_free(db),
150                ConstValue::Var(_, _) => false,
151                ConstValue::ImplConstant(impl_constant) => impl_constant.impl_id().is_var_free(db),
152            }
153    }
154
155    /// Returns the type of the const.
156    pub fn ty(&self, db: &dyn SemanticGroup) -> Maybe<TypeId> {
157        Ok(match self {
158            ConstValue::Int(_, ty) => *ty,
159            ConstValue::Struct(_, ty) => *ty,
160            ConstValue::Enum(variant, _) => {
161                TypeLongId::Concrete(ConcreteTypeId::Enum(variant.concrete_enum_id)).intern(db)
162            }
163            ConstValue::NonZero(value) => core_nonzero_ty(db, value.ty(db)?),
164            ConstValue::Boxed(value) => core_box_ty(db, value.ty(db)?),
165            ConstValue::Generic(param) => {
166                extract_matches!(db.generic_param_semantic(*param)?, GenericParam::Const).ty
167            }
168            ConstValue::Var(_, ty) => *ty,
169            ConstValue::Missing(_) => TypeId::missing(db, skip_diagnostic()),
170            ConstValue::ImplConstant(impl_constant_id) => {
171                db.impl_constant_concrete_implized_type(*impl_constant_id)?
172            }
173            ConstValue::TraitConstant(trait_constant) => db.trait_constant_type(*trait_constant)?,
174        })
175    }
176
177    /// Returns the value of an int const as a BigInt.
178    pub fn into_int(self) -> Option<BigInt> {
179        match self {
180            ConstValue::Int(value, _) => Some(value.clone()),
181            _ => None,
182        }
183    }
184}
185
186/// An impl item of kind const.
187#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, SemanticObject)]
188pub struct ImplConstantId {
189    /// The impl the item const is in.
190    impl_id: ImplId,
191    /// The trait const this impl const "implements".
192    trait_constant_id: TraitConstantId,
193}
194
195impl ImplConstantId {
196    /// Creates a new impl constant id. For an impl constant of a concrete impl, asserts that the
197    /// trait constant belongs to the same trait that the impl implements (panics if not).
198    pub fn new(
199        impl_id: ImplId,
200        trait_constant_id: TraitConstantId,
201        db: &dyn SemanticGroup,
202    ) -> Self {
203        if let crate::items::imp::ImplLongId::Concrete(concrete_impl) = impl_id.lookup_intern(db) {
204            let impl_def_id = concrete_impl.impl_def_id(db);
205            assert_eq!(Ok(trait_constant_id.trait_id(db.upcast())), db.impl_def_trait(impl_def_id));
206        }
207
208        ImplConstantId { impl_id, trait_constant_id }
209    }
210    pub fn impl_id(&self) -> ImplId {
211        self.impl_id
212    }
213    pub fn trait_constant_id(&self) -> TraitConstantId {
214        self.trait_constant_id
215    }
216
217    pub fn format(&self, db: &dyn SemanticGroup) -> SmolStr {
218        format!("{}::{}", self.impl_id.name(db.upcast()), self.trait_constant_id.name(db.upcast()))
219            .into()
220    }
221}
222impl DebugWithDb<dyn SemanticGroup> for ImplConstantId {
223    fn fmt(
224        &self,
225        f: &mut std::fmt::Formatter<'_>,
226        db: &(dyn SemanticGroup + 'static),
227    ) -> std::fmt::Result {
228        write!(f, "{}", self.format(db))
229    }
230}
231
232/// Query implementation of [SemanticGroup::priv_constant_semantic_data].
233pub fn priv_constant_semantic_data(
234    db: &dyn SemanticGroup,
235    const_id: ConstantId,
236    in_cycle: bool,
237) -> Maybe<ConstantData> {
238    let lookup_item_id = LookupItemId::ModuleItem(ModuleItemId::Constant(const_id));
239    if in_cycle {
240        constant_semantic_data_cycle_helper(
241            db,
242            &db.module_constant_by_id(const_id)?.to_maybe()?,
243            lookup_item_id,
244            None,
245            &const_id,
246        )
247    } else {
248        constant_semantic_data_helper(
249            db,
250            &db.module_constant_by_id(const_id)?.to_maybe()?,
251            lookup_item_id,
252            None,
253            &const_id,
254        )
255    }
256}
257
258/// Cycle handling for [SemanticGroup::priv_constant_semantic_data].
259pub fn priv_constant_semantic_data_cycle(
260    db: &dyn SemanticGroup,
261    _cycle: &salsa::Cycle,
262    const_id: &ConstantId,
263    _in_cycle: &bool,
264) -> Maybe<ConstantData> {
265    priv_constant_semantic_data(db, *const_id, true)
266}
267
268/// Returns constant semantic data for the given ItemConstant.
269pub fn constant_semantic_data_helper(
270    db: &dyn SemanticGroup,
271    constant_ast: &ItemConstant,
272    lookup_item_id: LookupItemId,
273    parent_resolver_data: Option<Arc<ResolverData>>,
274    element_id: &impl LanguageElementId,
275) -> Maybe<ConstantData> {
276    let mut diagnostics: SemanticDiagnostics = SemanticDiagnostics::default();
277    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
278    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
279    // the item instead of all the module data.
280    let syntax_db = db.upcast();
281
282    let inference_id = InferenceId::LookupItemDeclaration(lookup_item_id);
283
284    let mut resolver = match parent_resolver_data {
285        Some(parent_resolver_data) => {
286            Resolver::with_data(db, parent_resolver_data.clone_with_inference_id(db, inference_id))
287        }
288        None => Resolver::new(db, element_id.module_file_id(db.upcast()), inference_id),
289    };
290    resolver.set_feature_config(element_id, constant_ast, &mut diagnostics);
291
292    let constant_type = resolve_type(
293        db,
294        &mut diagnostics,
295        &mut resolver,
296        &constant_ast.type_clause(syntax_db).ty(syntax_db),
297    );
298
299    let environment = Environment::empty();
300    let mut ctx = ComputationContext::new(
301        db,
302        &mut diagnostics,
303        resolver,
304        None,
305        environment,
306        ContextFunction::Global,
307    );
308
309    let value = compute_expr_semantic(&mut ctx, &constant_ast.value(syntax_db));
310    let const_value = resolve_const_expr_and_evaluate(
311        db,
312        &mut ctx,
313        &value,
314        constant_ast.stable_ptr().untyped(),
315        constant_type,
316    )
317    .intern(db);
318
319    // Check fully resolved.
320    ctx.resolver.inference().finalize(ctx.diagnostics, constant_ast.stable_ptr().untyped());
321    ctx.apply_inference_rewriter_to_exprs();
322
323    let const_value = ctx
324        .resolver
325        .inference()
326        .rewrite(const_value)
327        .unwrap_or_else(|_| ConstValue::Missing(skip_diagnostic()).intern(db));
328    let resolver_data = Arc::new(ctx.resolver.data);
329    let constant = Constant { value: value.id, exprs: Arc::new(ctx.arenas.exprs) };
330    Ok(ConstantData {
331        diagnostics: diagnostics.build(),
332        const_value,
333        constant: Ok(constant),
334        resolver_data,
335    })
336}
337
338/// Helper for cycle handling of constants.
339pub fn constant_semantic_data_cycle_helper(
340    db: &dyn SemanticGroup,
341    constant_ast: &ItemConstant,
342    lookup_item_id: LookupItemId,
343    parent_resolver_data: Option<Arc<ResolverData>>,
344    element_id: &impl LanguageElementId,
345) -> Maybe<ConstantData> {
346    let mut diagnostics: SemanticDiagnostics = SemanticDiagnostics::default();
347
348    let inference_id = InferenceId::LookupItemDeclaration(lookup_item_id);
349
350    let resolver = match parent_resolver_data {
351        Some(parent_resolver_data) => {
352            Resolver::with_data(db, parent_resolver_data.clone_with_inference_id(db, inference_id))
353        }
354        None => Resolver::new(db, element_id.module_file_id(db.upcast()), inference_id),
355    };
356
357    let resolver_data = Arc::new(resolver.data);
358
359    let diagnostic_added = diagnostics.report(constant_ast, SemanticDiagnosticKind::ConstCycle);
360    Ok(ConstantData {
361        constant: Err(diagnostic_added),
362        const_value: ConstValue::Missing(diagnostic_added).intern(db),
363        diagnostics: diagnostics.build(),
364        resolver_data,
365    })
366}
367
368/// Resolves the given const expression and evaluates its value.
369pub fn resolve_const_expr_and_evaluate(
370    db: &dyn SemanticGroup,
371    ctx: &mut ComputationContext<'_>,
372    value: &ExprAndId,
373    const_stable_ptr: SyntaxStablePtrId,
374    target_type: TypeId,
375) -> ConstValue {
376    let inference = &mut ctx.resolver.inference();
377    if let Err(err_set) = inference.conform_ty(value.ty(), target_type) {
378        inference.report_on_pending_error(err_set, ctx.diagnostics, const_stable_ptr);
379    }
380
381    if let Err(err_set) = inference.solve() {
382        inference.report_on_pending_error(err_set, ctx.diagnostics, const_stable_ptr);
383    }
384
385    ctx.apply_inference_rewriter_to_exprs();
386
387    match &value.expr {
388        Expr::Constant(ExprConstant { const_value_id, .. }) => const_value_id.lookup_intern(db),
389        // Check that the expression is a valid constant.
390        _ => evaluate_constant_expr(db, &ctx.arenas.exprs, value.id, ctx.diagnostics),
391    }
392}
393
394/// creates a [ConstValue] from a [BigInt] value.
395pub fn value_as_const_value(
396    db: &dyn SemanticGroup,
397    ty: TypeId,
398    value: &BigInt,
399) -> Result<ConstValue, LiteralError> {
400    validate_literal(db.upcast(), ty, value.clone())?;
401    let get_basic_const_value = |ty| {
402        let u256_ty = get_core_ty_by_name(db.upcast(), "u256".into(), vec![]);
403
404        if ty != u256_ty {
405            ConstValue::Int(value.clone(), ty)
406        } else {
407            let u128_ty = get_core_ty_by_name(db.upcast(), "u128".into(), vec![]);
408            let mask128 = BigInt::from(u128::MAX);
409            let low = value & mask128;
410            let high = value >> 128;
411            ConstValue::Struct(
412                vec![(ConstValue::Int(low, u128_ty)), (ConstValue::Int(high, u128_ty))],
413                ty,
414            )
415        }
416    };
417
418    if let Some(inner) = try_extract_nz_wrapped_type(db.upcast(), ty) {
419        Ok(ConstValue::NonZero(Box::new(get_basic_const_value(inner))))
420    } else {
421        Ok(get_basic_const_value(ty))
422    }
423}
424
425/// evaluate the given const expression value.
426pub fn evaluate_constant_expr(
427    db: &dyn SemanticGroup,
428    exprs: &Arena<Expr>,
429    expr_id: ExprId,
430    diagnostics: &mut SemanticDiagnostics,
431) -> ConstValue {
432    let expr = &exprs[expr_id];
433
434    match expr {
435        Expr::Constant(expr) => expr.const_value_id.lookup_intern(db),
436        Expr::Block(ExprBlock { statements, tail: Some(inner), .. }) if statements.is_empty() => {
437            evaluate_constant_expr(db, exprs, *inner, diagnostics)
438        }
439        Expr::FunctionCall(expr) => evaluate_const_function_call(db, exprs, expr, diagnostics)
440            .map(|value| {
441                value_as_const_value(db, expr.ty, &value)
442                    .map_err(|err| {
443                        diagnostics.report(
444                            expr.stable_ptr.untyped(),
445                            SemanticDiagnosticKind::LiteralError(err),
446                        )
447                    })
448                    .unwrap_or_else(ConstValue::Missing)
449            })
450            .unwrap_or_else(ConstValue::Missing),
451        Expr::Literal(expr) => value_as_const_value(db, expr.ty, &expr.value)
452            .map_err(|err| {
453                diagnostics
454                    .report(expr.stable_ptr.untyped(), SemanticDiagnosticKind::LiteralError(err))
455            })
456            .unwrap_or_else(ConstValue::Missing),
457        Expr::Tuple(expr) => ConstValue::Struct(
458            expr.items
459                .iter()
460                .map(|expr_id| evaluate_constant_expr(db, exprs, *expr_id, diagnostics))
461                .collect(),
462            expr.ty,
463        ),
464        Expr::StructCtor(ExprStructCtor {
465            members,
466            base_struct: None,
467            ty,
468            concrete_struct_id,
469            ..
470        }) => {
471            let member_order = match db.concrete_struct_members(*concrete_struct_id) {
472                Ok(member_order) => member_order,
473                Err(diag_add) => return ConstValue::Missing(diag_add),
474            };
475            ConstValue::Struct(
476                member_order
477                    .values()
478                    .map(|m| {
479                        members
480                            .iter()
481                            .find(|(member_id, _)| m.id == *member_id)
482                            .map(|(_, expr_id)| {
483                                evaluate_constant_expr(db, exprs, *expr_id, diagnostics)
484                            })
485                            .unwrap_or_else(|| ConstValue::Missing(skip_diagnostic()))
486                    })
487                    .collect(),
488                *ty,
489            )
490        }
491        Expr::EnumVariantCtor(expr) => ConstValue::Enum(
492            expr.variant.clone(),
493            Box::new(evaluate_constant_expr(db, exprs, expr.value_expr, diagnostics)),
494        ),
495        Expr::MemberAccess(expr) => extract_const_member_access(db, exprs, expr, diagnostics)
496            .unwrap_or_else(ConstValue::Missing),
497        Expr::FixedSizeArray(expr) => ConstValue::Struct(
498            match &expr.items {
499                crate::FixedSizeArrayItems::Items(items) => items
500                    .iter()
501                    .map(|expr_id| evaluate_constant_expr(db, exprs, *expr_id, diagnostics))
502                    .collect(),
503                crate::FixedSizeArrayItems::ValueAndSize(value, count) => {
504                    let value = evaluate_constant_expr(db, exprs, *value, diagnostics);
505                    let count = count.lookup_intern(db);
506                    if let Some(count) = count.into_int() {
507                        (0..count.to_usize().unwrap()).map(|_| value.clone()).collect()
508                    } else {
509                        diagnostics.report(
510                            expr.stable_ptr.untyped(),
511                            SemanticDiagnosticKind::UnsupportedConstant,
512                        );
513                        vec![]
514                    }
515                }
516            },
517            expr.ty,
518        ),
519        _ if diagnostics.error_count == 0 => ConstValue::Missing(
520            diagnostics
521                .report(expr.stable_ptr().untyped(), SemanticDiagnosticKind::UnsupportedConstant),
522        ),
523        _ => ConstValue::Missing(skip_diagnostic()),
524    }
525}
526
527/// Returns true if the given function is allowed to be called in constant context.
528fn is_function_const(db: &dyn SemanticGroup, function_id: FunctionId) -> bool {
529    let concrete_function = function_id.get_concrete(db);
530    let Ok(Some(body)) = concrete_function.body(db) else { return false };
531    let GenericFunctionWithBodyId::Impl(imp) = body.generic_function(db) else { return false };
532    let impl_def = imp.concrete_impl_id.impl_def_id(db);
533    if impl_def.parent_module(db.upcast()).owning_crate(db.upcast()) != db.core_crate() {
534        return false;
535    }
536    let Ok(trait_id) = db.impl_def_trait(impl_def) else {
537        return false;
538    };
539    let expected_trait_name = match imp.function_body.name(db.upcast()).as_str() {
540        "neg" => "Neg",
541        "add" => "Add",
542        "sub" => "Sub",
543        "mul" => "Mul",
544        "div" => "Div",
545        "rem" => "Rem",
546        "bitand" => "BitAnd",
547        "bitor" => "BitOr",
548        "bitxor" => "BitXor",
549        _ => return false,
550    };
551    trait_id == get_core_trait(db, CoreTraitContext::TopLevel, expected_trait_name.into())
552}
553
554/// Attempts to evaluate constants from a function call.
555fn evaluate_const_function_call(
556    db: &dyn SemanticGroup,
557    exprs: &Arena<Expr>,
558    expr: &ExprFunctionCall,
559    diagnostics: &mut SemanticDiagnostics,
560) -> Maybe<BigInt> {
561    if let Some(value) = try_extract_minus_literal(db.upcast(), exprs, expr) {
562        return Ok(value);
563    }
564    let args = expr
565        .args
566        .iter()
567        .filter_map(|arg| try_extract_matches!(arg, ExprFunctionCallArg::Value))
568        .map(|arg| {
569            match evaluate_constant_expr(db, exprs, *arg, diagnostics) {
570                ConstValue::Int(v, _ty) => Ok(v),
571                // Handling u256 constants to enable const evaluation of them.
572                ConstValue::Struct(v, _) => {
573                    if let [ConstValue::Int(low, _), ConstValue::Int(high, _)] = &v[..] {
574                        Ok(low + (high << 128))
575                    } else {
576                        Err(diagnostics.report(
577                            exprs[*arg].stable_ptr().untyped(),
578                            SemanticDiagnosticKind::UnsupportedConstant,
579                        ))
580                    }
581                }
582                ConstValue::Missing(err) => Err(err),
583                _ => Err(diagnostics.report(
584                    exprs[*arg].stable_ptr().untyped(),
585                    SemanticDiagnosticKind::UnsupportedConstant,
586                )),
587            }
588        })
589        .collect_vec()
590        .into_iter()
591        .collect::<Result<Vec<_>, _>>()?;
592
593    if !is_function_const(db, expr.function) {
594        return Err(diagnostics
595            .report(expr.stable_ptr.untyped(), SemanticDiagnosticKind::UnsupportedConstant));
596    }
597
598    let imp = extract_matches!(
599        expr.function.get_concrete(db.upcast()).generic_function,
600        GenericFunctionId::Impl
601    );
602    let is_felt252_ty = expr.ty == db.core_felt252_ty();
603    let mut value = match imp.function.name(db.upcast()).as_str() {
604        "neg" => -&args[0],
605        "add" => &args[0] + &args[1],
606        "sub" => &args[0] - &args[1],
607        "mul" => &args[0] * &args[1],
608        "div" | "rem" if args[1].is_zero() => {
609            return Err(diagnostics
610                .report(expr.stable_ptr.untyped(), SemanticDiagnosticKind::DivisionByZero));
611        }
612        "div" if !is_felt252_ty => &args[0] / &args[1],
613        "rem" if !is_felt252_ty => &args[0] % &args[1],
614        "bitand" if !is_felt252_ty => &args[0] & &args[1],
615        "bitor" if !is_felt252_ty => &args[0] | &args[1],
616        "bitxor" if !is_felt252_ty => &args[0] ^ &args[1],
617        _ => unreachable!("Unexpected function call in constant lowering: {:?}", expr),
618    };
619    if is_felt252_ty {
620        // Specifically handling felt252s since their evaluation is more complex.
621        value %= BigInt::from_str_radix(
622            "800000000000011000000000000000000000000000000000000000000000001",
623            16,
624        )
625        .unwrap();
626    }
627    Ok(value)
628}
629
630/// Extract const member access from a const value.
631fn extract_const_member_access(
632    db: &dyn SemanticGroup,
633    exprs: &Arena<Expr>,
634    expr: &ExprMemberAccess,
635    diagnostics: &mut SemanticDiagnostics,
636) -> Maybe<ConstValue> {
637    let full_struct = evaluate_constant_expr(db, exprs, expr.expr, diagnostics);
638    let ConstValue::Struct(mut values, _) = full_struct else {
639        return Err(diagnostics.report(
640            exprs[expr.expr].stable_ptr().untyped(),
641            SemanticDiagnosticKind::UnsupportedConstant,
642        ));
643    };
644    let members = db.concrete_struct_members(expr.concrete_struct_id)?;
645    let Some(member_idx) = members.iter().position(|(_, member)| member.id == expr.member) else {
646        return Err(diagnostics.report(
647            exprs[expr.expr].stable_ptr().untyped(),
648            SemanticDiagnosticKind::UnsupportedConstant,
649        ));
650    };
651    Ok(values.swap_remove(member_idx))
652}
653
654/// Query implementation of [SemanticGroup::constant_semantic_diagnostics].
655pub fn constant_semantic_diagnostics(
656    db: &dyn SemanticGroup,
657    const_id: ConstantId,
658) -> Diagnostics<SemanticDiagnostic> {
659    db.priv_constant_semantic_data(const_id, false).map(|data| data.diagnostics).unwrap_or_default()
660}
661
662/// Query implementation of [SemanticGroup::constant_semantic_data].
663pub fn constant_semantic_data(db: &dyn SemanticGroup, const_id: ConstantId) -> Maybe<Constant> {
664    db.priv_constant_semantic_data(const_id, false)?.constant
665}
666
667/// Cycle handling for [SemanticGroup::constant_semantic_data].
668pub fn constant_semantic_data_cycle(
669    db: &dyn SemanticGroup,
670    _cycle: &salsa::Cycle,
671    const_id: &ConstantId,
672) -> Maybe<Constant> {
673    // Forwarding cycle handling to `priv_constant_semantic_data` handler.
674    db.priv_constant_semantic_data(*const_id, true)?.constant
675}
676
677/// Query implementation of [crate::db::SemanticGroup::constant_resolver_data].
678pub fn constant_resolver_data(
679    db: &dyn SemanticGroup,
680    const_id: ConstantId,
681) -> Maybe<Arc<ResolverData>> {
682    Ok(db.priv_constant_semantic_data(const_id, false)?.resolver_data)
683}
684
685/// Cycle handling for [crate::db::SemanticGroup::constant_resolver_data].
686pub fn constant_resolver_data_cycle(
687    db: &dyn SemanticGroup,
688    _cycle: &salsa::Cycle,
689    const_id: &ConstantId,
690) -> Maybe<Arc<ResolverData>> {
691    Ok(db.priv_constant_semantic_data(*const_id, true)?.resolver_data)
692}
693
694/// Query implementation of [crate::db::SemanticGroup::constant_const_value].
695pub fn constant_const_value(db: &dyn SemanticGroup, const_id: ConstantId) -> Maybe<ConstValueId> {
696    Ok(db.priv_constant_semantic_data(const_id, false)?.const_value)
697}
698
699/// Cycle handling for [crate::db::SemanticGroup::constant_const_value].
700pub fn constant_const_value_cycle(
701    db: &dyn SemanticGroup,
702    _cycle: &salsa::Cycle,
703    const_id: &ConstantId,
704) -> Maybe<ConstValueId> {
705    // Forwarding cycle handling to `priv_constant_semantic_data` handler.
706    Ok(db.priv_constant_semantic_data(*const_id, true)?.const_value)
707}
708
709/// Query implementation of [crate::db::SemanticGroup::constant_const_type].
710pub fn constant_const_type(db: &dyn SemanticGroup, const_id: ConstantId) -> Maybe<TypeId> {
711    db.priv_constant_semantic_data(const_id, false)?.const_value.ty(db)
712}
713
714/// Cycle handling for [crate::db::SemanticGroup::constant_const_type].
715pub fn constant_const_type_cycle(
716    db: &dyn SemanticGroup,
717    _cycle: &salsa::Cycle,
718    const_id: &ConstantId,
719) -> Maybe<TypeId> {
720    // Forwarding cycle handling to `priv_constant_semantic_data` handler.
721    db.priv_constant_semantic_data(*const_id, true)?.const_value.ty(db)
722}