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