1#[cfg(test)]
4mod tests;
5
6use base_db::Crate;
7use hir_def::{
8 ConstId, EnumVariantId, ExpressionStoreOwnerId, HasModule, StaticId,
9 attrs::AttrFlags,
10 expr_store::{Body, ExpressionStore, HygieneId, path::Path},
11 hir::{Expr, ExprId, Literal},
12 resolver::{Resolver, ValueNs},
13};
14use hir_expand::Lookup;
15use rustc_abi::Size;
16use rustc_apfloat::Float;
17use rustc_ast_ir::Mutability;
18use rustc_type_ir::inherent::{Const as _, GenericArgs as _, IntoKind, Ty as _};
19use salsa::Update;
20use stdx::never;
21
22use crate::{
23 ParamEnvAndCrate, Span,
24 db::{AnonConstId, AnonConstLoc, GeneralConstId, HirDatabase},
25 display::DisplayTarget,
26 generics::Generics,
27 lower::LoweringMode,
28 mir::{MirEvalError, MirLowerError, pad16},
29 next_solver::{
30 Allocation, Const, ConstKind, Consts, DbInterner, DefaultAny, GenericArgs, ParamConst,
31 ScalarInt, StoredAllocation, StoredEarlyBinder, StoredGenericArgs, Ty, TyKind,
32 UnevaluatedConst, ValTreeKind, default_types,
33 },
34 traits::StoredParamEnvAndCrate,
35};
36
37use super::mir::interpret_mir;
38
39#[derive(Debug, Clone, PartialEq, Eq, Update)]
40pub enum ConstEvalError<'db> {
41 MirLowerError(MirLowerError<'db>),
42 MirEvalError(MirEvalError<'db>),
43}
44
45impl ConstEvalError<'_> {
46 pub fn pretty_print(
47 &self,
48 f: &mut String,
49 db: &dyn HirDatabase,
50 span_formatter: impl Fn(span::FileId, span::TextRange) -> String,
51 display_target: DisplayTarget,
52 ) -> std::result::Result<(), std::fmt::Error> {
53 match self {
54 ConstEvalError::MirLowerError(e) => {
55 e.pretty_print(f, db, span_formatter, display_target)
56 }
57 ConstEvalError::MirEvalError(e) => {
58 e.pretty_print(f, db, span_formatter, display_target)
59 }
60 }
61 }
62}
63
64impl<'db> From<MirLowerError<'db>> for ConstEvalError<'db> {
65 fn from(value: MirLowerError<'db>) -> Self {
66 match value {
67 MirLowerError::ConstEvalError(_, e) => *e,
68 _ => ConstEvalError::MirLowerError(value),
69 }
70 }
71}
72
73impl<'db> From<MirEvalError<'db>> for ConstEvalError<'db> {
74 fn from(value: MirEvalError<'db>) -> Self {
75 ConstEvalError::MirEvalError(value)
76 }
77}
78
79fn intern_const_ref<'db>(
81 interner: DbInterner<'db>,
82 value: &Literal,
83 ty: Ty<'db>,
84) -> Result<Const<'db>, CreateConstError<'db>> {
85 let Ok(data_layout) = interner.db.target_data_layout(interner.expect_crate()) else {
86 return Ok(Const::error(interner));
87 };
88 let valtree = match (ty.kind(), value) {
89 (TyKind::Uint(uint), Literal::Uint(value, _)) => {
90 let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
91 let Some(scalar) = ScalarInt::try_from_uint(*value, size) else {
92 return Ok(Const::error(interner));
93 };
94 ValTreeKind::Leaf(scalar)
95 }
96 (TyKind::Uint(uint), Literal::Int(value, _)) => {
97 let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
99 let Some(scalar) = ScalarInt::try_from_uint(*value as u128, size) else {
100 return Ok(Const::error(interner));
101 };
102 ValTreeKind::Leaf(scalar)
103 }
104 (TyKind::Int(int), Literal::Int(value, _)) => {
105 let size = int.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
106 let Some(scalar) = ScalarInt::try_from_int(*value, size) else {
107 return Ok(Const::error(interner));
108 };
109 ValTreeKind::Leaf(scalar)
110 }
111 (TyKind::Bool, Literal::Bool(value)) => ValTreeKind::Leaf(ScalarInt::from(*value)),
112 (TyKind::Char, Literal::Char(value)) => ValTreeKind::Leaf(ScalarInt::from(*value)),
113 (TyKind::Float(float), Literal::Float(value, _)) => {
114 let size = Size::from_bits(float.bit_width());
115 let value = match float {
116 rustc_ast_ir::FloatTy::F16 => value.to_f16().to_bits(),
117 rustc_ast_ir::FloatTy::F32 => value.to_f32().to_bits(),
118 rustc_ast_ir::FloatTy::F64 => value.to_f64().to_bits(),
119 rustc_ast_ir::FloatTy::F128 => value.to_f128().to_bits(),
120 };
121 let scalar = ScalarInt::try_from_uint(value, size).unwrap();
122 ValTreeKind::Leaf(scalar)
123 }
124 (_, Literal::String(value)) => {
125 let u8_values = &interner.default_types().consts.u8_values;
126 ValTreeKind::Branch(Consts::new_from_iter(
127 interner,
128 value.as_str().as_bytes().iter().map(|&byte| u8_values[usize::from(byte)]),
129 ))
130 }
131 (_, Literal::ByteString(value)) => {
132 let u8_values = &interner.default_types().consts.u8_values;
133 ValTreeKind::Branch(Consts::new_from_iter(
134 interner,
135 value.iter().map(|&byte| u8_values[usize::from(byte)]),
136 ))
137 }
138 (_, Literal::CString(_)) => {
139 return Ok(Const::error(interner));
141 }
142 _ => {
143 never!("mismatching type for literal");
144 let actual = literal_ty(
145 interner,
146 value,
147 |types| types.types.i32,
148 |types| types.types.u32,
149 |types| types.types.f64,
150 );
151 return Err(CreateConstError::TypeMismatch { actual });
152 }
153 };
154 Ok(Const::new_valtree(interner, ty, valtree))
155}
156
157pub(crate) fn literal_ty<'db>(
158 interner: DbInterner<'db>,
159 value: &Literal,
160 default_int: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
161 default_uint: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
162 default_float: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>,
163) -> Ty<'db> {
164 let types = interner.default_types();
165 match value {
166 Literal::Bool(..) => types.types.bool,
167 Literal::String(..) => types.types.static_str_ref,
168 Literal::ByteString(bs) => {
169 let byte_type = types.types.u8;
170 let array_type = Ty::new_array(interner, byte_type, bs.len() as u128);
171 Ty::new_ref(interner, types.regions.statik, array_type, Mutability::Not)
172 }
173 Literal::CString(..) => Ty::new_ref(
174 interner,
175 types.regions.statik,
176 interner.lang_items().CStr.map_or(types.types.error, |strukt| {
177 Ty::new_adt(interner, strukt.into(), types.empty.generic_args)
178 }),
179 Mutability::Not,
180 ),
181 Literal::Char(..) => types.types.char,
182 Literal::Int(_v, ty) => match ty {
183 Some(int_ty) => match int_ty {
184 hir_def::builtin_type::BuiltinInt::Isize => types.types.isize,
185 hir_def::builtin_type::BuiltinInt::I8 => types.types.i8,
186 hir_def::builtin_type::BuiltinInt::I16 => types.types.i16,
187 hir_def::builtin_type::BuiltinInt::I32 => types.types.i32,
188 hir_def::builtin_type::BuiltinInt::I64 => types.types.i64,
189 hir_def::builtin_type::BuiltinInt::I128 => types.types.i128,
190 },
191 None => default_int(types),
192 },
193 Literal::Uint(_v, ty) => match ty {
194 Some(int_ty) => match int_ty {
195 hir_def::builtin_type::BuiltinUint::Usize => types.types.usize,
196 hir_def::builtin_type::BuiltinUint::U8 => types.types.u8,
197 hir_def::builtin_type::BuiltinUint::U16 => types.types.u16,
198 hir_def::builtin_type::BuiltinUint::U32 => types.types.u32,
199 hir_def::builtin_type::BuiltinUint::U64 => types.types.u64,
200 hir_def::builtin_type::BuiltinUint::U128 => types.types.u128,
201 },
202 None => default_uint(types),
203 },
204 Literal::Float(_v, ty) => match ty {
205 Some(float_ty) => match float_ty {
206 hir_def::builtin_type::BuiltinFloat::F16 => types.types.f16,
207 hir_def::builtin_type::BuiltinFloat::F32 => types.types.f32,
208 hir_def::builtin_type::BuiltinFloat::F64 => types.types.f64,
209 hir_def::builtin_type::BuiltinFloat::F128 => types.types.f128,
210 },
211 None => default_float(types),
212 },
213 }
214}
215
216pub fn usize_const<'db>(db: &'db dyn HirDatabase, value: Option<u128>, krate: Crate) -> Const<'db> {
218 let interner = DbInterner::new_no_crate(db);
219 let value = match value {
220 Some(value) => value,
221 None => {
222 return Const::error(interner);
223 }
224 };
225 let Ok(data_layout) = db.target_data_layout(krate) else {
226 return Const::error(interner);
227 };
228 let usize_ty = interner.default_types().types.usize;
229 let Some(scalar) = ScalarInt::try_from_uint(value, data_layout.pointer_size()) else {
230 return Const::error(interner);
231 };
232 Const::new_valtree(interner, usize_ty, ValTreeKind::Leaf(scalar))
233}
234
235pub fn allocation_as_usize(ec: Allocation<'_>) -> u128 {
236 u128::from_le_bytes(pad16(&ec.memory, false))
237}
238
239pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<u128> {
240 match c.kind() {
241 ConstKind::Param(_) => None,
242 ConstKind::Infer(_) => None,
243 ConstKind::Bound(_, _) => None,
244 ConstKind::Placeholder(_) => None,
245 ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
246 GeneralConstId::ConstId(id) => {
247 let subst = unevaluated_const.args;
248 let ec = db.const_eval(id, subst, None).ok()?;
249 Some(allocation_as_usize(ec))
250 }
251 GeneralConstId::StaticId(id) => {
252 let ec = db.const_eval_static(id).ok()?;
253 Some(allocation_as_usize(ec))
254 }
255 GeneralConstId::AnonConstId(id) => {
256 let subst = unevaluated_const.args;
257 let ec = db.anon_const_eval(id, subst, None).ok()?;
258 Some(allocation_as_usize(ec))
259 }
260 },
261 ConstKind::Value(val) => {
262 if val.ty == default_types(db).types.usize {
263 Some(val.value.inner().to_leaf().to_uint_unchecked())
264 } else {
265 None
266 }
267 }
268 ConstKind::Error(_) => None,
269 ConstKind::Expr(_) => None,
270 }
271}
272
273pub fn allocation_as_isize(ec: Allocation<'_>) -> i128 {
274 i128::from_le_bytes(pad16(&ec.memory, true))
275}
276
277pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<i128> {
278 match c.kind() {
279 ConstKind::Param(_) => None,
280 ConstKind::Infer(_) => None,
281 ConstKind::Bound(_, _) => None,
282 ConstKind::Placeholder(_) => None,
283 ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 {
284 GeneralConstId::ConstId(id) => {
285 let subst = unevaluated_const.args;
286 let ec = db.const_eval(id, subst, None).ok()?;
287 Some(allocation_as_isize(ec))
288 }
289 GeneralConstId::StaticId(id) => {
290 let ec = db.const_eval_static(id).ok()?;
291 Some(allocation_as_isize(ec))
292 }
293 GeneralConstId::AnonConstId(id) => {
294 let subst = unevaluated_const.args;
295 let ec = db.anon_const_eval(id, subst, None).ok()?;
296 Some(allocation_as_isize(ec))
297 }
298 },
299 ConstKind::Value(val) => {
300 if val.ty == default_types(db).types.isize {
301 Some(val.value.inner().to_leaf().to_int_unchecked())
302 } else {
303 None
304 }
305 }
306 ConstKind::Error(_) => None,
307 ConstKind::Expr(_) => None,
308 }
309}
310
311#[derive(Debug)]
312pub(crate) enum CreateConstError<'db> {
313 UsedForbiddenParam,
314 ResolveToNonConst,
315 DoesNotResolve,
316 ConstHasGenerics,
317 UnderscoreExpr,
318 AnonConstInterningDisabled,
319 TypeMismatch {
320 #[expect(unused, reason = "will need this for diagnostics")]
321 actual: Ty<'db>,
322 },
323}
324
325pub(crate) fn path_to_const<'a, 'db>(
326 db: &'db dyn HirDatabase,
327 resolver: &Resolver<'db>,
328 generics: &dyn Fn() -> &'a Generics<'db>,
329 forbid_params_after: Option<u32>,
330 path: &Path,
331) -> Result<Const<'db>, CreateConstError<'db>> {
332 let interner = DbInterner::new_no_crate(db);
333 let resolution = resolver
334 .resolve_path_in_value_ns_fully(db, path, HygieneId::ROOT)
335 .ok_or(CreateConstError::DoesNotResolve)?;
336 let no_generics = |def| crate::generics::generics(db, def).has_no_params();
337 let konst = match resolution {
338 ValueNs::ConstId(id) if no_generics(id.into()) => GeneralConstId::ConstId(id),
339 ValueNs::StaticId(id) => GeneralConstId::StaticId(id),
340 ValueNs::ConstId(_) => return Err(CreateConstError::ConstHasGenerics),
341 ValueNs::GenericParam(param) => {
342 let index = generics().type_or_const_param_idx(param.into());
343 if forbid_params_after.is_some_and(|forbid_after| index >= forbid_after) {
344 return Err(CreateConstError::UsedForbiddenParam);
345 }
346 return Ok(Const::new_param(interner, ParamConst { id: param, index }));
347 }
348 ValueNs::ImplSelf(_)
351 | ValueNs::LocalBinding(_)
352 | ValueNs::FunctionId(_)
353 | ValueNs::StructId(_)
354 | ValueNs::EnumVariantId(_) => return Err(CreateConstError::ResolveToNonConst),
355 };
356 let args = GenericArgs::empty(interner);
357 Ok(Const::new_unevaluated(interner, UnevaluatedConst { def: konst.into(), args }))
358}
359
360pub(crate) fn create_anon_const<'a, 'db>(
361 interner: DbInterner<'db>,
362 owner: ExpressionStoreOwnerId,
363 store: &ExpressionStore,
364 expr: ExprId,
365 resolver: &Resolver<'db>,
366 expected_ty: Ty<'db>,
367 generics: &dyn Fn() -> &'a Generics<'db>,
368 create_var: Option<&mut dyn FnMut(Span) -> Const<'db>>,
369 lowering_mode: LoweringMode,
370 forbid_params_after: Option<u32>,
371) -> Result<Const<'db>, CreateConstError<'db>> {
372 match &store[expr] {
373 Expr::Literal(literal) => intern_const_ref(interner, literal, expected_ty),
374 Expr::Underscore => match create_var {
375 Some(create_var) => Ok(create_var(expr.into())),
376 None => Err(CreateConstError::UnderscoreExpr),
377 },
378 Expr::Path(path)
379 if let konst =
380 path_to_const(interner.db, resolver, generics, forbid_params_after, path)
381 && !matches!(
382 konst,
383 Err(CreateConstError::DoesNotResolve | CreateConstError::ConstHasGenerics)
384 ) =>
385 {
386 konst
387 }
388 _ => {
389 let Some(token) = lowering_mode.allow_tracked_structs() else {
390 return Err(CreateConstError::AnonConstInterningDisabled);
391 };
392
393 let allow_using_generic_params = forbid_params_after.is_none();
394 let konst = AnonConstId::new(
395 interner.db,
396 AnonConstLoc {
397 owner,
398 expr,
399 ty: StoredEarlyBinder::bind(expected_ty.store()),
400 allow_using_generic_params,
401 },
402 token,
403 );
404 let args = if allow_using_generic_params {
405 GenericArgs::identity_for_item(interner, owner.generic_def(interner.db).into())
406 } else {
407 GenericArgs::empty(interner)
408 };
409 Ok(Const::new_unevaluated(
410 interner,
411 UnevaluatedConst { def: GeneralConstId::AnonConstId(konst).into(), args },
412 ))
413 }
414 }
415}
416
417#[salsa::tracked(cycle_result = const_eval_discriminant_cycle_result)]
418pub(crate) fn const_eval_discriminant_variant<'db>(
419 db: &'db dyn HirDatabase,
420 variant_id: EnumVariantId,
421) -> Result<i128, ConstEvalError<'db>> {
422 let interner = DbInterner::new_no_crate(db);
423 let def = variant_id.into();
424 let body = Body::of(db, def);
425 let loc = variant_id.lookup(db);
426 if matches!(body[body.root_expr()], Expr::Missing) {
427 let prev_idx = loc.index(db).checked_sub(1);
428 let value = match prev_idx {
429 Some(prev_idx) => {
430 1 + db.const_eval_discriminant(loc.parent.enum_variants(db).variants[prev_idx].0)?
431 }
432 _ => 0,
433 };
434 return Ok(value);
435 }
436
437 let repr = AttrFlags::repr(db, loc.parent.into());
438 let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
439
440 let mir_body = db.monomorphized_mir_body(
441 def.into(),
442 GenericArgs::empty(interner).store(),
443 ParamEnvAndCrate {
444 param_env: db.trait_environment(def.generic_def(db)),
445 krate: def.krate(db),
446 }
447 .store(),
448 )?;
449 let c = interpret_mir(db, mir_body, false, None)?.0?;
450 let c = if is_signed { allocation_as_isize(c) } else { allocation_as_usize(c) as i128 };
451 Ok(c)
452}
453
454fn const_eval_discriminant_cycle_result<'db>(
455 _: &'db dyn HirDatabase,
456 _: salsa::Id,
457 _: EnumVariantId,
458) -> Result<i128, ConstEvalError<'db>> {
459 Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
460}
461
462pub(crate) fn const_eval<'db>(
463 db: &'db dyn HirDatabase,
464 def: ConstId,
465 subst: GenericArgs<'db>,
466 trait_env: Option<ParamEnvAndCrate<'db>>,
467) -> Result<Allocation<'db>, ConstEvalError<'db>> {
468 return match const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) {
469 Ok(konst) => Ok(konst.as_ref()),
470 Err(err) => Err(err.clone()),
471 };
472
473 #[salsa::tracked(returns(ref), cycle_result = const_eval_cycle_result)]
474 pub(crate) fn const_eval_query<'db>(
475 db: &'db dyn HirDatabase,
476 def: ConstId,
477 subst: StoredGenericArgs,
478 trait_env: Option<StoredParamEnvAndCrate>,
479 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
480 let body = db.monomorphized_mir_body(
481 def.into(),
482 subst,
483 ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) }
484 .store(),
485 )?;
486 let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref(db)))?.0?;
487 Ok(c.store())
488 }
489
490 pub(crate) fn const_eval_cycle_result<'db>(
491 _: &'db dyn HirDatabase,
492 _: salsa::Id,
493 _: ConstId,
494 _: StoredGenericArgs,
495 _: Option<StoredParamEnvAndCrate>,
496 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
497 Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
498 }
499}
500
501pub(crate) fn anon_const_eval<'db>(
502 db: &'db dyn HirDatabase,
503 def: AnonConstId<'db>,
504 subst: GenericArgs<'db>,
505 trait_env: Option<ParamEnvAndCrate<'db>>,
506) -> Result<Allocation<'db>, ConstEvalError<'db>> {
507 return match anon_const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) {
508 Ok(konst) => Ok(konst.as_ref()),
509 Err(err) => Err(err.clone()),
510 };
511
512 #[salsa::tracked(returns(ref), cycle_result = anon_const_eval_cycle_result)]
513 pub(crate) fn anon_const_eval_query<'db>(
514 db: &'db dyn HirDatabase,
515 def: AnonConstId<'db>,
516 subst: StoredGenericArgs,
517 trait_env: Option<StoredParamEnvAndCrate>,
518 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
519 let body = db.monomorphized_mir_body(
520 def.into(),
521 subst,
522 ParamEnvAndCrate {
523 param_env: db.trait_environment(def.loc(db).owner.generic_def(db)),
524 krate: def.krate(db),
525 }
526 .store(),
527 )?;
528 let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref(db)))?.0?;
529 Ok(c.store())
530 }
531
532 pub(crate) fn anon_const_eval_cycle_result<'db>(
533 _: &'db dyn HirDatabase,
534 _: salsa::Id,
535 _: AnonConstId<'db>,
536 _: StoredGenericArgs,
537 _: Option<StoredParamEnvAndCrate>,
538 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
539 Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
540 }
541}
542
543pub(crate) fn const_eval_static<'db>(
544 db: &'db dyn HirDatabase,
545 def: StaticId,
546) -> Result<Allocation<'db>, ConstEvalError<'db>> {
547 return match const_eval_static_query(db, def) {
548 Ok(konst) => Ok(konst.as_ref()),
549 Err(err) => Err(err.clone()),
550 };
551
552 #[salsa::tracked(returns(ref), cycle_result = const_eval_static_cycle_result)]
553 pub(crate) fn const_eval_static_query<'db>(
554 db: &'db dyn HirDatabase,
555 def: StaticId,
556 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
557 let interner = DbInterner::new_no_crate(db);
558 let body = db.monomorphized_mir_body(
559 def.into(),
560 GenericArgs::empty(interner).store(),
561 ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) }
562 .store(),
563 )?;
564 let c = interpret_mir(db, body, false, None)?.0?;
565 Ok(c.store())
566 }
567
568 pub(crate) fn const_eval_static_cycle_result<'db>(
569 _: &'db dyn HirDatabase,
570 _: salsa::Id,
571 _: StaticId,
572 ) -> Result<StoredAllocation, ConstEvalError<'db>> {
573 Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
574 }
575}