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, GeneralConstId, HasModule, StaticId,
9    attrs::AttrFlags,
10    expr_store::Body,
11    hir::{Expr, ExprId},
12    type_ref::LiteralConstRef,
13};
14use hir_expand::Lookup;
15use rustc_type_ir::inherent::IntoKind;
16use triomphe::Arc;
17
18use crate::{
19    LifetimeElisionKind, MemoryMap, ParamEnvAndCrate, TyLoweringContext,
20    db::HirDatabase,
21    display::DisplayTarget,
22    infer::InferenceContext,
23    mir::{MirEvalError, MirLowerError},
24    next_solver::{
25        Const, ConstBytes, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs,
26        ParamEnv, Ty, ValueConst,
27    },
28};
29
30use super::mir::{interpret_mir, lower_to_mir, pad16};
31
32pub fn unknown_const<'db>(_ty: Ty<'db>) -> Const<'db> {
33    Const::new(DbInterner::conjure(), rustc_type_ir::ConstKind::Error(ErrorGuaranteed))
34}
35
36pub fn unknown_const_as_generic<'db>(ty: Ty<'db>) -> GenericArg<'db> {
37    unknown_const(ty).into()
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ConstEvalError<'db> {
42    MirLowerError(MirLowerError<'db>),
43    MirEvalError(MirEvalError<'db>),
44}
45
46impl ConstEvalError<'_> {
47    pub fn pretty_print(
48        &self,
49        f: &mut String,
50        db: &dyn HirDatabase,
51        span_formatter: impl Fn(span::FileId, span::TextRange) -> String,
52        display_target: DisplayTarget,
53    ) -> std::result::Result<(), std::fmt::Error> {
54        match self {
55            ConstEvalError::MirLowerError(e) => {
56                e.pretty_print(f, db, span_formatter, display_target)
57            }
58            ConstEvalError::MirEvalError(e) => {
59                e.pretty_print(f, db, span_formatter, display_target)
60            }
61        }
62    }
63}
64
65impl<'db> From<MirLowerError<'db>> for ConstEvalError<'db> {
66    fn from(value: MirLowerError<'db>) -> Self {
67        match value {
68            MirLowerError::ConstEvalError(_, e) => *e,
69            _ => ConstEvalError::MirLowerError(value),
70        }
71    }
72}
73
74impl<'db> From<MirEvalError<'db>> for ConstEvalError<'db> {
75    fn from(value: MirEvalError<'db>) -> Self {
76        ConstEvalError::MirEvalError(value)
77    }
78}
79
80/// Interns a constant scalar with the given type
81pub fn intern_const_ref<'a>(
82    db: &'a dyn HirDatabase,
83    value: &LiteralConstRef,
84    ty: Ty<'a>,
85    krate: Crate,
86) -> Const<'a> {
87    let interner = DbInterner::new_no_crate(db);
88    let layout = db.layout_of_ty(ty, ParamEnvAndCrate { param_env: ParamEnv::empty(), krate });
89    let kind = match value {
90        LiteralConstRef::Int(i) => {
91            // FIXME: We should handle failure of layout better.
92            let size = layout.map(|it| it.size.bytes_usize()).unwrap_or(16);
93            rustc_type_ir::ConstKind::Value(ValueConst::new(
94                ty,
95                ConstBytes {
96                    memory: i.to_le_bytes()[0..size].into(),
97                    memory_map: MemoryMap::default(),
98                },
99            ))
100        }
101        LiteralConstRef::UInt(i) => {
102            let size = layout.map(|it| it.size.bytes_usize()).unwrap_or(16);
103            rustc_type_ir::ConstKind::Value(ValueConst::new(
104                ty,
105                ConstBytes {
106                    memory: i.to_le_bytes()[0..size].into(),
107                    memory_map: MemoryMap::default(),
108                },
109            ))
110        }
111        LiteralConstRef::Bool(b) => rustc_type_ir::ConstKind::Value(ValueConst::new(
112            ty,
113            ConstBytes { memory: Box::new([*b as u8]), memory_map: MemoryMap::default() },
114        )),
115        LiteralConstRef::Char(c) => rustc_type_ir::ConstKind::Value(ValueConst::new(
116            ty,
117            ConstBytes {
118                memory: (*c as u32).to_le_bytes().into(),
119                memory_map: MemoryMap::default(),
120            },
121        )),
122        LiteralConstRef::Unknown => rustc_type_ir::ConstKind::Error(ErrorGuaranteed),
123    };
124    Const::new(interner, kind)
125}
126
127/// Interns a possibly-unknown target usize
128pub fn usize_const<'db>(db: &'db dyn HirDatabase, value: Option<u128>, krate: Crate) -> Const<'db> {
129    intern_const_ref(
130        db,
131        &value.map_or(LiteralConstRef::Unknown, LiteralConstRef::UInt),
132        Ty::new_uint(DbInterner::new_no_crate(db), rustc_type_ir::UintTy::Usize),
133        krate,
134    )
135}
136
137pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<u128> {
138    match c.kind() {
139        ConstKind::Param(_) => None,
140        ConstKind::Infer(_) => None,
141        ConstKind::Bound(_, _) => None,
142        ConstKind::Placeholder(_) => None,
143        ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
144            GeneralConstId::ConstId(id) => {
145                let subst = unevaluated_const.args;
146                let ec = db.const_eval(id, subst, None).ok()?;
147                try_const_usize(db, ec)
148            }
149            GeneralConstId::StaticId(id) => {
150                let ec = db.const_eval_static(id).ok()?;
151                try_const_usize(db, ec)
152            }
153        },
154        ConstKind::Value(val) => Some(u128::from_le_bytes(pad16(&val.value.inner().memory, false))),
155        ConstKind::Error(_) => None,
156        ConstKind::Expr(_) => None,
157    }
158}
159
160pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: &Const<'db>) -> Option<i128> {
161    match (*c).kind() {
162        ConstKind::Param(_) => None,
163        ConstKind::Infer(_) => None,
164        ConstKind::Bound(_, _) => None,
165        ConstKind::Placeholder(_) => None,
166        ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
167            GeneralConstId::ConstId(id) => {
168                let subst = unevaluated_const.args;
169                let ec = db.const_eval(id, subst, None).ok()?;
170                try_const_isize(db, &ec)
171            }
172            GeneralConstId::StaticId(id) => {
173                let ec = db.const_eval_static(id).ok()?;
174                try_const_isize(db, &ec)
175            }
176        },
177        ConstKind::Value(val) => Some(i128::from_le_bytes(pad16(&val.value.inner().memory, true))),
178        ConstKind::Error(_) => None,
179        ConstKind::Expr(_) => None,
180    }
181}
182
183pub(crate) fn const_eval_discriminant_variant<'db>(
184    db: &'db dyn HirDatabase,
185    variant_id: EnumVariantId,
186) -> Result<i128, ConstEvalError<'db>> {
187    let interner = DbInterner::new_no_crate(db);
188    let def = variant_id.into();
189    let body = db.body(def);
190    let loc = variant_id.lookup(db);
191    if matches!(body[body.body_expr], Expr::Missing) {
192        let prev_idx = loc.index.checked_sub(1);
193        let value = match prev_idx {
194            Some(prev_idx) => {
195                1 + db.const_eval_discriminant(
196                    loc.parent.enum_variants(db).variants[prev_idx as usize].0,
197                )?
198            }
199            _ => 0,
200        };
201        return Ok(value);
202    }
203
204    let repr = AttrFlags::repr(db, loc.parent.into());
205    let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
206
207    let mir_body = db.monomorphized_mir_body(
208        def,
209        GenericArgs::new_from_iter(interner, []),
210        ParamEnvAndCrate { param_env: db.trait_environment_for_body(def), krate: def.krate(db) },
211    )?;
212    let c = interpret_mir(db, mir_body, false, None)?.0?;
213    let c = if is_signed {
214        try_const_isize(db, &c).unwrap()
215    } else {
216        try_const_usize(db, c).unwrap() as i128
217    };
218    Ok(c)
219}
220
221// FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should
222// get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here
223// and make this function private. See the fixme comment on `InferenceContext::resolve_all`.
224pub(crate) fn eval_to_const<'db>(expr: ExprId, ctx: &mut InferenceContext<'_, 'db>) -> Const<'db> {
225    let infer = ctx.fixme_resolve_all_clone();
226    fn has_closure(body: &Body, expr: ExprId) -> bool {
227        if matches!(body[expr], Expr::Closure { .. }) {
228            return true;
229        }
230        let mut r = false;
231        body.walk_child_exprs(expr, |idx| r |= has_closure(body, idx));
232        r
233    }
234    if has_closure(ctx.body, expr) {
235        // Type checking clousres need an isolated body (See the above FIXME). Bail out early to prevent panic.
236        return unknown_const(infer[expr]);
237    }
238    if let Expr::Path(p) = &ctx.body[expr] {
239        let mut ctx = TyLoweringContext::new(
240            ctx.db,
241            &ctx.resolver,
242            ctx.body,
243            ctx.generic_def,
244            LifetimeElisionKind::Infer,
245        );
246        if let Some(c) = ctx.path_to_const(p) {
247            return c;
248        }
249    }
250    if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr)
251        && let Ok((Ok(result), _)) = interpret_mir(ctx.db, Arc::new(mir_body), true, None)
252    {
253        return result;
254    }
255    unknown_const(infer[expr])
256}
257
258pub(crate) fn const_eval_cycle_result<'db>(
259    _: &'db dyn HirDatabase,
260    _: ConstId,
261    _: GenericArgs<'db>,
262    _: Option<ParamEnvAndCrate<'db>>,
263) -> Result<Const<'db>, ConstEvalError<'db>> {
264    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
265}
266
267pub(crate) fn const_eval_static_cycle_result<'db>(
268    _: &'db dyn HirDatabase,
269    _: StaticId,
270) -> Result<Const<'db>, ConstEvalError<'db>> {
271    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
272}
273
274pub(crate) fn const_eval_discriminant_cycle_result<'db>(
275    _: &'db dyn HirDatabase,
276    _: EnumVariantId,
277) -> Result<i128, ConstEvalError<'db>> {
278    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
279}
280
281pub(crate) fn const_eval_query<'db>(
282    db: &'db dyn HirDatabase,
283    def: ConstId,
284    subst: GenericArgs<'db>,
285    trait_env: Option<ParamEnvAndCrate<'db>>,
286) -> Result<Const<'db>, ConstEvalError<'db>> {
287    let body = db.monomorphized_mir_body(
288        def.into(),
289        subst,
290        ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) },
291    )?;
292    let c = interpret_mir(db, body, false, trait_env)?.0?;
293    Ok(c)
294}
295
296pub(crate) fn const_eval_static_query<'db>(
297    db: &'db dyn HirDatabase,
298    def: StaticId,
299) -> Result<Const<'db>, ConstEvalError<'db>> {
300    let interner = DbInterner::new_no_crate(db);
301    let body = db.monomorphized_mir_body(
302        def.into(),
303        GenericArgs::new_from_iter(interner, []),
304        ParamEnvAndCrate {
305            param_env: db.trait_environment_for_body(def.into()),
306            krate: def.krate(db),
307        },
308    )?;
309    let c = interpret_mir(db, body, false, None)?.0?;
310    Ok(c)
311}