Skip to main content

hir_ty/
consteval.rs

1//! Constant evaluation details
2
3use base_db::Crate;
4use hir_def::{
5    ConstId, EnumVariantId, ExpressionStoreOwnerId, HasModule, StaticId,
6    attrs::AttrFlags,
7    expr_store::{Body, ExpressionStore, HygieneId, path::Path},
8    hir::{Expr, ExprId, Literal},
9    resolver::{Resolver, ValueNs},
10};
11use hir_expand::Lookup;
12use rustc_abi::Size;
13use rustc_apfloat::Float;
14use rustc_ast_ir::Mutability;
15use rustc_type_ir::inherent::{Const as _, GenericArgs as _, IntoKind, Ty as _};
16use salsa::SalsaValue;
17
18use crate::{
19    ParamEnvAndCrate, Span,
20    db::{AnonConstId, AnonConstLoc, GeneralConstId, HirDatabase},
21    generics::Generics,
22    lower::LoweringMode,
23    mir::{IsSigned, MirEvalError, MirLowerError, pad16},
24    next_solver::{
25        Allocation, Const, ConstKind, Consts, DbInterner, DefaultAny, GenericArgs, ParamConst,
26        ScalarInt, StoredAllocation, StoredEarlyBinder, StoredGenericArgs, Ty, TyKind,
27        UnevaluatedConst, ValTreeKind, default_types,
28    },
29    traits::StoredParamEnvAndCrate,
30};
31
32use super::mir::interpret_mir;
33
34#[derive(Debug, Clone, PartialEq, Eq, SalsaValue)]
35pub enum ConstEvalError<'db> {
36    MirLowerError(MirLowerError<'db>),
37    MirEvalError(MirEvalError<'db>),
38}
39
40impl<'db> From<MirLowerError<'db>> for ConstEvalError<'db> {
41    fn from(value: MirLowerError<'db>) -> Self {
42        match value {
43            MirLowerError::ConstEvalError(_, e) => *e,
44            _ => ConstEvalError::MirLowerError(value),
45        }
46    }
47}
48
49impl<'db> From<MirEvalError<'db>> for ConstEvalError<'db> {
50    fn from(value: MirEvalError<'db>) -> Self {
51        ConstEvalError::MirEvalError(value)
52    }
53}
54
55/// Interns a constant scalar with the given type
56fn intern_const_ref<'db>(
57    interner: DbInterner<'db>,
58    value: &Literal,
59    ty: Ty<'db>,
60) -> Option<Result<Const<'db>, CreateConstError<'db>>> {
61    let Ok(data_layout) = interner.db.target_data_layout(interner.expect_crate()) else {
62        return Some(Ok(Const::error(interner)));
63    };
64    let valtree = match (ty.kind(), value) {
65        (TyKind::Uint(uint), Literal::Uint(value, _)) => {
66            let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
67            let Some(scalar) = ScalarInt::try_from_uint(*value, size) else {
68                return Some(Ok(Const::error(interner)));
69            };
70            ValTreeKind::Leaf(scalar)
71        }
72        (TyKind::Uint(uint), Literal::Int(value, _)) => {
73            // `Literal::Int` is the default, so we also need to account for the type being uint.
74            let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
75            let Some(scalar) = ScalarInt::try_from_uint(*value as u128, size) else {
76                return Some(Ok(Const::error(interner)));
77            };
78            ValTreeKind::Leaf(scalar)
79        }
80        (TyKind::Int(int), Literal::Int(value, _)) => {
81            let size = int.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
82            let Some(scalar) = ScalarInt::try_from_int(*value, size) else {
83                return Some(Ok(Const::error(interner)));
84            };
85            ValTreeKind::Leaf(scalar)
86        }
87        (TyKind::Bool, Literal::Bool(value)) => ValTreeKind::Leaf(ScalarInt::from(*value)),
88        (TyKind::Char, Literal::Char(value)) => ValTreeKind::Leaf(ScalarInt::from(*value)),
89        (TyKind::Float(float), Literal::Float(value, _)) => {
90            let size = Size::from_bits(float.bit_width());
91            let value = match float {
92                rustc_ast_ir::FloatTy::F16 => value.to_f16().to_bits(),
93                rustc_ast_ir::FloatTy::F32 => value.to_f32().to_bits(),
94                rustc_ast_ir::FloatTy::F64 => value.to_f64().to_bits(),
95                rustc_ast_ir::FloatTy::F128 => value.to_f128().to_bits(),
96            };
97            let scalar = ScalarInt::try_from_uint(value, size).unwrap();
98            ValTreeKind::Leaf(scalar)
99        }
100        (TyKind::Ref(_, inner_ty, _), Literal::String(value))
101            if matches!(inner_ty.kind(), TyKind::Str) =>
102        {
103            let u8_values = &interner.default_types().consts.u8_values;
104            ValTreeKind::Branch(Consts::new_from_iter(
105                value.as_str().as_bytes().iter().map(|&byte| u8_values[usize::from(byte)]),
106            ))
107        }
108        (_, Literal::ByteString(_) | Literal::CString(_)) => {
109            // This literals are complicated to construct and/or are possible to coerce.
110            // So we just allocate an anon const for them, they should be rare so it's not a problem.
111            return None;
112        }
113        _ => {
114            let actual = literal_ty(
115                interner,
116                value,
117                |types| types.types.i32,
118                |types| types.types.u32,
119                |types| types.types.f64,
120            );
121            return Some(Err(CreateConstError::TypeMismatch { actual }));
122        }
123    };
124    Some(Ok(Const::new_valtree(interner, ty, valtree)))
125}
126
127pub(crate) fn literal_ty<'db>(
128    interner: DbInterner<'db>,
129    value: &Literal,
130    default_int: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
131    default_uint: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
132    default_float: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
133) -> Ty<'db> {
134    let types = interner.default_types();
135    match value {
136        Literal::Bool(..) => types.types.bool,
137        Literal::String(..) => types.types.static_str_ref,
138        Literal::ByteString(bs) => {
139            let byte_type = types.types.u8;
140            let array_type = Ty::new_array(interner, byte_type, bs.len() as u64);
141            Ty::new_ref(interner, types.regions.statik, array_type, Mutability::Not)
142        }
143        Literal::CString(..) => Ty::new_ref(
144            interner,
145            types.regions.statik,
146            interner.lang_items().CStr.map_or(types.types.error, |strukt| {
147                Ty::new_adt(interner, strukt.into(), types.empty.generic_args)
148            }),
149            Mutability::Not,
150        ),
151        Literal::Char(..) => types.types.char,
152        Literal::Int(_v, ty) => match ty {
153            Some(int_ty) => match int_ty {
154                hir_def::builtin_type::BuiltinInt::Isize => types.types.isize,
155                hir_def::builtin_type::BuiltinInt::I8 => types.types.i8,
156                hir_def::builtin_type::BuiltinInt::I16 => types.types.i16,
157                hir_def::builtin_type::BuiltinInt::I32 => types.types.i32,
158                hir_def::builtin_type::BuiltinInt::I64 => types.types.i64,
159                hir_def::builtin_type::BuiltinInt::I128 => types.types.i128,
160            },
161            None => default_int(types),
162        },
163        Literal::Uint(_v, ty) => match ty {
164            Some(int_ty) => match int_ty {
165                hir_def::builtin_type::BuiltinUint::Usize => types.types.usize,
166                hir_def::builtin_type::BuiltinUint::U8 => types.types.u8,
167                hir_def::builtin_type::BuiltinUint::U16 => types.types.u16,
168                hir_def::builtin_type::BuiltinUint::U32 => types.types.u32,
169                hir_def::builtin_type::BuiltinUint::U64 => types.types.u64,
170                hir_def::builtin_type::BuiltinUint::U128 => types.types.u128,
171            },
172            None => default_uint(types),
173        },
174        Literal::Float(_v, ty) => match ty {
175            Some(float_ty) => match float_ty {
176                hir_def::builtin_type::BuiltinFloat::F16 => types.types.f16,
177                hir_def::builtin_type::BuiltinFloat::F32 => types.types.f32,
178                hir_def::builtin_type::BuiltinFloat::F64 => types.types.f64,
179                hir_def::builtin_type::BuiltinFloat::F128 => types.types.f128,
180            },
181            None => default_float(types),
182        },
183    }
184}
185
186/// Interns a possibly-unknown target usize
187pub fn usize_const<'db>(db: &'db dyn HirDatabase, value: Option<u128>, krate: Crate) -> Const<'db> {
188    let interner = DbInterner::new_no_crate(db);
189    let value = match value {
190        Some(value) => value,
191        None => {
192            return Const::error(interner);
193        }
194    };
195    let Ok(data_layout) = db.target_data_layout(krate) else {
196        return Const::error(interner);
197    };
198    let usize_ty = interner.default_types().types.usize;
199    let Some(scalar) = ScalarInt::try_from_uint(value, data_layout.pointer_size()) else {
200        return Const::error(interner);
201    };
202    Const::new_valtree(interner, usize_ty, ValTreeKind::Leaf(scalar))
203}
204
205pub fn allocation_as_usize(ec: Allocation<'_>) -> u128 {
206    u128::from_le_bytes(pad16(&ec.memory, IsSigned::No))
207}
208
209pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<u128> {
210    match c.kind() {
211        ConstKind::Param(_) => None,
212        ConstKind::Infer(_) => None,
213        ConstKind::Bound(_, _) => None,
214        ConstKind::Placeholder(_) => None,
215        ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
216            GeneralConstId::ConstId(id) => {
217                let subst = unevaluated_const.args;
218                let ec = db.const_eval(id, subst, None).ok()?;
219                Some(allocation_as_usize(ec))
220            }
221            GeneralConstId::StaticId(id) => {
222                let ec = db.const_eval_static(id).ok()?;
223                Some(allocation_as_usize(ec))
224            }
225            GeneralConstId::AnonConstId(id) => {
226                let subst = unevaluated_const.args;
227                let ec = db.anon_const_eval(id, subst, None).ok()?;
228                Some(allocation_as_usize(ec))
229            }
230        },
231        ConstKind::Value(val) => {
232            if val.ty == default_types().types.usize {
233                Some(val.value.inner().to_leaf().to_uint_unchecked())
234            } else {
235                None
236            }
237        }
238        ConstKind::Error(_) => None,
239        ConstKind::Expr(_) => None,
240    }
241}
242
243pub fn allocation_as_isize(ec: Allocation<'_>) -> i128 {
244    i128::from_le_bytes(pad16(&ec.memory, IsSigned::Yes))
245}
246
247pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<i128> {
248    match c.kind() {
249        ConstKind::Param(_) => None,
250        ConstKind::Infer(_) => None,
251        ConstKind::Bound(_, _) => None,
252        ConstKind::Placeholder(_) => None,
253        ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
254            GeneralConstId::ConstId(id) => {
255                let subst = unevaluated_const.args;
256                let ec = db.const_eval(id, subst, None).ok()?;
257                Some(allocation_as_isize(ec))
258            }
259            GeneralConstId::StaticId(id) => {
260                let ec = db.const_eval_static(id).ok()?;
261                Some(allocation_as_isize(ec))
262            }
263            GeneralConstId::AnonConstId(id) => {
264                let subst = unevaluated_const.args;
265                let ec = db.anon_const_eval(id, subst, None).ok()?;
266                Some(allocation_as_isize(ec))
267            }
268        },
269        ConstKind::Value(val) => {
270            if val.ty == default_types().types.isize {
271                Some(val.value.inner().to_leaf().to_int_unchecked())
272            } else {
273                None
274            }
275        }
276        ConstKind::Error(_) => None,
277        ConstKind::Expr(_) => None,
278    }
279}
280
281#[derive(Debug)]
282pub(crate) enum CreateConstError<'db> {
283    UsedForbiddenParam,
284    ResolveToNonConst,
285    DoesNotResolve,
286    ConstHasGenerics,
287    UnderscoreExpr,
288    AnonConstInterningDisabled,
289    TypeMismatch {
290        #[expect(unused, reason = "will need this for diagnostics")]
291        actual: Ty<'db>,
292    },
293}
294
295pub(crate) fn path_to_const<'a, 'db>(
296    db: &'db dyn HirDatabase,
297    resolver: &Resolver<'db>,
298    generics: &dyn Fn() -> &'a Generics<'db>,
299    forbid_params_after: Option<u32>,
300    path: &Path,
301) -> Result<Const<'db>, CreateConstError<'db>> {
302    let interner = DbInterner::new_no_crate(db);
303    let resolution = resolver
304        .resolve_path_in_value_ns_fully(db, path, HygieneId::ROOT)
305        .ok_or(CreateConstError::DoesNotResolve)?;
306    let no_generics = |def| crate::generics::generics(db, def).has_no_params();
307    let konst = match resolution {
308        ValueNs::ConstId(id) if no_generics(id.into()) => GeneralConstId::ConstId(id),
309        ValueNs::StaticId(id) => GeneralConstId::StaticId(id),
310        ValueNs::ConstId(_) => return Err(CreateConstError::ConstHasGenerics),
311        ValueNs::GenericParam(param) => {
312            let index = generics().type_or_const_param_idx(param.into());
313            if forbid_params_after.is_some_and(|forbid_after| index >= forbid_after) {
314                return Err(CreateConstError::UsedForbiddenParam);
315            }
316            return Ok(Const::new_param(interner, ParamConst { id: param, index }));
317        }
318        // These are not valid as consts.
319        // FIXME: Report an error?
320        ValueNs::ImplSelf(_)
321        | ValueNs::LocalBinding(_)
322        | ValueNs::FunctionId(_)
323        | ValueNs::StructId(_)
324        | ValueNs::EnumVariantId(_) => return Err(CreateConstError::ResolveToNonConst),
325    };
326    let args = GenericArgs::empty();
327    Ok(Const::new_unevaluated(interner, UnevaluatedConst { def: konst.into(), args }))
328}
329
330/// `forbid_params_after` and `allow_anon_const_using_generic_params` fill a similar role, but are not the same.
331///
332/// `forbid_params_after` is only respected if an anon const is *not* created, that is, we directly resolve to a const param.
333/// `allow_anon_const_using_generic_params` is only respected if an anon const *is* created.
334///
335/// For example, inside types, you are allowed to refer to const params in bare paths (but might be forbidden from referring
336/// to *some*, in param defaults), but not allowed to do so in anon consts (e.g. `{ {}; N }` is forbidden, because it creates
337/// an anon const). Inside field defaults, you are allowed to refer to them even inside anon consts.
338///
339/// There's also `generic_const_exprs`, `min_generic_const_args`, and `generic_const_args`, all of which we don't implement yet.
340pub(crate) fn create_anon_const<'a, 'db>(
341    interner: DbInterner<'db>,
342    owner: ExpressionStoreOwnerId,
343    store: &ExpressionStore,
344    expr_id: ExprId,
345    resolver: &Resolver<'db>,
346    expected_ty: Ty<'db>,
347    generics: &dyn Fn() -> &'a Generics<'db>,
348    create_var: Option<&mut dyn FnMut(Span) -> Const<'db>>,
349    lowering_mode: LoweringMode,
350    forbid_params_after: Option<u32>,
351    allow_anon_const_using_generic_params: bool,
352) -> Result<Const<'db>, CreateConstError<'db>> {
353    let mut expr = &store[expr_id];
354    if let Expr::Block { statements, tail: Some(tail), .. } = expr
355        && statements.is_empty()
356    {
357        // rustc unwraps *one* layer of blocks, so we do too (this impacts whether the const can use generic parameters.
358        // Anon consts sometimes cannot while bare paths can). mGCA allows arbitrarily many blocks, but we don't implement
359        // it yet.
360        expr = &store[*tail];
361    }
362    match expr {
363        Expr::Literal(literal)
364            if let Some(literal) = intern_const_ref(interner, literal, expected_ty) =>
365        {
366            literal
367        }
368        Expr::Underscore => match create_var {
369            Some(create_var) => Ok(create_var(expr_id.into())),
370            None => Err(CreateConstError::UnderscoreExpr),
371        },
372        Expr::Path(path)
373            if let konst =
374                path_to_const(interner.db, resolver, generics, forbid_params_after, path)
375                && !matches!(
376                    konst,
377                    Err(CreateConstError::DoesNotResolve | CreateConstError::ConstHasGenerics)
378                ) =>
379        {
380            konst
381        }
382        _ => {
383            let Some(token) = lowering_mode.allow_tracked_structs() else {
384                return Err(CreateConstError::AnonConstInterningDisabled);
385            };
386
387            let konst = AnonConstId::new(
388                interner.db,
389                AnonConstLoc {
390                    owner,
391                    expr: expr_id,
392                    ty: StoredEarlyBinder::bind(expected_ty.store()),
393                    allow_using_generic_params: allow_anon_const_using_generic_params,
394                },
395                token,
396            );
397            let args = if allow_anon_const_using_generic_params {
398                GenericArgs::identity_for_item(interner, owner.generic_def(interner.db).into())
399            } else {
400                GenericArgs::empty()
401            };
402            Ok(Const::new_unevaluated(
403                interner,
404                UnevaluatedConst { def: GeneralConstId::AnonConstId(konst).into(), args },
405            ))
406        }
407    }
408}
409
410#[salsa::tracked(cycle_result = const_eval_discriminant_cycle_result, returns(clone))]
411pub(crate) fn const_eval_discriminant_variant<'db>(
412    db: &'db dyn HirDatabase,
413    variant_id: EnumVariantId,
414) -> Result<i128, ConstEvalError<'db>> {
415    let def = variant_id.into();
416    let body = Body::of(db, def);
417    let loc = variant_id.lookup(db);
418    if matches!(body[body.root_expr()], Expr::Missing) {
419        let prev_idx = loc.index(db).checked_sub(1);
420        let value = match prev_idx {
421            Some(prev_idx) => {
422                1 + db.const_eval_discriminant(loc.parent.enum_variants(db).variants[prev_idx].0)?
423            }
424            _ => 0,
425        };
426        return Ok(value);
427    }
428
429    let repr = AttrFlags::repr(db, loc.parent.into());
430    let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
431
432    let mir_body = db.monomorphized_mir_body(
433        def.into(),
434        GenericArgs::empty().store(),
435        ParamEnvAndCrate {
436            param_env: db.trait_environment(def.generic_def(db)),
437            krate: def.krate(db),
438        }
439        .store(),
440    )?;
441    let c = interpret_mir(db, mir_body, false, None)?.0?;
442    let c = if is_signed { allocation_as_isize(c) } else { allocation_as_usize(c) as i128 };
443    Ok(c)
444}
445
446fn const_eval_discriminant_cycle_result<'db>(
447    _: &'db dyn HirDatabase,
448    _: salsa::Id,
449    _: EnumVariantId,
450) -> Result<i128, ConstEvalError<'db>> {
451    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
452}
453
454pub(crate) fn const_eval<'db>(
455    db: &'db dyn HirDatabase,
456    def: ConstId,
457    subst: GenericArgs<'db>,
458    trait_env: Option<ParamEnvAndCrate<'db>>,
459) -> Result<Allocation<'db>, ConstEvalError<'db>> {
460    return match const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) {
461        Ok(konst) => Ok(konst.as_ref()),
462        Err(err) => Err(err.clone()),
463    };
464
465    #[salsa::tracked(returns(ref), cycle_result = const_eval_cycle_result)]
466    pub(crate) fn const_eval_query<'db>(
467        db: &'db dyn HirDatabase,
468        def: ConstId,
469        subst: StoredGenericArgs,
470        trait_env: Option<StoredParamEnvAndCrate>,
471    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
472        let body = db.monomorphized_mir_body(
473            def.into(),
474            subst,
475            ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) }
476                .store(),
477        )?;
478        let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref(db)))?.0?;
479        Ok(c.store())
480    }
481
482    pub(crate) fn const_eval_cycle_result<'db>(
483        _: &'db dyn HirDatabase,
484        _: salsa::Id,
485        _: ConstId,
486        _: StoredGenericArgs,
487        _: Option<StoredParamEnvAndCrate>,
488    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
489        Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
490    }
491}
492
493pub(crate) fn anon_const_eval<'db>(
494    db: &'db dyn HirDatabase,
495    def: AnonConstId<'db>,
496    subst: GenericArgs<'db>,
497    trait_env: Option<ParamEnvAndCrate<'db>>,
498) -> Result<Allocation<'db>, ConstEvalError<'db>> {
499    return match anon_const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) {
500        Ok(konst) => Ok(konst.as_ref()),
501        Err(err) => Err(err.clone()),
502    };
503
504    #[salsa::tracked(returns(ref), cycle_result = anon_const_eval_cycle_result)]
505    pub(crate) fn anon_const_eval_query<'db>(
506        db: &'db dyn HirDatabase,
507        def: AnonConstId<'db>,
508        subst: StoredGenericArgs,
509        trait_env: Option<StoredParamEnvAndCrate>,
510    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
511        let body = db.monomorphized_mir_body(
512            def.into(),
513            subst,
514            ParamEnvAndCrate {
515                param_env: db.trait_environment(def.loc(db).owner.generic_def(db)),
516                krate: def.krate(db),
517            }
518            .store(),
519        )?;
520        let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref(db)))?.0?;
521        Ok(c.store())
522    }
523
524    pub(crate) fn anon_const_eval_cycle_result<'db>(
525        _: &'db dyn HirDatabase,
526        _: salsa::Id,
527        _: AnonConstId<'db>,
528        _: StoredGenericArgs,
529        _: Option<StoredParamEnvAndCrate>,
530    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
531        Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
532    }
533}
534
535pub(crate) fn const_eval_static<'db>(
536    db: &'db dyn HirDatabase,
537    def: StaticId,
538) -> Result<Allocation<'db>, ConstEvalError<'db>> {
539    return match const_eval_static_query(db, def) {
540        Ok(konst) => Ok(konst.as_ref()),
541        Err(err) => Err(err.clone()),
542    };
543
544    #[salsa::tracked(returns(ref), cycle_result = const_eval_static_cycle_result)]
545    pub(crate) fn const_eval_static_query<'db>(
546        db: &'db dyn HirDatabase,
547        def: StaticId,
548    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
549        let body = db.monomorphized_mir_body(
550            def.into(),
551            GenericArgs::empty().store(),
552            ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) }
553                .store(),
554        )?;
555        let c = interpret_mir(db, body, false, None)?.0?;
556        Ok(c.store())
557    }
558
559    pub(crate) fn const_eval_static_cycle_result<'db>(
560        _: &'db dyn HirDatabase,
561        _: salsa::Id,
562        _: StaticId,
563    ) -> Result<StoredAllocation, ConstEvalError<'db>> {
564        Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
565    }
566}