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