• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Constant evaluation details
2 
3 use base_db::CrateId;
4 use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData};
5 use hir_def::{
6     hir::Expr,
7     path::Path,
8     resolver::{Resolver, ValueNs},
9     type_ref::LiteralConstRef,
10     ConstBlockLoc, EnumVariantId, GeneralConstId, StaticId,
11 };
12 use la_arena::{Idx, RawIdx};
13 use stdx::never;
14 use triomphe::Arc;
15 
16 use crate::{
17     db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode,
18     mir::monomorphize_mir_body_bad, to_placeholder_idx, utils::Generics, Const, ConstData,
19     ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, Ty, TyBuilder,
20 };
21 
22 use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError};
23 
24 /// Extension trait for [`Const`]
25 pub trait ConstExt {
26     /// Is a [`Const`] unknown?
is_unknown(&self) -> bool27     fn is_unknown(&self) -> bool;
28 }
29 
30 impl ConstExt for Const {
is_unknown(&self) -> bool31     fn is_unknown(&self) -> bool {
32         match self.data(Interner).value {
33             // interned Unknown
34             chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
35                 interned: ConstScalar::Unknown,
36             }) => true,
37 
38             // interned concrete anything else
39             chalk_ir::ConstValue::Concrete(..) => false,
40 
41             _ => {
42                 tracing::error!(
43                     "is_unknown was called on a non-concrete constant value! {:?}",
44                     self
45                 );
46                 true
47             }
48         }
49     }
50 }
51 
52 #[derive(Debug, Clone, PartialEq, Eq)]
53 pub enum ConstEvalError {
54     MirLowerError(MirLowerError),
55     MirEvalError(MirEvalError),
56 }
57 
58 impl From<MirLowerError> for ConstEvalError {
from(value: MirLowerError) -> Self59     fn from(value: MirLowerError) -> Self {
60         match value {
61             MirLowerError::ConstEvalError(_, e) => *e,
62             _ => ConstEvalError::MirLowerError(value),
63         }
64     }
65 }
66 
67 impl From<MirEvalError> for ConstEvalError {
from(value: MirEvalError) -> Self68     fn from(value: MirEvalError) -> Self {
69         ConstEvalError::MirEvalError(value)
70     }
71 }
72 
path_to_const( db: &dyn HirDatabase, resolver: &Resolver, path: &Path, mode: ParamLoweringMode, args_lazy: impl FnOnce() -> Generics, debruijn: DebruijnIndex, expected_ty: Ty, ) -> Option<Const>73 pub(crate) fn path_to_const(
74     db: &dyn HirDatabase,
75     resolver: &Resolver,
76     path: &Path,
77     mode: ParamLoweringMode,
78     args_lazy: impl FnOnce() -> Generics,
79     debruijn: DebruijnIndex,
80     expected_ty: Ty,
81 ) -> Option<Const> {
82     match resolver.resolve_path_in_value_ns_fully(db.upcast(), path) {
83         Some(ValueNs::GenericParam(p)) => {
84             let ty = db.const_param_ty(p);
85             let args = args_lazy();
86             let value = match mode {
87                 ParamLoweringMode::Placeholder => {
88                     ConstValue::Placeholder(to_placeholder_idx(db, p.into()))
89                 }
90                 ParamLoweringMode::Variable => match args.param_idx(p.into()) {
91                     Some(x) => ConstValue::BoundVar(BoundVar::new(debruijn, x)),
92                     None => {
93                         never!(
94                             "Generic list doesn't contain this param: {:?}, {:?}, {:?}",
95                             args,
96                             path,
97                             p
98                         );
99                         return None;
100                     }
101                 },
102             };
103             Some(ConstData { ty, value }.intern(Interner))
104         }
105         Some(ValueNs::ConstId(c)) => Some(intern_const_scalar(
106             ConstScalar::UnevaluatedConst(c.into(), Substitution::empty(Interner)),
107             expected_ty,
108         )),
109         _ => None,
110     }
111 }
112 
unknown_const(ty: Ty) -> Const113 pub fn unknown_const(ty: Ty) -> Const {
114     ConstData {
115         ty,
116         value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: ConstScalar::Unknown }),
117     }
118     .intern(Interner)
119 }
120 
unknown_const_as_generic(ty: Ty) -> GenericArg121 pub fn unknown_const_as_generic(ty: Ty) -> GenericArg {
122     GenericArgData::Const(unknown_const(ty)).intern(Interner)
123 }
124 
125 /// Interns a constant scalar with the given type
intern_const_scalar(value: ConstScalar, ty: Ty) -> Const126 pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const {
127     ConstData { ty, value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: value }) }
128         .intern(Interner)
129 }
130 
131 /// Interns a constant scalar with the given type
intern_const_ref( db: &dyn HirDatabase, value: &LiteralConstRef, ty: Ty, krate: CrateId, ) -> Const132 pub fn intern_const_ref(
133     db: &dyn HirDatabase,
134     value: &LiteralConstRef,
135     ty: Ty,
136     krate: CrateId,
137 ) -> Const {
138     let layout = db.layout_of_ty(ty.clone(), krate);
139     let bytes = match value {
140         LiteralConstRef::Int(i) => {
141             // FIXME: We should handle failure of layout better.
142             let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16);
143             ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
144         }
145         LiteralConstRef::UInt(i) => {
146             let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16);
147             ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
148         }
149         LiteralConstRef::Bool(b) => ConstScalar::Bytes(vec![*b as u8], MemoryMap::default()),
150         LiteralConstRef::Char(c) => {
151             ConstScalar::Bytes((*c as u32).to_le_bytes().to_vec(), MemoryMap::default())
152         }
153         LiteralConstRef::Unknown => ConstScalar::Unknown,
154     };
155     intern_const_scalar(bytes, ty)
156 }
157 
158 /// Interns a possibly-unknown target usize
usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) -> Const159 pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) -> Const {
160     intern_const_ref(
161         db,
162         &value.map_or(LiteralConstRef::Unknown, LiteralConstRef::UInt),
163         TyBuilder::usize(),
164         krate,
165     )
166 }
167 
try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128>168 pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128> {
169     match &c.data(Interner).value {
170         chalk_ir::ConstValue::BoundVar(_) => None,
171         chalk_ir::ConstValue::InferenceVar(_) => None,
172         chalk_ir::ConstValue::Placeholder(_) => None,
173         chalk_ir::ConstValue::Concrete(c) => match &c.interned {
174             ConstScalar::Bytes(x, _) => Some(u128::from_le_bytes(pad16(&x, false))),
175             ConstScalar::UnevaluatedConst(c, subst) => {
176                 let ec = db.const_eval(*c, subst.clone()).ok()?;
177                 try_const_usize(db, &ec)
178             }
179             _ => None,
180         },
181     }
182 }
183 
const_eval_recover( _: &dyn HirDatabase, _: &[String], _: &GeneralConstId, _: &Substitution, ) -> Result<Const, ConstEvalError>184 pub(crate) fn const_eval_recover(
185     _: &dyn HirDatabase,
186     _: &[String],
187     _: &GeneralConstId,
188     _: &Substitution,
189 ) -> Result<Const, ConstEvalError> {
190     Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
191 }
192 
const_eval_static_recover( _: &dyn HirDatabase, _: &[String], _: &StaticId, ) -> Result<Const, ConstEvalError>193 pub(crate) fn const_eval_static_recover(
194     _: &dyn HirDatabase,
195     _: &[String],
196     _: &StaticId,
197 ) -> Result<Const, ConstEvalError> {
198     Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
199 }
200 
const_eval_discriminant_recover( _: &dyn HirDatabase, _: &[String], _: &EnumVariantId, ) -> Result<i128, ConstEvalError>201 pub(crate) fn const_eval_discriminant_recover(
202     _: &dyn HirDatabase,
203     _: &[String],
204     _: &EnumVariantId,
205 ) -> Result<i128, ConstEvalError> {
206     Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
207 }
208 
const_eval_query( db: &dyn HirDatabase, def: GeneralConstId, subst: Substitution, ) -> Result<Const, ConstEvalError>209 pub(crate) fn const_eval_query(
210     db: &dyn HirDatabase,
211     def: GeneralConstId,
212     subst: Substitution,
213 ) -> Result<Const, ConstEvalError> {
214     let body = match def {
215         GeneralConstId::ConstId(c) => {
216             db.monomorphized_mir_body(c.into(), subst, db.trait_environment(c.into()))?
217         }
218         GeneralConstId::ConstBlockId(c) => {
219             let ConstBlockLoc { parent, root } = db.lookup_intern_anonymous_const(c);
220             let body = db.body(parent);
221             let infer = db.infer(parent);
222             Arc::new(monomorphize_mir_body_bad(
223                 db,
224                 lower_to_mir(db, parent, &body, &infer, root)?,
225                 subst,
226                 db.trait_environment_for_body(parent),
227             )?)
228         }
229         GeneralConstId::InTypeConstId(c) => db.mir_body(c.into())?,
230     };
231     let c = interpret_mir(db, &body, false).0?;
232     Ok(c)
233 }
234 
const_eval_static_query( db: &dyn HirDatabase, def: StaticId, ) -> Result<Const, ConstEvalError>235 pub(crate) fn const_eval_static_query(
236     db: &dyn HirDatabase,
237     def: StaticId,
238 ) -> Result<Const, ConstEvalError> {
239     let body = db.monomorphized_mir_body(
240         def.into(),
241         Substitution::empty(Interner),
242         db.trait_environment_for_body(def.into()),
243     )?;
244     let c = interpret_mir(db, &body, false).0?;
245     Ok(c)
246 }
247 
const_eval_discriminant_variant( db: &dyn HirDatabase, variant_id: EnumVariantId, ) -> Result<i128, ConstEvalError>248 pub(crate) fn const_eval_discriminant_variant(
249     db: &dyn HirDatabase,
250     variant_id: EnumVariantId,
251 ) -> Result<i128, ConstEvalError> {
252     let def = variant_id.into();
253     let body = db.body(def);
254     if body.exprs[body.body_expr] == Expr::Missing {
255         let prev_idx: u32 = variant_id.local_id.into_raw().into();
256         let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw);
257         let value = match prev_idx {
258             Some(local_id) => {
259                 let prev_variant = EnumVariantId { local_id, parent: variant_id.parent };
260                 1 + db.const_eval_discriminant(prev_variant)?
261             }
262             _ => 0,
263         };
264         return Ok(value);
265     }
266     let mir_body = db.monomorphized_mir_body(
267         def,
268         Substitution::empty(Interner),
269         db.trait_environment_for_body(def),
270     )?;
271     let c = interpret_mir(db, &mir_body, false).0?;
272     let c = try_const_usize(db, &c).unwrap() as i128;
273     Ok(c)
274 }
275 
276 // FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should
277 // get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here
278 // and make this function private. See the fixme comment on `InferenceContext::resolve_all`.
eval_to_const( expr: Idx<Expr>, mode: ParamLoweringMode, ctx: &mut InferenceContext<'_>, args: impl FnOnce() -> Generics, debruijn: DebruijnIndex, ) -> Const279 pub(crate) fn eval_to_const(
280     expr: Idx<Expr>,
281     mode: ParamLoweringMode,
282     ctx: &mut InferenceContext<'_>,
283     args: impl FnOnce() -> Generics,
284     debruijn: DebruijnIndex,
285 ) -> Const {
286     let db = ctx.db;
287     let infer = ctx.clone().resolve_all();
288     if let Expr::Path(p) = &ctx.body.exprs[expr] {
289         let resolver = &ctx.resolver;
290         if let Some(c) = path_to_const(db, resolver, p, mode, args, debruijn, infer[expr].clone()) {
291             return c;
292         }
293     }
294     let infer = ctx.clone().resolve_all();
295     if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) {
296         if let Ok(result) = interpret_mir(db, &mir_body, true).0 {
297             return result;
298         }
299     }
300     unknown_const(infer[expr].clone())
301 }
302 
303 #[cfg(test)]
304 mod tests;
305