hir_ty/
consteval.rs

1//! Constant evaluation details
2
3use base_db::Crate;
4use chalk_ir::{BoundVar, DebruijnIndex, cast::Cast};
5use hir_def::{
6    EnumVariantId, GeneralConstId, HasModule as _, StaticId,
7    expr_store::{Body, HygieneId, path::Path},
8    hir::{Expr, ExprId},
9    resolver::{Resolver, ValueNs},
10    type_ref::LiteralConstRef,
11};
12use hir_expand::Lookup;
13use stdx::never;
14use triomphe::Arc;
15
16use crate::{
17    Const, ConstData, ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution,
18    TraitEnvironment, Ty, TyBuilder,
19    db::HirDatabase,
20    display::DisplayTarget,
21    generics::Generics,
22    infer::InferenceContext,
23    lower::ParamLoweringMode,
24    next_solver::{DbInterner, mapping::ChalkToNextSolver},
25    to_placeholder_idx,
26};
27
28use super::mir::{MirEvalError, MirLowerError, interpret_mir, lower_to_mir, pad16};
29
30/// Extension trait for [`Const`]
31pub trait ConstExt {
32    /// Is a [`Const`] unknown?
33    fn is_unknown(&self) -> bool;
34}
35
36impl ConstExt for Const {
37    fn is_unknown(&self) -> bool {
38        match self.data(Interner).value {
39            // interned Unknown
40            chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
41                interned: ConstScalar::Unknown,
42            }) => true,
43
44            // interned concrete anything else
45            chalk_ir::ConstValue::Concrete(..) => false,
46
47            _ => {
48                tracing::error!(
49                    "is_unknown was called on a non-concrete constant value! {:?}",
50                    self
51                );
52                true
53            }
54        }
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum ConstEvalError {
60    MirLowerError(MirLowerError),
61    MirEvalError(MirEvalError),
62}
63
64impl ConstEvalError {
65    pub fn pretty_print(
66        &self,
67        f: &mut String,
68        db: &dyn HirDatabase,
69        span_formatter: impl Fn(span::FileId, span::TextRange) -> String,
70        display_target: DisplayTarget,
71    ) -> std::result::Result<(), std::fmt::Error> {
72        match self {
73            ConstEvalError::MirLowerError(e) => {
74                e.pretty_print(f, db, span_formatter, display_target)
75            }
76            ConstEvalError::MirEvalError(e) => {
77                e.pretty_print(f, db, span_formatter, display_target)
78            }
79        }
80    }
81}
82
83impl From<MirLowerError> for ConstEvalError {
84    fn from(value: MirLowerError) -> Self {
85        match value {
86            MirLowerError::ConstEvalError(_, e) => *e,
87            _ => ConstEvalError::MirLowerError(value),
88        }
89    }
90}
91
92impl From<MirEvalError> for ConstEvalError {
93    fn from(value: MirEvalError) -> Self {
94        ConstEvalError::MirEvalError(value)
95    }
96}
97
98pub(crate) fn path_to_const<'g>(
99    db: &dyn HirDatabase,
100    resolver: &Resolver<'_>,
101    path: &Path,
102    mode: ParamLoweringMode,
103    args: impl FnOnce() -> &'g Generics,
104    debruijn: DebruijnIndex,
105    expected_ty: Ty,
106) -> Option<Const> {
107    match resolver.resolve_path_in_value_ns_fully(db, path, HygieneId::ROOT) {
108        Some(ValueNs::GenericParam(p)) => {
109            let ty = db.const_param_ty(p);
110            let args = args();
111            let value = match mode {
112                ParamLoweringMode::Placeholder => {
113                    let idx = args.type_or_const_param_idx(p.into()).unwrap();
114                    ConstValue::Placeholder(to_placeholder_idx(db, p.into(), idx as u32))
115                }
116                ParamLoweringMode::Variable => match args.type_or_const_param_idx(p.into()) {
117                    Some(it) => ConstValue::BoundVar(BoundVar::new(debruijn, it)),
118                    None => {
119                        never!(
120                            "Generic list doesn't contain this param: {:?}, {:?}, {:?}",
121                            args,
122                            path,
123                            p
124                        );
125                        return None;
126                    }
127                },
128            };
129            Some(ConstData { ty, value }.intern(Interner))
130        }
131        Some(ValueNs::ConstId(c)) => Some(intern_const_scalar(
132            ConstScalar::UnevaluatedConst(c.into(), Substitution::empty(Interner)),
133            expected_ty,
134        )),
135        // FIXME: With feature(adt_const_params), we also need to consider other things here, e.g. struct constructors.
136        _ => None,
137    }
138}
139
140pub fn unknown_const(ty: Ty) -> Const {
141    ConstData {
142        ty,
143        value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: ConstScalar::Unknown }),
144    }
145    .intern(Interner)
146}
147
148pub fn unknown_const_as_generic(ty: Ty) -> GenericArg {
149    unknown_const(ty).cast(Interner)
150}
151
152/// Interns a constant scalar with the given type
153pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const {
154    ConstData { ty, value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: value }) }
155        .intern(Interner)
156}
157
158/// Interns a constant scalar with the given type
159pub fn intern_const_ref(
160    db: &dyn HirDatabase,
161    value: &LiteralConstRef,
162    ty: Ty,
163    krate: Crate,
164) -> Const {
165    let interner = DbInterner::new_with(db, Some(krate), None);
166    let layout = || db.layout_of_ty(ty.to_nextsolver(interner), TraitEnvironment::empty(krate));
167    let bytes = match value {
168        LiteralConstRef::Int(i) => {
169            // FIXME: We should handle failure of layout better.
170            let size = layout().map(|it| it.size.bytes_usize()).unwrap_or(16);
171            ConstScalar::Bytes(i.to_le_bytes()[0..size].into(), MemoryMap::default())
172        }
173        LiteralConstRef::UInt(i) => {
174            let size = layout().map(|it| it.size.bytes_usize()).unwrap_or(16);
175            ConstScalar::Bytes(i.to_le_bytes()[0..size].into(), MemoryMap::default())
176        }
177        LiteralConstRef::Bool(b) => ConstScalar::Bytes(Box::new([*b as u8]), MemoryMap::default()),
178        LiteralConstRef::Char(c) => {
179            ConstScalar::Bytes((*c as u32).to_le_bytes().into(), MemoryMap::default())
180        }
181        LiteralConstRef::Unknown => ConstScalar::Unknown,
182    };
183    intern_const_scalar(bytes, ty)
184}
185
186/// Interns a possibly-unknown target usize
187pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: Crate) -> Const {
188    intern_const_ref(
189        db,
190        &value.map_or(LiteralConstRef::Unknown, LiteralConstRef::UInt),
191        TyBuilder::usize(),
192        krate,
193    )
194}
195
196pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128> {
197    match &c.data(Interner).value {
198        chalk_ir::ConstValue::BoundVar(_) => None,
199        chalk_ir::ConstValue::InferenceVar(_) => None,
200        chalk_ir::ConstValue::Placeholder(_) => None,
201        chalk_ir::ConstValue::Concrete(c) => match &c.interned {
202            ConstScalar::Bytes(it, _) => Some(u128::from_le_bytes(pad16(it, false))),
203            ConstScalar::UnevaluatedConst(c, subst) => {
204                let ec = db.const_eval(*c, subst.clone(), None).ok()?;
205                try_const_usize(db, &ec)
206            }
207            _ => None,
208        },
209    }
210}
211
212pub fn try_const_isize(db: &dyn HirDatabase, c: &Const) -> Option<i128> {
213    match &c.data(Interner).value {
214        chalk_ir::ConstValue::BoundVar(_) => None,
215        chalk_ir::ConstValue::InferenceVar(_) => None,
216        chalk_ir::ConstValue::Placeholder(_) => None,
217        chalk_ir::ConstValue::Concrete(c) => match &c.interned {
218            ConstScalar::Bytes(it, _) => Some(i128::from_le_bytes(pad16(it, true))),
219            ConstScalar::UnevaluatedConst(c, subst) => {
220                let ec = db.const_eval(*c, subst.clone(), None).ok()?;
221                try_const_isize(db, &ec)
222            }
223            _ => None,
224        },
225    }
226}
227
228pub(crate) fn const_eval_cycle_result(
229    _: &dyn HirDatabase,
230    _: GeneralConstId,
231    _: Substitution,
232    _: Option<Arc<TraitEnvironment>>,
233) -> Result<Const, ConstEvalError> {
234    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
235}
236
237pub(crate) fn const_eval_static_cycle_result(
238    _: &dyn HirDatabase,
239    _: StaticId,
240) -> Result<Const, ConstEvalError> {
241    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
242}
243
244pub(crate) fn const_eval_discriminant_cycle_result(
245    _: &dyn HirDatabase,
246    _: EnumVariantId,
247) -> Result<i128, ConstEvalError> {
248    Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
249}
250
251pub(crate) fn const_eval_query(
252    db: &dyn HirDatabase,
253    def: GeneralConstId,
254    subst: Substitution,
255    trait_env: Option<Arc<TraitEnvironment>>,
256) -> Result<Const, ConstEvalError> {
257    let body = match def {
258        GeneralConstId::ConstId(c) => {
259            db.monomorphized_mir_body(c.into(), subst, db.trait_environment(c.into()))?
260        }
261        GeneralConstId::StaticId(s) => {
262            let krate = s.module(db).krate();
263            db.monomorphized_mir_body(s.into(), subst, TraitEnvironment::empty(krate))?
264        }
265    };
266    let c = interpret_mir(db, body, false, trait_env)?.0?;
267    Ok(c)
268}
269
270pub(crate) fn const_eval_static_query(
271    db: &dyn HirDatabase,
272    def: StaticId,
273) -> Result<Const, ConstEvalError> {
274    let body = db.monomorphized_mir_body(
275        def.into(),
276        Substitution::empty(Interner),
277        db.trait_environment_for_body(def.into()),
278    )?;
279    let c = interpret_mir(db, body, false, None)?.0?;
280    Ok(c)
281}
282
283pub(crate) fn const_eval_discriminant_variant(
284    db: &dyn HirDatabase,
285    variant_id: EnumVariantId,
286) -> Result<i128, ConstEvalError> {
287    let def = variant_id.into();
288    let body = db.body(def);
289    let loc = variant_id.lookup(db);
290    if matches!(body[body.body_expr], Expr::Missing) {
291        let prev_idx = loc.index.checked_sub(1);
292        let value = match prev_idx {
293            Some(prev_idx) => {
294                1 + db.const_eval_discriminant(
295                    loc.parent.enum_variants(db).variants[prev_idx as usize].0,
296                )?
297            }
298            _ => 0,
299        };
300        return Ok(value);
301    }
302
303    let repr = db.enum_signature(loc.parent).repr;
304    let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
305
306    let mir_body = db.monomorphized_mir_body(
307        def,
308        Substitution::empty(Interner),
309        db.trait_environment_for_body(def),
310    )?;
311    let c = interpret_mir(db, mir_body, false, None)?.0?;
312    let c = if is_signed {
313        try_const_isize(db, &c).unwrap()
314    } else {
315        try_const_usize(db, &c).unwrap() as i128
316    };
317    Ok(c)
318}
319
320// FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should
321// get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here
322// and make this function private. See the fixme comment on `InferenceContext::resolve_all`.
323pub(crate) fn eval_to_const(
324    expr: ExprId,
325    mode: ParamLoweringMode,
326    ctx: &mut InferenceContext<'_>,
327    debruijn: DebruijnIndex,
328) -> Const {
329    let db = ctx.db;
330    let infer = ctx.clone().resolve_all();
331    fn has_closure(body: &Body, expr: ExprId) -> bool {
332        if matches!(body[expr], Expr::Closure { .. }) {
333            return true;
334        }
335        let mut r = false;
336        body.walk_child_exprs(expr, |idx| r |= has_closure(body, idx));
337        r
338    }
339    if has_closure(ctx.body, expr) {
340        // Type checking clousres need an isolated body (See the above FIXME). Bail out early to prevent panic.
341        return unknown_const(infer[expr].clone());
342    }
343    if let Expr::Path(p) = &ctx.body[expr] {
344        let resolver = &ctx.resolver;
345        if let Some(c) =
346            path_to_const(db, resolver, p, mode, || ctx.generics(), debruijn, infer[expr].clone())
347        {
348            return c;
349        }
350    }
351    if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr)
352        && let Ok((Ok(result), _)) = interpret_mir(db, Arc::new(mir_body), true, None)
353    {
354        return result;
355    }
356    unknown_const(infer[expr].clone())
357}
358
359#[cfg(test)]
360mod tests;