Skip to main content

ra_ap_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, GeneralConstId, GenericDefId, HasModule,
9    StaticId,
10    attrs::AttrFlags,
11    builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
12    expr_store::{Body, ExpressionStore},
13    hir::{Expr, ExprId, Literal},
14};
15use hir_expand::Lookup;
16use rustc_type_ir::inherent::IntoKind;
17use triomphe::Arc;
18
19use crate::{
20    LifetimeElisionKind, MemoryMap, ParamEnvAndCrate, TyLoweringContext,
21    db::HirDatabase,
22    display::DisplayTarget,
23    infer::InferenceContext,
24    mir::{MirEvalError, MirLowerError},
25    next_solver::{
26        Const, ConstBytes, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs,
27        StoredConst, StoredGenericArgs, Ty, ValueConst,
28    },
29    traits::StoredParamEnvAndCrate,
30};
31
32use super::mir::{interpret_mir, lower_body_to_mir, pad16};
33
34pub fn unknown_const<'db>(_ty: Ty<'db>) -> Const<'db> {
35    Const::new(DbInterner::conjure(), rustc_type_ir::ConstKind::Error(ErrorGuaranteed))
36}
37
38pub fn unknown_const_as_generic<'db>(ty: Ty<'db>) -> GenericArg<'db> {
39    unknown_const(ty).into()
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum ConstEvalError {
44    MirLowerError(MirLowerError),
45    MirEvalError(MirEvalError),
46}
47
48impl ConstEvalError {
49    pub fn pretty_print(
50        &self,
51        f: &mut String,
52        db: &dyn HirDatabase,
53        span_formatter: impl Fn(span::FileId, span::TextRange) -> String,
54        display_target: DisplayTarget,
55    ) -> std::result::Result<(), std::fmt::Error> {
56        match self {
57            ConstEvalError::MirLowerError(e) => {
58                e.pretty_print(f, db, span_formatter, display_target)
59            }
60            ConstEvalError::MirEvalError(e) => {
61                e.pretty_print(f, db, span_formatter, display_target)
62            }
63        }
64    }
65}
66
67impl From<MirLowerError> for ConstEvalError {
68    fn from(value: MirLowerError) -> Self {
69        match value {
70            MirLowerError::ConstEvalError(_, e) => *e,
71            _ => ConstEvalError::MirLowerError(value),
72        }
73    }
74}
75
76impl From<MirEvalError> for ConstEvalError {
77    fn from(value: MirEvalError) -> Self {
78        ConstEvalError::MirEvalError(value)
79    }
80}
81
82/// Interns a constant scalar with the given type
83pub fn intern_const_ref<'a>(
84    db: &'a dyn HirDatabase,
85    value: &Literal,
86    ty: Ty<'a>,
87    _krate: Crate,
88) -> Const<'a> {
89    let interner = DbInterner::new_no_crate(db);
90    let kind = match value {
91        &Literal::Uint(i, builtin_ty)
92            if builtin_ty.is_none() || ty.as_builtin() == builtin_ty.map(BuiltinType::Uint) =>
93        {
94            let memory = match ty.as_builtin() {
95                Some(BuiltinType::Uint(builtin_uint)) => match builtin_uint {
96                    BuiltinUint::U8 => Box::new([i as u8]) as Box<[u8]>,
97                    BuiltinUint::U16 => Box::new((i as u16).to_le_bytes()),
98                    BuiltinUint::U32 => Box::new((i as u32).to_le_bytes()),
99                    BuiltinUint::U64 => Box::new((i as u64).to_le_bytes()),
100                    BuiltinUint::U128 => Box::new((i).to_le_bytes()),
101                    BuiltinUint::Usize => Box::new((i as usize).to_le_bytes()),
102                },
103                _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)),
104            };
105            rustc_type_ir::ConstKind::Value(ValueConst::new(
106                ty,
107                ConstBytes { memory, memory_map: MemoryMap::default() },
108            ))
109        }
110        &Literal::Int(i, None)
111            if ty
112                .as_builtin()
113                .is_some_and(|builtin_ty| matches!(builtin_ty, BuiltinType::Uint(_))) =>
114        {
115            let memory = match ty.as_builtin() {
116                Some(BuiltinType::Uint(builtin_uint)) => match builtin_uint {
117                    BuiltinUint::U8 => Box::new([i as u8]) as Box<[u8]>,
118                    BuiltinUint::U16 => Box::new((i as u16).to_le_bytes()),
119                    BuiltinUint::U32 => Box::new((i as u32).to_le_bytes()),
120                    BuiltinUint::U64 => Box::new((i as u64).to_le_bytes()),
121                    BuiltinUint::U128 => Box::new((i as u128).to_le_bytes()),
122                    BuiltinUint::Usize => Box::new((i as usize).to_le_bytes()),
123                },
124                _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)),
125            };
126            rustc_type_ir::ConstKind::Value(ValueConst::new(
127                ty,
128                ConstBytes { memory, memory_map: MemoryMap::default() },
129            ))
130        }
131        &Literal::Int(i, builtin_ty)
132            if builtin_ty.is_none() || ty.as_builtin() == builtin_ty.map(BuiltinType::Int) =>
133        {
134            let memory = match ty.as_builtin() {
135                Some(BuiltinType::Int(builtin_int)) => match builtin_int {
136                    BuiltinInt::I8 => Box::new([i as u8]) as Box<[u8]>,
137                    BuiltinInt::I16 => Box::new((i as i16).to_le_bytes()),
138                    BuiltinInt::I32 => Box::new((i as i32).to_le_bytes()),
139                    BuiltinInt::I64 => Box::new((i as i64).to_le_bytes()),
140                    BuiltinInt::I128 => Box::new((i).to_le_bytes()),
141                    BuiltinInt::Isize => Box::new((i as isize).to_le_bytes()),
142                },
143                _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)),
144            };
145            rustc_type_ir::ConstKind::Value(ValueConst::new(
146                ty,
147                ConstBytes { memory, memory_map: MemoryMap::default() },
148            ))
149        }
150        Literal::Float(float_type_wrapper, builtin_float)
151            if builtin_float.is_none()
152                || ty.as_builtin() == builtin_float.map(BuiltinType::Float) =>
153        {
154            let memory = match ty.as_builtin().unwrap() {
155                BuiltinType::Float(builtin_float) => match builtin_float {
156                    // FIXME:
157                    hir_def::builtin_type::BuiltinFloat::F16 => Box::new([0u8; 2]) as Box<[u8]>,
158                    hir_def::builtin_type::BuiltinFloat::F32 => {
159                        Box::new(float_type_wrapper.to_f32().to_le_bytes())
160                    }
161                    hir_def::builtin_type::BuiltinFloat::F64 => {
162                        Box::new(float_type_wrapper.to_f64().to_le_bytes())
163                    }
164                    // FIXME:
165                    hir_def::builtin_type::BuiltinFloat::F128 => Box::new([0; 16]),
166                },
167                _ => unreachable!(),
168            };
169            rustc_type_ir::ConstKind::Value(ValueConst::new(
170                ty,
171                ConstBytes { memory, memory_map: MemoryMap::default() },
172            ))
173        }
174        Literal::Bool(b) if ty.is_bool() => rustc_type_ir::ConstKind::Value(ValueConst::new(
175            ty,
176            ConstBytes { memory: Box::new([*b as u8]), memory_map: MemoryMap::default() },
177        )),
178        Literal::Char(c) if ty.is_char() => rustc_type_ir::ConstKind::Value(ValueConst::new(
179            ty,
180            ConstBytes {
181                memory: (*c as u32).to_le_bytes().into(),
182                memory_map: MemoryMap::default(),
183            },
184        )),
185        Literal::String(symbol) if ty.is_str() => rustc_type_ir::ConstKind::Value(ValueConst::new(
186            ty,
187            ConstBytes {
188                memory: symbol.as_str().as_bytes().into(),
189                memory_map: MemoryMap::default(),
190            },
191        )),
192        Literal::ByteString(items) if ty.as_slice().is_some_and(|ty| ty.is_u8()) => {
193            rustc_type_ir::ConstKind::Value(ValueConst::new(
194                ty,
195                ConstBytes { memory: items.clone(), memory_map: MemoryMap::default() },
196            ))
197        }
198        // FIXME
199        Literal::CString(_items) => rustc_type_ir::ConstKind::Error(ErrorGuaranteed),
200        _ => rustc_type_ir::ConstKind::Error(ErrorGuaranteed),
201    };
202    Const::new(interner, kind)
203}
204
205/// Interns a possibly-unknown target usize
206pub fn usize_const<'db>(db: &'db dyn HirDatabase, value: Option<u128>, krate: Crate) -> Const<'db> {
207    intern_const_ref(
208        db,
209        &match value {
210            Some(value) => Literal::Uint(value, Some(BuiltinUint::Usize)),
211            None => {
212                return Const::new(
213                    DbInterner::new_no_crate(db),
214                    rustc_type_ir::ConstKind::Error(ErrorGuaranteed),
215                );
216            }
217        },
218        Ty::new_uint(DbInterner::new_no_crate(db), rustc_type_ir::UintTy::Usize),
219        krate,
220    )
221}
222
223pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<u128> {
224    match c.kind() {
225        ConstKind::Param(_) => None,
226        ConstKind::Infer(_) => None,
227        ConstKind::Bound(_, _) => None,
228        ConstKind::Placeholder(_) => None,
229        ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
230            GeneralConstId::ConstId(id) => {
231                let subst = unevaluated_const.args;
232                let ec = db.const_eval(id, subst, None).ok()?;
233                try_const_usize(db, ec)
234            }
235            GeneralConstId::StaticId(id) => {
236                let ec = db.const_eval_static(id).ok()?;
237                try_const_usize(db, ec)
238            }
239            GeneralConstId::AnonConstId(_) => None,
240        },
241        ConstKind::Value(val) => Some(u128::from_le_bytes(pad16(&val.value.inner().memory, false))),
242        ConstKind::Error(_) => None,
243        ConstKind::Expr(_) => None,
244    }
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                try_const_isize(db, &ec)
258            }
259            GeneralConstId::StaticId(id) => {
260                let ec = db.const_eval_static(id).ok()?;
261                try_const_isize(db, &ec)
262            }
263            GeneralConstId::AnonConstId(_) => None,
264        },
265        ConstKind::Value(val) => Some(i128::from_le_bytes(pad16(&val.value.inner().memory, true))),
266        ConstKind::Error(_) => None,
267        ConstKind::Expr(_) => None,
268    }
269}
270
271pub(crate) fn const_eval_discriminant_variant(
272    db: &dyn HirDatabase,
273    variant_id: EnumVariantId,
274) -> Result<i128, ConstEvalError> {
275    let interner = DbInterner::new_no_crate(db);
276    let def = variant_id.into();
277    let body = Body::of(db, def);
278    let loc = variant_id.lookup(db);
279    if matches!(body[body.root_expr()], Expr::Missing) {
280        let prev_idx = loc.index.checked_sub(1);
281        let value = match prev_idx {
282            Some(prev_idx) => {
283                1 + db.const_eval_discriminant(
284                    loc.parent.enum_variants(db).variants[prev_idx as usize].0,
285                )?
286            }
287            _ => 0,
288        };
289        return Ok(value);
290    }
291
292    let repr = AttrFlags::repr(db, loc.parent.into());
293    let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
294
295    let mir_body = db.monomorphized_mir_body(
296        def,
297        GenericArgs::empty(interner).store(),
298        ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) }
299            .store(),
300    )?;
301    let c = interpret_mir(db, mir_body, false, None)?.0?;
302    let c = if is_signed {
303        try_const_isize(db, &c).unwrap()
304    } else {
305        try_const_usize(db, c).unwrap() as i128
306    };
307    Ok(c)
308}
309
310// FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should
311// get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here
312// and make this function private. See the fixme comment on `InferenceContext::resolve_all`.
313pub(crate) fn eval_to_const<'db>(expr: ExprId, ctx: &mut InferenceContext<'_, 'db>) -> Const<'db> {
314    let infer = ctx.fixme_resolve_all_clone();
315    fn has_closure(store: &ExpressionStore, expr: ExprId) -> bool {
316        if matches!(store[expr], Expr::Closure { .. }) {
317            return true;
318        }
319        let mut r = false;
320        store.walk_child_exprs(expr, |idx| r |= has_closure(store, idx));
321        r
322    }
323    if has_closure(ctx.store, expr) {
324        // Type checking clousres need an isolated body (See the above FIXME). Bail out early to prevent panic.
325        return Const::error(ctx.interner());
326    }
327    if let Expr::Path(p) = &ctx.store[expr] {
328        let mut ctx = TyLoweringContext::new(
329            ctx.db,
330            &ctx.resolver,
331            ctx.store,
332            ctx.generic_def,
333            LifetimeElisionKind::Infer,
334        );
335        if let Some(c) = ctx.path_to_const(p) {
336            return c;
337        }
338    }
339    if let Some(body_owner) = ctx.owner.as_def_with_body()
340        && let Ok(mir_body) =
341            lower_body_to_mir(ctx.db, body_owner, Body::of(ctx.db, body_owner), &infer, expr)
342        && let Ok((Ok(result), _)) = interpret_mir(ctx.db, Arc::new(mir_body), true, None)
343    {
344        return result;
345    }
346    Const::error(ctx.interner())
347}
348
349pub(crate) fn const_eval_discriminant_cycle_result(
350    _: &dyn HirDatabase,
351    _: salsa::Id,
352    _: EnumVariantId,
353) -> Result<i128, ConstEvalError> {
354    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
355}
356
357pub(crate) fn const_eval<'db>(
358    db: &'db dyn HirDatabase,
359    def: ConstId,
360    subst: GenericArgs<'db>,
361    trait_env: Option<ParamEnvAndCrate<'db>>,
362) -> Result<Const<'db>, ConstEvalError> {
363    return match const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) {
364        Ok(konst) => Ok(konst.as_ref()),
365        Err(err) => Err(err.clone()),
366    };
367
368    #[salsa::tracked(returns(ref), cycle_result = const_eval_cycle_result)]
369    pub(crate) fn const_eval_query<'db>(
370        db: &'db dyn HirDatabase,
371        def: ConstId,
372        subst: StoredGenericArgs,
373        trait_env: Option<StoredParamEnvAndCrate>,
374    ) -> Result<StoredConst, ConstEvalError> {
375        let body = db.monomorphized_mir_body(
376            def.into(),
377            subst,
378            ParamEnvAndCrate {
379                param_env: db
380                    .trait_environment(ExpressionStoreOwnerId::from(GenericDefId::from(def))),
381                krate: def.krate(db),
382            }
383            .store(),
384        )?;
385        let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref()))?.0?;
386        Ok(c.store())
387    }
388
389    pub(crate) fn const_eval_cycle_result(
390        _: &dyn HirDatabase,
391        _: salsa::Id,
392        _: ConstId,
393        _: StoredGenericArgs,
394        _: Option<StoredParamEnvAndCrate>,
395    ) -> Result<StoredConst, ConstEvalError> {
396        Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
397    }
398}
399
400pub(crate) fn const_eval_static<'db>(
401    db: &'db dyn HirDatabase,
402    def: StaticId,
403) -> Result<Const<'db>, ConstEvalError> {
404    return match const_eval_static_query(db, def) {
405        Ok(konst) => Ok(konst.as_ref()),
406        Err(err) => Err(err.clone()),
407    };
408
409    #[salsa::tracked(returns(ref), cycle_result = const_eval_static_cycle_result)]
410    pub(crate) fn const_eval_static_query<'db>(
411        db: &'db dyn HirDatabase,
412        def: StaticId,
413    ) -> Result<StoredConst, ConstEvalError> {
414        let interner = DbInterner::new_no_crate(db);
415        let body = db.monomorphized_mir_body(
416            def.into(),
417            GenericArgs::empty(interner).store(),
418            ParamEnvAndCrate {
419                param_env: db
420                    .trait_environment(ExpressionStoreOwnerId::from(GenericDefId::from(def))),
421                krate: def.krate(db),
422            }
423            .store(),
424        )?;
425        let c = interpret_mir(db, body, false, None)?.0?;
426        Ok(c.store())
427    }
428
429    pub(crate) fn const_eval_static_cycle_result(
430        _: &dyn HirDatabase,
431        _: salsa::Id,
432        _: StaticId,
433    ) -> Result<StoredConst, ConstEvalError> {
434        Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
435    }
436}