Skip to main content

cairo_lang_semantic/items/
constant.rs

1use std::iter::zip;
2use std::sync::Arc;
3
4use cairo_lang_debug::DebugWithDb;
5use cairo_lang_defs::db::DefsGroup;
6use cairo_lang_defs::ids::{
7    ConstantId, ExternFunctionId, GenericParamId, LanguageElementId, LookupItemId, ModuleItemId,
8    NamedLanguageElementId, TraitConstantId, TraitId, VarId,
9};
10use cairo_lang_diagnostics::{
11    DiagnosticAdded, DiagnosticEntry, DiagnosticNote, Diagnostics, Maybe, MaybeAsRef,
12    skip_diagnostic,
13};
14use cairo_lang_proc_macros::{DebugWithDb, HeapSize, SemanticObject};
15use cairo_lang_syntax::node::ast::ItemConstant;
16use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
17use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode};
18use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
19use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
20use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
21use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
22use cairo_lang_utils::{Intern, define_short_id, extract_matches, require, try_extract_matches};
23use itertools::Itertools;
24use num_bigint::BigInt;
25use num_traits::{ToPrimitive, Zero};
26use salsa::Database;
27use starknet_types_core::felt::{CAIRO_PRIME_BIGINT, Felt as Felt252};
28
29use super::functions::{GenericFunctionId, GenericFunctionWithBodyId};
30use super::imp::{ImplId, ImplLongId};
31use crate::corelib::{
32    CoreInfo, CorelibSemantic, core_nonzero_ty, false_variant, true_variant,
33    try_extract_bounded_int_type, try_extract_nz_wrapped_type, unit_ty, validate_literal,
34};
35use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
36use crate::expr::compute::{ComputationContext, ExprAndId, compute_expr_semantic};
37use crate::expr::inference::conform::InferenceConform;
38use crate::expr::inference::{ConstVar, InferenceId};
39use crate::helper::ModuleHelper;
40use crate::items::enm::SemanticEnumEx;
41use crate::items::extern_function::ExternFunctionSemantic;
42use crate::items::free_function::FreeFunctionSemantic;
43use crate::items::function_with_body::FunctionWithBodySemantic;
44use crate::items::generics::GenericParamSemantic;
45use crate::items::imp::ImplSemantic;
46use crate::items::structure::StructSemantic;
47use crate::items::trt::TraitSemantic;
48use crate::resolve::{Resolver, ResolverData};
49use crate::substitution::{GenericSubstitution, SemanticRewriter};
50use crate::types::resolve_type;
51use crate::{
52    Arenas, ConcreteFunction, ConcreteTypeId, ConcreteVariant, Condition, Expr, ExprBlock,
53    ExprConstant, ExprFunctionCall, ExprFunctionCallArg, ExprId, ExprMemberAccess, ExprStructCtor,
54    FunctionId, GenericParam, LogicalOperator, Pattern, PatternId, SemanticDiagnostic, Statement,
55    TypeId, TypeLongId, semantic_object_for_id,
56};
57
58#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
59#[debug_db(dyn Database)]
60pub struct Constant<'db> {
61    /// The actual id of the const expression value.
62    pub value: ExprId,
63    /// The arena of all the expressions for the const calculation.
64    pub arenas: Arc<Arenas<'db>>,
65}
66
67// TODO: Review this well.
68unsafe impl<'db> salsa::Update for Constant<'db> {
69    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
70        let old_constant: &mut Constant<'db> = unsafe { &mut *old_pointer };
71
72        if old_constant.value != new_value.value {
73            *old_constant = new_value;
74            return true;
75        }
76
77        false
78    }
79}
80
81impl<'db> Constant<'db> {
82    pub fn ty(&self) -> TypeId<'db> {
83        self.arenas.exprs[self.value].ty()
84    }
85}
86
87/// Information about a constant definition.
88///
89/// Helper struct for the data returned by [ConstantSemantic::constant_semantic_data].
90#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
91#[debug_db(dyn Database)]
92pub struct ConstantData<'db> {
93    pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
94    pub constant: Maybe<Constant<'db>>,
95    pub const_value: ConstValueId<'db>,
96    pub resolver_data: Arc<ResolverData<'db>>,
97}
98
99define_short_id!(ConstValueId, ConstValue<'db>);
100semantic_object_for_id!(ConstValueId, ConstValue<'a>);
101impl<'db> ConstValueId<'db> {
102    /// Creates a new const value from a BigInt.
103    pub fn from_int(db: &'db dyn Database, ty: TypeId<'db>, value: &BigInt) -> Self {
104        let get_basic_const_value = |ty| {
105            let info = db.const_calc_info();
106            if ty != info.u256 {
107                ConstValue::Int(value.clone(), ty).intern(db)
108            } else {
109                let mask128 = BigInt::from(u128::MAX);
110                let low = value & mask128;
111                let high = value >> 128;
112                ConstValue::Struct(
113                    vec![
114                        (ConstValue::Int(low, info.u128).intern(db)),
115                        (ConstValue::Int(high, info.u128).intern(db)),
116                    ],
117                    ty,
118                )
119                .intern(db)
120            }
121        };
122        if let Some(inner) = try_extract_nz_wrapped_type(db, ty) {
123            ConstValue::NonZero(get_basic_const_value(inner)).intern(db)
124        } else {
125            get_basic_const_value(ty)
126        }
127    }
128    /// Returns a formatted string of the const value.
129    pub fn format(&self, db: &dyn Database) -> String {
130        format!("{:?}", self.long(db).debug(db))
131    }
132
133    /// Returns true if the const does not depend on any generics.
134    pub fn is_fully_concrete(&self, db: &dyn Database) -> bool {
135        self.long(db).is_fully_concrete(db)
136    }
137
138    /// Returns true if the const does not contain any inference variables.
139    pub fn is_var_free(&self, db: &dyn Database) -> bool {
140        self.long(db).is_var_free(db)
141    }
142
143    /// Returns the type of the const.
144    pub fn ty(&self, db: &'db dyn Database) -> Maybe<TypeId<'db>> {
145        self.long(db).ty(db)
146    }
147
148    /// Returns the value of an int const as a BigInt.
149    pub fn to_int(&self, db: &'db dyn Database) -> Option<&'db BigInt> {
150        self.long(db).to_int()
151    }
152
153    /// A utility function for extracting the generic parameters arguments from a ConstValueId.
154    /// Uses memoization via `visited` to avoid re-traversing shared subtypes in DAG structures.
155    pub fn extract_generic_params(
156        &self,
157        db: &'db dyn Database,
158        generic_parameters: &mut OrderedHashSet<GenericParamId<'db>>,
159        visited: &mut OrderedHashSet<TypeId<'db>>,
160    ) -> Maybe<()> {
161        match self.long(db) {
162            ConstValue::Int(_, type_id) | ConstValue::Struct(_, type_id) => {
163                type_id.extract_generic_params(db, generic_parameters, visited)?
164            }
165            ConstValue::Enum(_, const_value_id) => {
166                const_value_id.ty(db)?.extract_generic_params(db, generic_parameters, visited)?
167            }
168            ConstValue::NonZero(const_value_id) => {
169                const_value_id.extract_generic_params(db, generic_parameters, visited)?
170            }
171            ConstValue::Generic(generic_param_id) => {
172                generic_parameters.insert(*generic_param_id);
173            }
174            ConstValue::ImplConstant(impl_constant_id) => {
175                for garg in impl_constant_id.impl_id().concrete_trait(db)?.generic_args(db) {
176                    garg.extract_generic_params(db, generic_parameters, visited)?;
177                }
178            }
179            ConstValue::Var(_, type_id) => {
180                type_id.extract_generic_params(db, generic_parameters, visited)?
181            }
182            ConstValue::Missing(diagnostic_added) => return Err(*diagnostic_added),
183        }
184        Ok(())
185    }
186}
187
188/// Moves the value of a felt252, to the range of `[range_min, range_min + PRIME)`.
189pub fn felt252_for_downcast(value: &BigInt, range_min: &BigInt) -> BigInt {
190    Felt252::from(value - range_min).to_bigint() + range_min
191}
192
193/// Canonicalize the value of a felt252, to the range of `(-PRIME, PRIME)`.
194pub fn canonical_felt252(value: &BigInt) -> BigInt {
195    value % &*CAIRO_PRIME_BIGINT
196}
197
198/// A constant value.
199#[derive(Clone, Debug, Hash, PartialEq, Eq, SemanticObject, HeapSize, salsa::Update)]
200pub enum ConstValue<'db> {
201    Int(#[dont_rewrite] BigInt, TypeId<'db>),
202    Struct(Vec<ConstValueId<'db>>, TypeId<'db>),
203    Enum(ConcreteVariant<'db>, ConstValueId<'db>),
204    NonZero(ConstValueId<'db>),
205    Generic(#[dont_rewrite] GenericParamId<'db>),
206    ImplConstant(ImplConstantId<'db>),
207    Var(ConstVar<'db>, TypeId<'db>),
208    /// A missing value, used in cases where the value is not known due to diagnostics.
209    Missing(#[dont_rewrite] DiagnosticAdded),
210}
211impl<'db> ConstValue<'db> {
212    /// Returns true if the const does not depend on any generics.
213    pub fn is_fully_concrete(&self, db: &dyn Database) -> bool {
214        self.ty(db).unwrap().is_fully_concrete(db)
215            && match self {
216                ConstValue::Int(_, _) => true,
217                ConstValue::Struct(members, _) => {
218                    members.iter().all(|member| member.is_fully_concrete(db))
219                }
220                ConstValue::Enum(_, val) | ConstValue::NonZero(val) => val.is_fully_concrete(db),
221                ConstValue::Generic(_)
222                | ConstValue::Var(_, _)
223                | ConstValue::Missing(_)
224                | ConstValue::ImplConstant(_) => false,
225            }
226    }
227
228    /// Returns true if the const does not contain any inference variables.
229    pub fn is_var_free(&self, db: &dyn Database) -> bool {
230        self.ty(db).unwrap().is_var_free(db)
231            && match self {
232                ConstValue::Int(_, _) | ConstValue::Generic(_) | ConstValue::Missing(_) => true,
233                ConstValue::Struct(members, _) => {
234                    members.iter().all(|member| member.is_var_free(db))
235                }
236                ConstValue::Enum(_, val) | ConstValue::NonZero(val) => val.is_var_free(db),
237                ConstValue::Var(_, _) => false,
238                ConstValue::ImplConstant(impl_constant) => impl_constant.impl_id().is_var_free(db),
239            }
240    }
241
242    /// Returns the type of the const.
243    pub fn ty(&self, db: &'db dyn Database) -> Maybe<TypeId<'db>> {
244        Ok(match self {
245            ConstValue::Int(_, ty) => *ty,
246            ConstValue::Struct(_, ty) => *ty,
247            ConstValue::Enum(variant, _) => {
248                TypeLongId::Concrete(ConcreteTypeId::Enum(variant.concrete_enum_id)).intern(db)
249            }
250            ConstValue::NonZero(value) => core_nonzero_ty(db, value.ty(db)?),
251            ConstValue::Generic(param) => {
252                extract_matches!(db.generic_param_semantic(*param)?, GenericParam::Const).ty
253            }
254            ConstValue::Var(_, ty) => *ty,
255            ConstValue::Missing(_) => TypeId::missing(db, skip_diagnostic()),
256            ConstValue::ImplConstant(impl_constant_id) => {
257                db.impl_constant_concrete_implized_type(*impl_constant_id)?
258            }
259        })
260    }
261
262    /// Returns the value of an int const as a BigInt.
263    pub fn to_int(&self) -> Option<&BigInt> {
264        match self {
265            ConstValue::Int(value, _) => Some(value),
266            _ => None,
267        }
268    }
269}
270
271/// An impl item of kind const.
272#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, SemanticObject, HeapSize, salsa::Update)]
273pub struct ImplConstantId<'db> {
274    /// The impl the item const is in.
275    impl_id: ImplId<'db>,
276    /// The trait const this impl const "implements".
277    trait_constant_id: TraitConstantId<'db>,
278}
279
280impl<'db> ImplConstantId<'db> {
281    /// Creates a new impl constant id. For an impl constant of a concrete impl, asserts that the
282    /// trait constant belongs to the same trait that the impl implements (panics if not).
283    pub fn new(
284        impl_id: ImplId<'db>,
285        trait_constant_id: TraitConstantId<'db>,
286        db: &dyn Database,
287    ) -> Self {
288        if let ImplLongId::Concrete(concrete_impl) = impl_id.long(db) {
289            let impl_def_id = concrete_impl.impl_def_id(db);
290            assert_eq!(Ok(trait_constant_id.trait_id(db)), db.impl_def_trait(impl_def_id));
291        }
292
293        ImplConstantId { impl_id, trait_constant_id }
294    }
295    pub fn impl_id(&self) -> ImplId<'db> {
296        self.impl_id
297    }
298    pub fn trait_constant_id(&self) -> TraitConstantId<'db> {
299        self.trait_constant_id
300    }
301
302    pub fn format(&self, db: &dyn Database) -> String {
303        format!("{}::{}", self.impl_id.name(db), self.trait_constant_id.name(db).long(db))
304    }
305}
306impl<'db> DebugWithDb<'db> for ImplConstantId<'db> {
307    type Db = dyn Database;
308
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db dyn Database) -> std::fmt::Result {
310        write!(f, "{}", self.format(db))
311    }
312}
313
314/// Returns the semantic data of a constant.
315#[salsa::tracked(returns(ref), cycle_result=constant_semantic_data_cycle)]
316fn constant_semantic_data<'db>(
317    db: &'db dyn Database,
318    const_id: ConstantId<'db>,
319    in_cycle: bool,
320) -> Maybe<ConstantData<'db>> {
321    let lookup_item_id = LookupItemId::ModuleItem(ModuleItemId::Constant(const_id));
322    if in_cycle {
323        constant_semantic_data_cycle_helper(
324            db,
325            &db.module_constant_by_id(const_id)?,
326            lookup_item_id,
327            None,
328            &const_id,
329        )
330    } else {
331        constant_semantic_data_helper(
332            db,
333            &db.module_constant_by_id(const_id)?,
334            lookup_item_id,
335            None,
336            &const_id,
337        )
338    }
339}
340
341/// Cycle handling for [ConstantSemantic::constant_semantic_data].
342fn constant_semantic_data_cycle<'db>(
343    db: &'db dyn Database,
344    _id: salsa::Id,
345    const_id: ConstantId<'db>,
346    _in_cycle: bool,
347) -> Maybe<ConstantData<'db>> {
348    let lookup_item_id = LookupItemId::ModuleItem(ModuleItemId::Constant(const_id));
349    constant_semantic_data_cycle_helper(
350        db,
351        &db.module_constant_by_id(const_id)?,
352        lookup_item_id,
353        None,
354        &const_id,
355    )
356}
357
358/// Returns constant semantic data for the given ItemConstant.
359pub fn constant_semantic_data_helper<'db>(
360    db: &'db dyn Database,
361    constant_ast: &ItemConstant<'db>,
362    lookup_item_id: LookupItemId<'db>,
363    parent_resolver_data: Option<Arc<ResolverData<'db>>>,
364    element_id: &impl LanguageElementId<'db>,
365) -> Maybe<ConstantData<'db>> {
366    let module_id = element_id.parent_module(db);
367    let mut diagnostics: SemanticDiagnostics<'_> = SemanticDiagnostics::new(module_id);
368    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
369    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
370    // the item instead of all the module data.
371
372    let inference_id = InferenceId::LookupItemDeclaration(lookup_item_id);
373
374    let mut resolver = match parent_resolver_data {
375        Some(parent_resolver_data) => {
376            Resolver::with_data(db, parent_resolver_data.clone_with_inference_id(db, inference_id))
377        }
378        None => Resolver::new(db, module_id, inference_id),
379    };
380    resolver.set_feature_config(element_id, constant_ast, &mut diagnostics);
381
382    let ty_syntax = constant_ast.type_clause(db).ty(db);
383    let constant_type = resolve_type(db, &mut diagnostics, &mut resolver, &ty_syntax);
384    // The type of a constant must not rely on its value, to create better stability for the
385    // constant signature.
386    if !constant_type.is_var_free(db) {
387        diagnostics.report(
388            ty_syntax.stable_ptr(db).untyped(),
389            SemanticDiagnosticKind::ConstTypeNotVarFree,
390        );
391    }
392
393    let mut ctx = ComputationContext::new_global(db, &mut diagnostics, &mut resolver);
394    let value = compute_expr_semantic(&mut ctx, &constant_ast.value(db));
395    let const_value = resolve_const_expr_and_evaluate(
396        db,
397        &mut ctx,
398        &value,
399        constant_ast.stable_ptr(db).untyped(),
400        constant_type,
401        true,
402    );
403    let constant = Ok(Constant { value: value.id, arenas: Arc::new(ctx.arenas) });
404    let const_value = resolver
405        .inference()
406        .rewrite(const_value)
407        .unwrap_or_else(|_| ConstValue::Missing(skip_diagnostic()).intern(db));
408    let resolver_data = Arc::new(resolver.data);
409    Ok(ConstantData { diagnostics: diagnostics.build(), const_value, constant, resolver_data })
410}
411
412/// Helper for cycle handling of constants.
413pub fn constant_semantic_data_cycle_helper<'db>(
414    db: &'db dyn Database,
415    constant_ast: &ItemConstant<'db>,
416    lookup_item_id: LookupItemId<'db>,
417    parent_resolver_data: Option<Arc<ResolverData<'db>>>,
418    element_id: &impl LanguageElementId<'db>,
419) -> Maybe<ConstantData<'db>> {
420    let module_id = element_id.parent_module(db);
421    let mut diagnostics: SemanticDiagnostics<'_> = SemanticDiagnostics::new(module_id);
422
423    let inference_id = InferenceId::LookupItemDeclaration(lookup_item_id);
424
425    let resolver = match parent_resolver_data {
426        Some(parent_resolver_data) => {
427            Resolver::with_data(db, parent_resolver_data.clone_with_inference_id(db, inference_id))
428        }
429        None => Resolver::new(db, module_id, inference_id),
430    };
431
432    let resolver_data = Arc::new(resolver.data);
433
434    let diagnostic_added =
435        diagnostics.report(constant_ast.stable_ptr(db), SemanticDiagnosticKind::ConstCycle);
436    Ok(ConstantData {
437        constant: Err(diagnostic_added),
438        const_value: ConstValue::Missing(diagnostic_added).intern(db),
439        diagnostics: diagnostics.build(),
440        resolver_data,
441    })
442}
443
444/// Checks if the given expression only involved constant calculations.
445pub fn validate_const_expr<'db>(ctx: &mut ComputationContext<'db, '_>, expr_id: ExprId) {
446    let info = ctx.db.const_calc_info();
447    let mut eval_ctx = ConstantEvaluateContext {
448        db: ctx.db,
449        info: info.as_ref(),
450        arenas: &ctx.arenas,
451        vars: Default::default(),
452        generic_substitution: Default::default(),
453        depth: 0,
454        diagnostics: ctx.diagnostics,
455    };
456    eval_ctx.validate(expr_id);
457}
458
459/// Resolves the given const expression and evaluates its value.
460pub fn resolve_const_expr_and_evaluate<'db, 'mt>(
461    db: &'db dyn Database,
462    ctx: &'mt mut ComputationContext<'db, '_>,
463    value: &ExprAndId<'db>,
464    const_stable_ptr: SyntaxStablePtrId<'db>,
465    target_type: TypeId<'db>,
466    finalize: bool,
467) -> ConstValueId<'db> {
468    let prev_err_count = ctx.diagnostics.error_count;
469    let mut_ref = &mut ctx.resolver;
470    let mut inference: crate::expr::inference::Inference<'db, '_> = mut_ref.inference();
471    if let Err(err_set) = inference.conform_ty(value.ty(), target_type) {
472        inference.report_on_pending_error(err_set, ctx.diagnostics, value.stable_ptr().untyped());
473    }
474
475    if finalize {
476        // Check fully resolved.
477        inference.finalize(ctx.diagnostics, const_stable_ptr);
478    } else if let Err(err_set) = inference.solve() {
479        inference.report_on_pending_error(err_set, ctx.diagnostics, const_stable_ptr);
480    }
481    ctx.apply_inference_rewriter();
482
483    match &value.expr {
484        Expr::Constant(ExprConstant { const_value_id, .. }) => *const_value_id,
485        // Check that the expression is a valid constant.
486        _ if ctx.diagnostics.error_count > prev_err_count => {
487            ConstValue::Missing(skip_diagnostic()).intern(db)
488        }
489        _ => {
490            let info = db.const_calc_info();
491            let mut eval_ctx = ConstantEvaluateContext {
492                db,
493                info: info.as_ref(),
494                arenas: &ctx.arenas,
495                vars: Default::default(),
496                generic_substitution: Default::default(),
497                depth: 0,
498                diagnostics: ctx.diagnostics,
499            };
500            eval_ctx.validate(value.id);
501            if eval_ctx.diagnostics.error_count > prev_err_count {
502                ConstValue::Missing(skip_diagnostic()).intern(db)
503            } else {
504                eval_ctx.evaluate(value.id)
505            }
506        }
507    }
508}
509
510/// A context for evaluating constant expressions.
511struct ConstantEvaluateContext<'a, 'r, 'mt> {
512    db: &'a dyn Database,
513    info: &'r ConstCalcInfo<'a>,
514    arenas: &'r Arenas<'a>,
515    vars: OrderedHashMap<VarId<'a>, ConstValueId<'a>>,
516    generic_substitution: GenericSubstitution<'a>,
517    depth: usize,
518    diagnostics: &'mt mut SemanticDiagnostics<'a>,
519}
520impl<'a, 'r, 'mt> ConstantEvaluateContext<'a, 'r, 'mt> {
521    /// Validate the given expression can be used as constant.
522    fn validate(&mut self, expr_id: ExprId) {
523        match &self.arenas.exprs[expr_id] {
524            Expr::Var(_) | Expr::Constant(_) | Expr::Missing(_) => {}
525            Expr::Block(ExprBlock { statements, tail: Some(inner), .. }) => {
526                for statement_id in statements {
527                    match &self.arenas.statements[*statement_id] {
528                        Statement::Let(statement) => {
529                            self.validate(statement.expr);
530                            if let Some(else_clause) = &statement.else_clause {
531                                self.validate(*else_clause);
532                            }
533                        }
534                        Statement::Expr(expr) => {
535                            self.validate(expr.expr);
536                        }
537                        other => {
538                            self.diagnostics.report(
539                                other.stable_ptr(),
540                                SemanticDiagnosticKind::UnsupportedConstant,
541                            );
542                        }
543                    }
544                }
545                self.validate(*inner);
546            }
547            Expr::FunctionCall(expr) => {
548                for arg in &expr.args {
549                    match arg {
550                        ExprFunctionCallArg::Value(arg) => self.validate(*arg),
551                        ExprFunctionCallArg::Reference(var) => {
552                            self.diagnostics.report(
553                                var.stable_ptr(),
554                                SemanticDiagnosticKind::UnsupportedConstant,
555                            );
556                        }
557                        ExprFunctionCallArg::TempReference(expr_id) => {
558                            self.diagnostics.report(
559                                self.arenas.exprs[*expr_id].stable_ptr(),
560                                SemanticDiagnosticKind::UnsupportedConstant,
561                            );
562                        }
563                    }
564                }
565                if !self.is_function_const(expr.function) {
566                    self.diagnostics.report(
567                        expr.stable_ptr.untyped(),
568                        SemanticDiagnosticKind::UnsupportedConstant,
569                    );
570                }
571            }
572            // Already validated by `apply_inference_rewriter`.
573            Expr::Literal(_) => {}
574            Expr::Tuple(expr) => {
575                for item in &expr.items {
576                    self.validate(*item);
577                }
578            }
579            Expr::StructCtor(ExprStructCtor { members, base_struct: None, .. }) => {
580                for (expr_id, _) in members {
581                    self.validate(*expr_id);
582                }
583            }
584            Expr::EnumVariantCtor(expr) => self.validate(expr.value_expr),
585            Expr::MemberAccess(expr) => self.validate(expr.expr),
586            Expr::FixedSizeArray(expr) => match &expr.items {
587                crate::FixedSizeArrayItems::Items(items) => {
588                    for item in items {
589                        self.validate(*item);
590                    }
591                }
592                crate::FixedSizeArrayItems::ValueAndSize(value, _) => {
593                    self.validate(*value);
594                }
595            },
596            Expr::Snapshot(expr) => self.validate(expr.inner),
597            Expr::Desnap(expr) => self.validate(expr.inner),
598            Expr::LogicalOperator(expr) => {
599                self.validate(expr.lhs);
600                self.validate(expr.rhs);
601            }
602            Expr::Match(expr) => {
603                self.validate(expr.matched_expr);
604                for arm in &expr.arms {
605                    self.validate(arm.expression);
606                }
607            }
608            Expr::If(expr) => {
609                for condition in &expr.conditions {
610                    self.validate(match condition {
611                        Condition::BoolExpr(id) | Condition::Let(id, _) => *id,
612                    });
613                }
614                self.validate(expr.if_block);
615                if let Some(else_block) = expr.else_block {
616                    self.validate(else_block);
617                }
618            }
619            other => {
620                self.diagnostics.report(
621                    other.stable_ptr().untyped(),
622                    SemanticDiagnosticKind::UnsupportedConstant,
623                );
624            }
625        }
626    }
627
628    /// Returns true if the given function is allowed to be called in constant context.
629    fn is_function_const(&self, function_id: FunctionId<'a>) -> bool {
630        if function_id == self.panic_with_felt252 {
631            return true;
632        }
633        let db = self.db;
634        let concrete_function = function_id.get_concrete(db);
635        let signature = (|| match concrete_function.generic_function {
636            GenericFunctionId::Free(id) => db.free_function_signature(id),
637            GenericFunctionId::Extern(id) => db.extern_function_signature(id),
638            GenericFunctionId::Impl(id) => {
639                if let ImplLongId::Concrete(impl_id) = id.impl_id.long(db)
640                    && let Ok(Some(impl_function_id)) = impl_id.get_impl_function(db, id.function)
641                {
642                    return self.db.impl_function_signature(impl_function_id);
643                }
644                self.db.trait_function_signature(id.function)
645            }
646        })();
647        if signature.map(|s| s.is_const) == Ok(true) {
648            return true;
649        }
650        let Ok(Some(body)) = concrete_function.body(db) else { return false };
651        let GenericFunctionWithBodyId::Impl(imp) = body.generic_function(db) else {
652            return false;
653        };
654        let impl_def = imp.concrete_impl_id.impl_def_id(db);
655        if impl_def.parent_module(db).owning_crate(db) != db.core_crate() {
656            return false;
657        }
658        let Ok(trait_id) = db.impl_def_trait(impl_def) else {
659            return false;
660        };
661        self.const_traits.contains(&trait_id)
662    }
663
664    /// Evaluate the given const expression value.
665    fn evaluate<'ctx>(&'ctx mut self, expr_id: ExprId) -> ConstValueId<'a> {
666        let expr = &self.arenas.exprs[expr_id];
667        let db = self.db;
668        let to_missing = |diag_added| ConstValue::Missing(diag_added).intern(db);
669        match expr {
670            Expr::Var(expr) => self.vars.get(&expr.var).copied().unwrap_or_else(|| {
671                to_missing(
672                    self.diagnostics
673                        .report(expr.stable_ptr, SemanticDiagnosticKind::UnsupportedConstant),
674                )
675            }),
676            Expr::Constant(expr) => self
677                .generic_substitution
678                .substitute(self.db, expr.const_value_id)
679                .unwrap_or_else(to_missing),
680            Expr::Block(ExprBlock { statements, tail: Some(inner), .. }) => {
681                for statement_id in statements {
682                    match &self.arenas.statements[*statement_id] {
683                        Statement::Let(statement) => {
684                            let value = self.evaluate(statement.expr);
685                            if self.destructure_pattern(statement.pattern, value).is_none() {
686                                if let Some(else_clause) = &statement.else_clause {
687                                    // Only exiting the block is possible - so this is a return of a
688                                    // panic.
689                                    return self.evaluate(*else_clause);
690                                } else {
691                                    // Either the pattern is refutable and we are missing an else
692                                    // clause, or the pattern is irrefutable and the pattern have
693                                    // failed for some reason. Both should already cause diagstics.
694                                    return to_missing(skip_diagnostic());
695                                }
696                            }
697                        }
698                        Statement::Expr(expr) => {
699                            self.evaluate(expr.expr);
700                        }
701                        other => {
702                            self.diagnostics.report(
703                                other.stable_ptr(),
704                                SemanticDiagnosticKind::UnsupportedConstant,
705                            );
706                        }
707                    }
708                }
709                self.evaluate(*inner)
710            }
711            Expr::FunctionCall(expr) => self.evaluate_function_call(expr),
712            Expr::Literal(expr) => ConstValueId::from_int(db, expr.ty, &expr.value),
713            Expr::Tuple(expr) => ConstValue::Struct(
714                expr.items.iter().map(|expr_id| self.evaluate(*expr_id)).collect(),
715                expr.ty,
716            )
717            .intern(db),
718            Expr::StructCtor(ExprStructCtor {
719                members,
720                base_struct: None,
721                ty,
722                concrete_struct_id,
723                ..
724            }) => {
725                let member_order = match db.concrete_struct_members(*concrete_struct_id) {
726                    Ok(member_order) => member_order,
727                    Err(diag_add) => return to_missing(diag_add),
728                };
729                ConstValue::Struct(
730                    member_order
731                        .values()
732                        .map(|m| {
733                            members
734                                .iter()
735                                .find(|(_, member_id)| m.id == *member_id)
736                                .map(|(expr_id, _)| self.evaluate(*expr_id))
737                                // Semantic validation already reported an error, suppress cascading
738                                // errors from const evaluation.
739                                .unwrap_or_else(|| ConstValue::Missing(skip_diagnostic()).intern(db))
740                        })
741                        .collect(),
742                    *ty,
743                )
744                .intern(db)
745            }
746            Expr::EnumVariantCtor(expr) => {
747                ConstValue::Enum(expr.variant, self.evaluate(expr.value_expr)).intern(db)
748            }
749            Expr::MemberAccess(expr) => {
750                self.evaluate_member_access(expr).unwrap_or_else(to_missing)
751            }
752            Expr::FixedSizeArray(expr) => ConstValue::Struct(
753                match &expr.items {
754                    crate::FixedSizeArrayItems::Items(items) => {
755                        items.iter().map(|expr_id| self.evaluate(*expr_id)).collect()
756                    }
757                    crate::FixedSizeArrayItems::ValueAndSize(value, count) => {
758                        let value = self.evaluate(*value);
759                        if let Some(count) = count.to_int(db) {
760                            vec![value; count.to_usize().unwrap()]
761                        } else {
762                            self.diagnostics.report(
763                                expr.stable_ptr.untyped(),
764                                SemanticDiagnosticKind::UnsupportedConstant,
765                            );
766                            vec![]
767                        }
768                    }
769                },
770                expr.ty,
771            )
772            .intern(db),
773            Expr::Snapshot(expr) => self.evaluate(expr.inner),
774            Expr::Desnap(expr) => self.evaluate(expr.inner),
775            Expr::LogicalOperator(expr) => {
776                let lhs = self.evaluate(expr.lhs);
777                if let ConstValue::Enum(v, _) = lhs.long(db) {
778                    let early_return_variant = match expr.op {
779                        LogicalOperator::AndAnd => false_variant(self.db),
780                        LogicalOperator::OrOr => true_variant(self.db),
781                    };
782                    if *v == early_return_variant { lhs } else { self.evaluate(expr.rhs) }
783                } else if let ConstValue::Missing(diag_added) = lhs.long(db) {
784                    to_missing(*diag_added)
785                } else {
786                    to_missing(self.diagnostics.report(
787                        self.arenas.exprs[expr.lhs].stable_ptr(),
788                        SemanticDiagnosticKind::UnsupportedConstant,
789                    ))
790                }
791            }
792            Expr::Match(expr) => {
793                let value = self.evaluate(expr.matched_expr);
794                for arm in &expr.arms {
795                    for pattern_id in &arm.patterns {
796                        if self.destructure_pattern(*pattern_id, value).is_some() {
797                            return self.evaluate(arm.expression);
798                        }
799                    }
800                }
801                to_missing(
802                    self.diagnostics.report(
803                        expr.stable_ptr.untyped(),
804                        SemanticDiagnosticKind::UnsupportedConstant,
805                    ),
806                )
807            }
808            Expr::If(expr) => {
809                let mut if_condition: bool = true;
810                for condition in &expr.conditions {
811                    match condition {
812                        crate::Condition::BoolExpr(id) => {
813                            let condition = self.evaluate(*id);
814                            if condition == self.true_const {
815                                continue;
816                            } else if condition == self.false_const {
817                                if_condition = false;
818                                break;
819                            } else {
820                                return to_missing(skip_diagnostic());
821                            }
822                        }
823                        crate::Condition::Let(id, patterns) => {
824                            let value = self.evaluate(*id);
825                            let mut found_pattern = false;
826                            for pattern_id in patterns {
827                                if self.destructure_pattern(*pattern_id, value).is_some() {
828                                    found_pattern = true;
829                                    break;
830                                }
831                            }
832                            if !found_pattern {
833                                if_condition = false;
834                                break;
835                            }
836                        }
837                    }
838                }
839
840                if if_condition {
841                    self.evaluate(expr.if_block)
842                } else if let Some(else_block) = expr.else_block {
843                    self.evaluate(else_block)
844                } else {
845                    self.unit_const
846                }
847            }
848            _ => to_missing(skip_diagnostic()),
849        }
850    }
851
852    /// Attempts to evaluate constants from a const function call.
853    fn evaluate_function_call(&mut self, expr: &ExprFunctionCall<'a>) -> ConstValueId<'a> {
854        let db = self.db;
855        let to_missing = |diag_added| ConstValue::Missing(diag_added).intern(db);
856        let args = expr
857            .args
858            .iter()
859            .filter_map(|arg| try_extract_matches!(arg, ExprFunctionCallArg::Value))
860            .map(|arg| {
861                let value = self.evaluate(*arg);
862                if value.is_fully_concrete(db) || matches!(value.long(db), ConstValue::Missing(_)) {
863                    value
864                } else {
865                    to_missing(self.diagnostics.report(
866                        self.arenas.exprs[*arg].stable_ptr(),
867                        SemanticDiagnosticKind::UnsupportedConstant,
868                    ))
869                }
870            })
871            .collect_vec();
872        if expr.function == self.panic_with_felt252 {
873            return to_missing(self.diagnostics.report(
874                expr.stable_ptr.untyped(),
875                SemanticDiagnosticKind::FailedConstantCalculation,
876            ));
877        }
878        let concrete_function =
879            match self.generic_substitution.substitute(db, expr.function.get_concrete(db)) {
880                Ok(v) => v,
881                Err(err) => return to_missing(err),
882            };
883        if let Some(calc_result) =
884            self.evaluate_const_function_call(&concrete_function, &args, expr)
885        {
886            return calc_result;
887        }
888
889        let GenericFunctionId::Impl(imp) = concrete_function.generic_function else {
890            return to_missing(skip_diagnostic());
891        };
892        let bool_value = |condition: bool| {
893            if condition { self.true_const } else { self.false_const }
894        };
895
896        if imp.function == self.eq_fn {
897            return bool_value(self.const_values_eq(args[0], args[1]));
898        } else if imp.function == self.ne_fn {
899            return bool_value(!self.const_values_eq(args[0], args[1]));
900        } else if imp.function == self.not_fn {
901            return bool_value(args[0] == self.false_const);
902        }
903
904        let args = match args
905            .into_iter()
906            .map(|arg| NumericArg::try_new(db, arg))
907            .collect::<Option<Vec<_>>>()
908        {
909            Some(args) => args,
910            None => return to_missing(skip_diagnostic()),
911        };
912        let value = match imp.function {
913            id if id == self.neg_fn => -&args[0].v,
914            id if id == self.add_fn => &args[0].v + &args[1].v,
915            id if id == self.sub_fn => &args[0].v - &args[1].v,
916            id if id == self.mul_fn => &args[0].v * &args[1].v,
917            id if (id == self.div_fn || id == self.rem_fn) && args[1].v.is_zero() => {
918                return to_missing(
919                    self.diagnostics
920                        .report(expr.stable_ptr.untyped(), SemanticDiagnosticKind::DivisionByZero),
921                );
922            }
923            id if id == self.div_fn => &args[0].v / &args[1].v,
924            id if id == self.rem_fn => &args[0].v % &args[1].v,
925            id if id == self.bitand_fn => &args[0].v & &args[1].v,
926            id if id == self.bitor_fn => &args[0].v | &args[1].v,
927            id if id == self.bitxor_fn => &args[0].v ^ &args[1].v,
928            id if id == self.lt_fn => return bool_value(args[0].v < args[1].v),
929            id if id == self.le_fn => return bool_value(args[0].v <= args[1].v),
930            id if id == self.gt_fn => return bool_value(args[0].v > args[1].v),
931            id if id == self.ge_fn => return bool_value(args[0].v >= args[1].v),
932            id if id == self.div_rem_fn => {
933                // No need for non-zero check as this is type checked to begin with.
934                // Also results are always in the range of the input type, so `unwrap`s are ok.
935                return ConstValue::Struct(
936                    vec![
937                        ConstValueId::from_int(db, args[0].ty, &(&args[0].v / &args[1].v)),
938                        ConstValueId::from_int(db, args[0].ty, &(&args[0].v % &args[1].v)),
939                    ],
940                    expr.ty,
941                )
942                .intern(db);
943            }
944            _ => return to_missing(skip_diagnostic()),
945        };
946        if expr.ty == self.felt252 {
947            ConstValue::Int(canonical_felt252(&value), expr.ty).intern(db)
948        } else if let Err(err) = validate_literal(db, expr.ty, &value) {
949            to_missing(
950                self.diagnostics
951                    .report(expr.stable_ptr.untyped(), SemanticDiagnosticKind::LiteralError(err)),
952            )
953        } else {
954            ConstValueId::from_int(db, expr.ty, &value)
955        }
956    }
957
958    /// Attempts to evaluate a constant function call.
959    fn evaluate_const_function_call(
960        &mut self,
961        concrete_function: &ConcreteFunction<'a>,
962        args: &[ConstValueId<'a>],
963        expr: &ExprFunctionCall<'a>,
964    ) -> Option<ConstValueId<'a>> {
965        let db = self.db;
966        if let GenericFunctionId::Extern(extern_fn) = concrete_function.generic_function {
967            let expr_ty = self.generic_substitution.substitute(db, expr.ty).ok()?;
968            if self.upcast_fns.contains(&extern_fn) {
969                let [arg] = args else { return None };
970                return Some(ConstValueId::from_int(db, expr_ty, arg.to_int(db)?));
971            } else if self.unwrap_non_zero == extern_fn {
972                let [arg] = args else { return None };
973                return try_extract_matches!(arg.long(db), ConstValue::NonZero).copied();
974            } else if self.u128s_from_felt252 == extern_fn {
975                let [arg] = args else { return None };
976                let TypeLongId::Concrete(ConcreteTypeId::Enum(enm)) = expr_ty.long(db) else {
977                    return None;
978                };
979                let (narrow, wide) =
980                    db.concrete_enum_variants(*enm).ok()?.into_iter().collect_tuple()?;
981                let value = Felt252::from(arg.to_int(db)?).to_bigint();
982                let mask128 = BigInt::from(u128::MAX);
983                let low = (&value) & mask128;
984                let high: BigInt = (&value) >> 128;
985                return Some(if high.is_zero() {
986                    ConstValue::Enum(narrow, ConstValue::Int(low, narrow.ty).intern(db)).intern(db)
987                } else {
988                    ConstValue::Enum(
989                        wide,
990                        ConstValue::Struct(
991                            vec![
992                                (ConstValue::Int(high, self.u128).intern(db)),
993                                (ConstValue::Int(low, self.u128).intern(db)),
994                            ],
995                            wide.ty,
996                        )
997                        .intern(db),
998                    )
999                    .intern(db)
1000                });
1001            } else if let Some(reversed) = self.downcast_fns.get(&extern_fn) {
1002                let [arg] = args else { return None };
1003                let ConstValue::Int(value, input_ty) = arg.long(db) else { return None };
1004                let TypeLongId::Concrete(ConcreteTypeId::Enum(enm)) = expr_ty.long(db) else {
1005                    return None;
1006                };
1007                let (variant0, variant1) =
1008                    db.concrete_enum_variants(*enm).ok()?.into_iter().collect_tuple()?;
1009                let (some, none) =
1010                    if *reversed { (variant1, variant0) } else { (variant0, variant1) };
1011                let success_ty = some.ty;
1012                let out_range = self
1013                    .type_value_ranges
1014                    .get(&success_ty)
1015                    .cloned()
1016                    .or_else(|| {
1017                        let (min, max) = try_extract_bounded_int_type(db, success_ty)?;
1018                        Some(TypeRange::new(min.to_int(db)?.clone(), max.to_int(db)?.clone()))
1019                    })
1020                    .unwrap_or_else(|| {
1021                        unreachable!(
1022                            "`downcast` is only allowed into types that can be literals. Got `{}`.",
1023                            success_ty.format(db)
1024                        )
1025                    });
1026                let value = if *input_ty == self.felt252 {
1027                    felt252_for_downcast(value, &out_range.min)
1028                } else {
1029                    value.clone()
1030                };
1031                return Some(if value >= out_range.min && value <= out_range.max {
1032                    ConstValue::Enum(some, ConstValue::Int(value, success_ty).intern(db)).intern(db)
1033                } else {
1034                    ConstValue::Enum(none, self.unit_const).intern(db)
1035                });
1036            } else if self.nz_fns.contains(&extern_fn) {
1037                let [arg] = args else { return None };
1038                let (ty, is_zero) = match arg.long(db) {
1039                    ConstValue::Int(val, ty) => (ty, val.is_zero()),
1040                    ConstValue::Struct(members, ty) => (
1041                        ty,
1042                        // For u256 struct with (low, high), check if both are zero
1043                        members.iter().all(|member| match member.long(db) {
1044                            ConstValue::Int(val, _) => val.is_zero(),
1045                            _ => false,
1046                        }),
1047                    ),
1048                    _ => unreachable!(
1049                        "`is_zero` is only allowed for integers got `{}`",
1050                        arg.ty(db).unwrap().format(db)
1051                    ),
1052                };
1053
1054                return Some(
1055                    if is_zero {
1056                        ConstValue::Enum(
1057                            crate::corelib::jump_nz_zero_variant(db, *ty),
1058                            self.unit_const,
1059                        )
1060                    } else {
1061                        ConstValue::Enum(
1062                            crate::corelib::jump_nz_nonzero_variant(db, *ty),
1063                            ConstValue::NonZero(*arg).intern(db),
1064                        )
1065                    }
1066                    .intern(db),
1067                );
1068            } else {
1069                return Some(
1070                    ConstValue::Missing(self.diagnostics.report(
1071                        expr.stable_ptr.untyped(),
1072                        SemanticDiagnosticKind::UnsupportedConstant,
1073                    ))
1074                    .intern(db),
1075                );
1076            }
1077        }
1078        let body_id = concrete_function.body(db).ok()??;
1079        let concrete_body_id = body_id.function_with_body_id(db);
1080        let signature = db.function_with_body_signature(concrete_body_id).ok()?;
1081        require(signature.is_const)?;
1082        let generic_substitution = body_id.substitution(db).ok()?;
1083        let body = db.function_body(concrete_body_id).ok()?;
1084        const MAX_CONST_EVAL_DEPTH: usize = 100;
1085        if self.depth > MAX_CONST_EVAL_DEPTH {
1086            return Some(
1087                ConstValue::Missing(self.diagnostics.report(
1088                    expr.stable_ptr,
1089                    SemanticDiagnosticKind::ConstantCalculationDepthExceeded,
1090                ))
1091                .intern(db),
1092            );
1093        }
1094        let mut diagnostics = SemanticDiagnostics::new(concrete_body_id.parent_module(db));
1095        let mut inner = ConstantEvaluateContext {
1096            db,
1097            info: self.info,
1098            arenas: &body.arenas,
1099            vars: signature
1100                .params
1101                .iter()
1102                .map(|p| VarId::Param(p.id))
1103                .zip(args.iter().cloned())
1104                .collect(),
1105            generic_substitution,
1106            depth: self.depth + 1,
1107            diagnostics: &mut diagnostics,
1108        };
1109        let value = inner.evaluate(body.body_expr);
1110        for diagnostic in diagnostics.build().get_all() {
1111            let location = diagnostic.location(db);
1112            let (inner_diag, mut notes) = match diagnostic.kind {
1113                SemanticDiagnosticKind::ConstantCalculationDepthExceeded => {
1114                    self.diagnostics.report(
1115                        expr.stable_ptr,
1116                        SemanticDiagnosticKind::ConstantCalculationDepthExceeded,
1117                    );
1118                    continue;
1119                }
1120                SemanticDiagnosticKind::InnerFailedConstantCalculation(inner_diag, notes) => {
1121                    (inner_diag, notes)
1122                }
1123                _ => (diagnostic.into(), vec![]),
1124            };
1125            notes.push(DiagnosticNote::with_location(
1126                format!("In `{}`", concrete_function.full_path(db)),
1127                location,
1128            ));
1129            self.diagnostics.report(
1130                expr.stable_ptr,
1131                SemanticDiagnosticKind::InnerFailedConstantCalculation(inner_diag, notes),
1132            );
1133        }
1134        Some(value)
1135    }
1136
1137    /// Extract const member access from a const value.
1138    fn evaluate_member_access(&mut self, expr: &ExprMemberAccess<'a>) -> Maybe<ConstValueId<'a>> {
1139        let full_struct = self.evaluate(expr.expr);
1140        let ConstValue::Struct(values, _) = full_struct.long(self.db) else {
1141            // A semantic diagnostic should have been reported.
1142            return Err(skip_diagnostic());
1143        };
1144        let members = self.db.concrete_struct_members(expr.concrete_struct_id)?;
1145        let Some(member_idx) = members.iter().position(|(_, member)| member.id == expr.member)
1146        else {
1147            // A semantic diagnostic should have been reported.
1148            return Err(skip_diagnostic());
1149        };
1150        Ok(values[member_idx])
1151    }
1152
1153    /// Destructures the pattern, binding variables to their values; returns `None` if the pattern
1154    /// didn't match.
1155    fn destructure_pattern(
1156        &mut self,
1157        pattern_id: PatternId,
1158        value: ConstValueId<'a>,
1159    ) -> Option<()> {
1160        let db = self.db;
1161        let pattern = &self.arenas.patterns[pattern_id];
1162        match pattern {
1163            Pattern::Missing(_) | Pattern::StringLiteral(_) => None,
1164            Pattern::Otherwise(_) => Some(()),
1165            Pattern::Literal(v) => {
1166                let arg = NumericArg::try_new(db, value)?;
1167                require(self.eq_in_type(&arg.v, &v.literal.value, arg.ty))
1168            }
1169            Pattern::Variable(pattern) => {
1170                self.vars.insert(VarId::Local(pattern.var.id), value);
1171                Some(())
1172            }
1173            Pattern::Struct(pattern) => {
1174                let ConstValue::Struct(inner_values, _) = value.long(db) else {
1175                    return None;
1176                };
1177                let member_order = db.concrete_struct_members(pattern.concrete_struct_id).ok()?;
1178                for (member, inner_value) in zip(member_order.values(), inner_values) {
1179                    if let Some((inner_pattern, _)) =
1180                        pattern.field_patterns.iter().find(|(_, field)| member.id == field.id)
1181                    {
1182                        self.destructure_pattern(*inner_pattern, *inner_value)?;
1183                    }
1184                }
1185                Some(())
1186            }
1187            Pattern::Tuple(pattern) => {
1188                let ConstValue::Struct(inner_values, _) = value.long(db) else {
1189                    return None;
1190                };
1191                for (inner_pattern, inner_value) in zip(&pattern.field_patterns, inner_values) {
1192                    self.destructure_pattern(*inner_pattern, *inner_value)?;
1193                }
1194                Some(())
1195            }
1196            Pattern::FixedSizeArray(pattern) => {
1197                let ConstValue::Struct(inner_values, _) = value.long(db) else {
1198                    return None;
1199                };
1200                for (inner_pattern, inner_value) in zip(&pattern.elements_patterns, inner_values) {
1201                    self.destructure_pattern(*inner_pattern, *inner_value)?;
1202                }
1203                Some(())
1204            }
1205            Pattern::EnumVariant(pattern) => {
1206                let ConstValue::Enum(variant, inner_value) = value.long(db) else {
1207                    return None;
1208                };
1209                require(pattern.variant.id == variant.id)?;
1210                if let Some(inner_pattern) = pattern.inner_pattern {
1211                    self.destructure_pattern(inner_pattern, *inner_value)
1212                } else {
1213                    Some(())
1214                }
1215            }
1216        }
1217    }
1218
1219    /// Compares two const values for value equality, treating `felt252` as a field.
1220    ///
1221    /// Direct `felt252` literals are stored in their original signed form (`-1` vs `PRIME - 1` are
1222    /// distinct `ConstValueId`s). The PartialEq operator must agree with the field, not with the
1223    /// stored representation, so we canonicalize `felt252` ints before comparing, and recurse into
1224    /// the structural variants - a `felt252` may be nested inside a struct, enum or `NonZero`.
1225    fn const_values_eq(&self, a: ConstValueId<'a>, b: ConstValueId<'a>) -> bool {
1226        if a == b {
1227            return true;
1228        }
1229        match (a.long(self.db), b.long(self.db)) {
1230            (ConstValue::Int(va, ty), ConstValue::Int(vb, _)) => self.eq_in_type(va, vb, *ty),
1231            (ConstValue::Struct(a_members, a_ty), ConstValue::Struct(b_members, b_ty)) => {
1232                a_ty == b_ty
1233                    && a_members.len() == b_members.len()
1234                    && zip(a_members, b_members).all(|(a, b)| self.const_values_eq(*a, *b))
1235            }
1236            (ConstValue::Enum(a_variant, a_value), ConstValue::Enum(b_variant, b_value)) => {
1237                a_variant.id == b_variant.id && self.const_values_eq(*a_value, *b_value)
1238            }
1239            (ConstValue::NonZero(a_value), ConstValue::NonZero(b_value)) => {
1240                self.const_values_eq(*a_value, *b_value)
1241            }
1242            _ => false,
1243        }
1244    }
1245
1246    /// Compares two `BigInt`s of type `ty` for value equality, treating `felt252` as a field.
1247    fn eq_in_type(&self, a: &BigInt, b: &BigInt, ty: TypeId<'a>) -> bool {
1248        a == b || (ty == self.felt252 && Felt252::from(a) == Felt252::from(b))
1249    }
1250}
1251
1252impl<'db, 'r> std::ops::Deref for ConstantEvaluateContext<'db, 'r, '_> {
1253    type Target = ConstCalcInfo<'db>;
1254    fn deref(&self) -> &Self::Target {
1255        self.info
1256    }
1257}
1258
1259/// Helper for the arguments info.
1260struct NumericArg<'db> {
1261    /// The arg's integer value.
1262    v: BigInt,
1263    /// The arg's type, unwrapping `NonZero` types.
1264    ty: TypeId<'db>,
1265}
1266impl<'db> NumericArg<'db> {
1267    /// Extracts the numeric value and its type from `arg`, unwrapping `NonZero` values and reading
1268    /// a struct of 2 values as a `u256`.
1269    fn try_new(db: &'db dyn Database, arg: ConstValueId<'db>) -> Option<Self> {
1270        match arg.long(db) {
1271            ConstValue::Int(v, ty) => Some(Self { v: v.clone(), ty: *ty }),
1272            ConstValue::Struct(v, ty) if *ty == db.core_info().u256 => {
1273                if let [low, high] = &v[..] {
1274                    Some(Self { v: low.to_int(db)? + (high.to_int(db)? << 128), ty: *ty })
1275                } else {
1276                    None
1277                }
1278            }
1279            ConstValue::NonZero(inner) => Self::try_new(db, *inner),
1280            _ => None,
1281        }
1282    }
1283}
1284
1285/// Query implementation of [ConstantSemantic::const_calc_info].
1286fn const_calc_info<'db>(db: &'db dyn Database) -> Arc<ConstCalcInfo<'db>> {
1287    Arc::new(ConstCalcInfo::new(db))
1288}
1289
1290/// Implementation of [ConstantSemantic::const_calc_info].
1291#[salsa::tracked]
1292fn const_calc_info_tracked<'db>(db: &'db dyn Database) -> Arc<ConstCalcInfo<'db>> {
1293    const_calc_info(db)
1294}
1295
1296/// Holds static information about extern functions required for const calculations.
1297#[derive(Debug, PartialEq, Eq, salsa::Update)]
1298pub struct ConstCalcInfo<'db> {
1299    /// Traits that are allowed for consts if their impls is in the corelib.
1300    const_traits: UnorderedHashSet<TraitId<'db>>,
1301    /// The const value for the unit type `()`.
1302    unit_const: ConstValueId<'db>,
1303    /// The const value for `true`.
1304    true_const: ConstValueId<'db>,
1305    /// The const value for `false`.
1306    false_const: ConstValueId<'db>,
1307    /// The function for panicking with a felt252.
1308    panic_with_felt252: FunctionId<'db>,
1309    /// The integer `upcast` style functions.
1310    pub upcast_fns: UnorderedHashSet<ExternFunctionId<'db>>,
1311    /// The integer `downcast` style functions, mapping to whether it returns a reversed Option
1312    /// enum.
1313    pub downcast_fns: UnorderedHashMap<ExternFunctionId<'db>, bool>,
1314    /// The `felt252` into `u128` words libfunc.
1315    pub u128s_from_felt252: ExternFunctionId<'db>,
1316    /// The `unwrap_non_zero` function.
1317    unwrap_non_zero: ExternFunctionId<'db>,
1318    /// The `is_zero` style functions.
1319    pub nz_fns: UnorderedHashSet<ExternFunctionId<'db>>,
1320    /// The range of values of a numeric type.
1321    pub type_value_ranges: UnorderedHashMap<TypeId<'db>, TypeRange>,
1322
1323    core_info: Arc<CoreInfo<'db>>,
1324}
1325
1326impl<'db> std::ops::Deref for ConstCalcInfo<'db> {
1327    type Target = CoreInfo<'db>;
1328    fn deref(&self) -> &CoreInfo<'db> {
1329        &self.core_info
1330    }
1331}
1332
1333impl<'db> ConstCalcInfo<'db> {
1334    /// Creates a new ConstCalcInfo.
1335    fn new(db: &'db dyn Database) -> Self {
1336        let core_info = db.core_info();
1337        let unit_const = ConstValue::Struct(vec![], unit_ty(db)).intern(db);
1338        let core = ModuleHelper::core(db);
1339        let bounded_int = core.submodule("internal").submodule("bounded_int");
1340        let integer = core.submodule("integer");
1341        let zeroable = core.submodule("zeroable");
1342        let starknet = core.submodule("starknet");
1343        let class_hash_module = starknet.submodule("class_hash");
1344        let class_hash_ty = class_hash_module.ty("ClassHash", vec![]);
1345        let contract_address_module = starknet.submodule("contract_address");
1346        let contract_address_ty = contract_address_module.ty("ContractAddress", vec![]);
1347        Self {
1348            const_traits: FromIterator::from_iter([
1349                core_info.neg_trt,
1350                core_info.add_trt,
1351                core_info.sub_trt,
1352                core_info.mul_trt,
1353                core_info.div_trt,
1354                core_info.rem_trt,
1355                core_info.div_rem_trt,
1356                core_info.bitand_trt,
1357                core_info.bitor_trt,
1358                core_info.bitxor_trt,
1359                core_info.partialeq_trt,
1360                core_info.partialord_trt,
1361                core_info.not_trt,
1362            ]),
1363            true_const: ConstValue::Enum(true_variant(db), unit_const).intern(db),
1364            false_const: ConstValue::Enum(false_variant(db), unit_const).intern(db),
1365            unit_const,
1366            panic_with_felt252: core.function_id("panic_with_felt252", vec![]),
1367            upcast_fns: FromIterator::from_iter([
1368                bounded_int.extern_function_id("upcast"),
1369                integer.extern_function_id("u8_to_felt252"),
1370                integer.extern_function_id("u16_to_felt252"),
1371                integer.extern_function_id("u32_to_felt252"),
1372                integer.extern_function_id("u64_to_felt252"),
1373                integer.extern_function_id("u128_to_felt252"),
1374                integer.extern_function_id("i8_to_felt252"),
1375                integer.extern_function_id("i16_to_felt252"),
1376                integer.extern_function_id("i32_to_felt252"),
1377                integer.extern_function_id("i64_to_felt252"),
1378                integer.extern_function_id("i128_to_felt252"),
1379                class_hash_module.extern_function_id("class_hash_to_felt252"),
1380                contract_address_module.extern_function_id("contract_address_to_felt252"),
1381            ]),
1382            downcast_fns: FromIterator::from_iter([
1383                (bounded_int.extern_function_id("downcast"), false),
1384                (bounded_int.extern_function_id("bounded_int_trim_min"), true),
1385                (bounded_int.extern_function_id("bounded_int_trim_max"), true),
1386                (integer.extern_function_id("u8_try_from_felt252"), false),
1387                (integer.extern_function_id("u16_try_from_felt252"), false),
1388                (integer.extern_function_id("u32_try_from_felt252"), false),
1389                (integer.extern_function_id("u64_try_from_felt252"), false),
1390                (integer.extern_function_id("i8_try_from_felt252"), false),
1391                (integer.extern_function_id("i16_try_from_felt252"), false),
1392                (integer.extern_function_id("i32_try_from_felt252"), false),
1393                (integer.extern_function_id("i64_try_from_felt252"), false),
1394                (integer.extern_function_id("i128_try_from_felt252"), false),
1395                (class_hash_module.extern_function_id("class_hash_try_from_felt252"), false),
1396                (
1397                    contract_address_module.extern_function_id("contract_address_try_from_felt252"),
1398                    false,
1399                ),
1400            ]),
1401            u128s_from_felt252: integer.extern_function_id("u128s_from_felt252"),
1402            unwrap_non_zero: zeroable.extern_function_id("unwrap_non_zero"),
1403            nz_fns: FromIterator::from_iter([
1404                core.extern_function_id("felt252_is_zero"),
1405                bounded_int.extern_function_id("bounded_int_is_zero"),
1406                integer.extern_function_id("u8_is_zero"),
1407                integer.extern_function_id("u16_is_zero"),
1408                integer.extern_function_id("u32_is_zero"),
1409                integer.extern_function_id("u64_is_zero"),
1410                integer.extern_function_id("u128_is_zero"),
1411                integer.extern_function_id("u256_is_zero"),
1412            ]),
1413            type_value_ranges: FromIterator::from_iter([
1414                (core_info.u8, TypeRange::new(u8::MIN, u8::MAX)),
1415                (core_info.u16, TypeRange::new(u16::MIN, u16::MAX)),
1416                (core_info.u32, TypeRange::new(u32::MIN, u32::MAX)),
1417                (core_info.u64, TypeRange::new(u64::MIN, u64::MAX)),
1418                (core_info.u128, TypeRange::new(u128::MIN, u128::MAX)),
1419                (core_info.u256, TypeRange::new(BigInt::ZERO, (BigInt::from(1) << 256) - 1)),
1420                (core_info.i8, TypeRange::new(i8::MIN, i8::MAX)),
1421                (core_info.i16, TypeRange::new(i16::MIN, i16::MAX)),
1422                (core_info.i32, TypeRange::new(i32::MIN, i32::MAX)),
1423                (core_info.i64, TypeRange::new(i64::MIN, i64::MAX)),
1424                (core_info.i128, TypeRange::new(i128::MIN, i128::MAX)),
1425                // `ClassHash` and `ContractAddress` accept values in `[0, 2^251 - 1]`.
1426                (class_hash_ty, TypeRange::new(BigInt::ZERO, (BigInt::from(1) << 251) - 1)),
1427                (contract_address_ty, TypeRange::new(BigInt::ZERO, (BigInt::from(1) << 251) - 1)),
1428            ]),
1429            core_info,
1430        }
1431    }
1432}
1433
1434/// Trait for constant-related semantic queries.
1435pub trait ConstantSemantic<'db>: Database {
1436    /// Returns the semantic diagnostics of a constant definition.
1437    fn constant_semantic_diagnostics(
1438        &'db self,
1439        const_id: ConstantId<'db>,
1440    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
1441        let db = self.as_dyn_database();
1442        constant_semantic_data(db, const_id, false)
1443            .as_ref()
1444            .map(|data| data.diagnostics.clone())
1445            .unwrap_or_default()
1446    }
1447    /// Returns the semantic data of a constant definition.
1448    fn constant_semantic_data(&'db self, use_id: ConstantId<'db>) -> Maybe<Constant<'db>> {
1449        let db = self.as_dyn_database();
1450        constant_semantic_data(db, use_id, false).maybe_as_ref()?.constant.clone()
1451    }
1452    /// Returns the resolver data of a constant definition.
1453    fn constant_resolver_data(&'db self, use_id: ConstantId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
1454        let db = self.as_dyn_database();
1455        Ok(constant_semantic_data(db, use_id, false).maybe_as_ref()?.resolver_data.clone())
1456    }
1457    /// Returns the const value of a constant definition.
1458    fn constant_const_value(&'db self, const_id: ConstantId<'db>) -> Maybe<ConstValueId<'db>> {
1459        let db = self.as_dyn_database();
1460        Ok(constant_semantic_data(db, const_id, false).maybe_as_ref()?.const_value)
1461    }
1462    /// Returns information required for const calculations.
1463    fn const_calc_info(&'db self) -> Arc<ConstCalcInfo<'db>> {
1464        const_calc_info_tracked(self.as_dyn_database())
1465    }
1466}
1467impl<'db, T: Database + ?Sized> ConstantSemantic<'db> for T {}
1468
1469/// A range of values of a numeric type.
1470#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
1471pub struct TypeRange {
1472    /// The minimum value of the range.
1473    pub min: BigInt,
1474    /// The maximum value of the range.
1475    pub max: BigInt,
1476}
1477impl TypeRange {
1478    pub fn new(min: impl Into<BigInt>, max: impl Into<BigInt>) -> Self {
1479        Self { min: min.into(), max: max.into() }
1480    }
1481}