Skip to main content

hir_ty/
consteval.rs

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