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