//! Constant evaluation details #[cfg(test)] mod tests; use base_db::Crate; use hir_def::{ ConstId, EnumVariantId, GeneralConstId, HasModule, StaticId, attrs::AttrFlags, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, expr_store::Body, hir::{Expr, ExprId, Literal}, }; use hir_expand::Lookup; use rustc_type_ir::inherent::IntoKind; use triomphe::Arc; use crate::{ LifetimeElisionKind, MemoryMap, ParamEnvAndCrate, TyLoweringContext, db::HirDatabase, display::DisplayTarget, infer::InferenceContext, mir::{MirEvalError, MirLowerError}, next_solver::{ Const, ConstBytes, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, StoredConst, StoredGenericArgs, Ty, ValueConst, }, traits::StoredParamEnvAndCrate, }; use super::mir::{interpret_mir, lower_to_mir, pad16}; pub fn unknown_const<'db>(_ty: Ty<'db>) -> Const<'db> { Const::new(DbInterner::conjure(), rustc_type_ir::ConstKind::Error(ErrorGuaranteed)) } pub fn unknown_const_as_generic<'db>(ty: Ty<'db>) -> GenericArg<'db> { unknown_const(ty).into() } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConstEvalError { MirLowerError(MirLowerError), MirEvalError(MirEvalError), } impl ConstEvalError { pub fn pretty_print( &self, f: &mut String, db: &dyn HirDatabase, span_formatter: impl Fn(span::FileId, span::TextRange) -> String, display_target: DisplayTarget, ) -> std::result::Result<(), std::fmt::Error> { match self { ConstEvalError::MirLowerError(e) => { e.pretty_print(f, db, span_formatter, display_target) } ConstEvalError::MirEvalError(e) => { e.pretty_print(f, db, span_formatter, display_target) } } } } impl From for ConstEvalError { fn from(value: MirLowerError) -> Self { match value { MirLowerError::ConstEvalError(_, e) => *e, _ => ConstEvalError::MirLowerError(value), } } } impl From for ConstEvalError { fn from(value: MirEvalError) -> Self { ConstEvalError::MirEvalError(value) } } /// Interns a constant scalar with the given type pub fn intern_const_ref<'a>( db: &'a dyn HirDatabase, value: &Literal, ty: Ty<'a>, _krate: Crate, ) -> Const<'a> { let interner = DbInterner::new_no_crate(db); let kind = match value { &Literal::Uint(i, builtin_ty) if builtin_ty.is_none() || ty.as_builtin() == builtin_ty.map(BuiltinType::Uint) => { let memory = match ty.as_builtin() { Some(BuiltinType::Uint(builtin_uint)) => match builtin_uint { BuiltinUint::U8 => Box::new([i as u8]) as Box<[u8]>, BuiltinUint::U16 => Box::new((i as u16).to_le_bytes()), BuiltinUint::U32 => Box::new((i as u32).to_le_bytes()), BuiltinUint::U64 => Box::new((i as u64).to_le_bytes()), BuiltinUint::U128 => Box::new((i).to_le_bytes()), BuiltinUint::Usize => Box::new((i as usize).to_le_bytes()), }, _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)), }; rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory, memory_map: MemoryMap::default() }, )) } &Literal::Int(i, None) if ty .as_builtin() .is_some_and(|builtin_ty| matches!(builtin_ty, BuiltinType::Uint(_))) => { let memory = match ty.as_builtin() { Some(BuiltinType::Uint(builtin_uint)) => match builtin_uint { BuiltinUint::U8 => Box::new([i as u8]) as Box<[u8]>, BuiltinUint::U16 => Box::new((i as u16).to_le_bytes()), BuiltinUint::U32 => Box::new((i as u32).to_le_bytes()), BuiltinUint::U64 => Box::new((i as u64).to_le_bytes()), BuiltinUint::U128 => Box::new((i as u128).to_le_bytes()), BuiltinUint::Usize => Box::new((i as usize).to_le_bytes()), }, _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)), }; rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory, memory_map: MemoryMap::default() }, )) } &Literal::Int(i, builtin_ty) if builtin_ty.is_none() || ty.as_builtin() == builtin_ty.map(BuiltinType::Int) => { let memory = match ty.as_builtin() { Some(BuiltinType::Int(builtin_int)) => match builtin_int { BuiltinInt::I8 => Box::new([i as u8]) as Box<[u8]>, BuiltinInt::I16 => Box::new((i as i16).to_le_bytes()), BuiltinInt::I32 => Box::new((i as i32).to_le_bytes()), BuiltinInt::I64 => Box::new((i as i64).to_le_bytes()), BuiltinInt::I128 => Box::new((i).to_le_bytes()), BuiltinInt::Isize => Box::new((i as isize).to_le_bytes()), }, _ => return Const::new(interner, rustc_type_ir::ConstKind::Error(ErrorGuaranteed)), }; rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory, memory_map: MemoryMap::default() }, )) } Literal::Float(float_type_wrapper, builtin_float) if builtin_float.is_none() || ty.as_builtin() == builtin_float.map(BuiltinType::Float) => { let memory = match ty.as_builtin().unwrap() { BuiltinType::Float(builtin_float) => match builtin_float { // FIXME: hir_def::builtin_type::BuiltinFloat::F16 => Box::new([0u8; 2]) as Box<[u8]>, hir_def::builtin_type::BuiltinFloat::F32 => { Box::new(float_type_wrapper.to_f32().to_le_bytes()) } hir_def::builtin_type::BuiltinFloat::F64 => { Box::new(float_type_wrapper.to_f64().to_le_bytes()) } // FIXME: hir_def::builtin_type::BuiltinFloat::F128 => Box::new([0; 16]), }, _ => unreachable!(), }; rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory, memory_map: MemoryMap::default() }, )) } Literal::Bool(b) if ty.is_bool() => rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory: Box::new([*b as u8]), memory_map: MemoryMap::default() }, )), Literal::Char(c) if ty.is_char() => rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory: (*c as u32).to_le_bytes().into(), memory_map: MemoryMap::default(), }, )), Literal::String(symbol) if ty.is_str() => rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory: symbol.as_str().as_bytes().into(), memory_map: MemoryMap::default(), }, )), Literal::ByteString(items) if ty.as_slice().is_some_and(|ty| ty.is_u8()) => { rustc_type_ir::ConstKind::Value(ValueConst::new( ty, ConstBytes { memory: items.clone(), memory_map: MemoryMap::default() }, )) } // FIXME Literal::CString(_items) => rustc_type_ir::ConstKind::Error(ErrorGuaranteed), _ => rustc_type_ir::ConstKind::Error(ErrorGuaranteed), }; Const::new(interner, kind) } /// Interns a possibly-unknown target usize pub fn usize_const<'db>(db: &'db dyn HirDatabase, value: Option, krate: Crate) -> Const<'db> { intern_const_ref( db, &match value { Some(value) => Literal::Uint(value, Some(BuiltinUint::Usize)), None => { return Const::new( DbInterner::new_no_crate(db), rustc_type_ir::ConstKind::Error(ErrorGuaranteed), ); } }, Ty::new_uint(DbInterner::new_no_crate(db), rustc_type_ir::UintTy::Usize), krate, ) } pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option { match c.kind() { ConstKind::Param(_) => None, ConstKind::Infer(_) => None, ConstKind::Bound(_, _) => None, ConstKind::Placeholder(_) => None, ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 { GeneralConstId::ConstId(id) => { let subst = unevaluated_const.args; let ec = db.const_eval(id, subst, None).ok()?; try_const_usize(db, ec) } GeneralConstId::StaticId(id) => { let ec = db.const_eval_static(id).ok()?; try_const_usize(db, ec) } }, ConstKind::Value(val) => Some(u128::from_le_bytes(pad16(&val.value.inner().memory, false))), ConstKind::Error(_) => None, ConstKind::Expr(_) => None, } } pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: &Const<'db>) -> Option { match (*c).kind() { ConstKind::Param(_) => None, ConstKind::Infer(_) => None, ConstKind::Bound(_, _) => None, ConstKind::Placeholder(_) => None, ConstKind::Unevaluated(unevaluated_const) => match unevaluated_const.def.0 { GeneralConstId::ConstId(id) => { let subst = unevaluated_const.args; let ec = db.const_eval(id, subst, None).ok()?; try_const_isize(db, &ec) } GeneralConstId::StaticId(id) => { let ec = db.const_eval_static(id).ok()?; try_const_isize(db, &ec) } }, ConstKind::Value(val) => Some(i128::from_le_bytes(pad16(&val.value.inner().memory, true))), ConstKind::Error(_) => None, ConstKind::Expr(_) => None, } } pub(crate) fn const_eval_discriminant_variant( db: &dyn HirDatabase, variant_id: EnumVariantId, ) -> Result { let interner = DbInterner::new_no_crate(db); let def = variant_id.into(); let body = db.body(def); let loc = variant_id.lookup(db); if matches!(body[body.body_expr], Expr::Missing) { let prev_idx = loc.index.checked_sub(1); let value = match prev_idx { Some(prev_idx) => { 1 + db.const_eval_discriminant( loc.parent.enum_variants(db).variants[prev_idx as usize].0, )? } _ => 0, }; return Ok(value); } let repr = AttrFlags::repr(db, loc.parent.into()); let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed()); let mir_body = db.monomorphized_mir_body( def, GenericArgs::empty(interner).store(), ParamEnvAndCrate { param_env: db.trait_environment_for_body(def), krate: def.krate(db) } .store(), )?; let c = interpret_mir(db, mir_body, false, None)?.0?; let c = if is_signed { try_const_isize(db, &c).unwrap() } else { try_const_usize(db, c).unwrap() as i128 }; Ok(c) } // FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should // get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here // and make this function private. See the fixme comment on `InferenceContext::resolve_all`. pub(crate) fn eval_to_const<'db>(expr: ExprId, ctx: &mut InferenceContext<'_, 'db>) -> Const<'db> { let infer = ctx.fixme_resolve_all_clone(); fn has_closure(body: &Body, expr: ExprId) -> bool { if matches!(body[expr], Expr::Closure { .. }) { return true; } let mut r = false; body.walk_child_exprs(expr, |idx| r |= has_closure(body, idx)); r } if has_closure(ctx.body, expr) { // Type checking clousres need an isolated body (See the above FIXME). Bail out early to prevent panic. return Const::error(ctx.interner()); } if let Expr::Path(p) = &ctx.body[expr] { let mut ctx = TyLoweringContext::new( ctx.db, &ctx.resolver, ctx.body, ctx.generic_def, LifetimeElisionKind::Infer, ); if let Some(c) = ctx.path_to_const(p) { return c; } } if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, ctx.body, &infer, expr) && let Ok((Ok(result), _)) = interpret_mir(ctx.db, Arc::new(mir_body), true, None) { return result; } Const::error(ctx.interner()) } pub(crate) fn const_eval_discriminant_cycle_result( _: &dyn HirDatabase, _: salsa::Id, _: EnumVariantId, ) -> Result { Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } pub(crate) fn const_eval<'db>( db: &'db dyn HirDatabase, def: ConstId, subst: GenericArgs<'db>, trait_env: Option>, ) -> Result, ConstEvalError> { return match const_eval_query(db, def, subst.store(), trait_env.map(|env| env.store())) { Ok(konst) => Ok(konst.as_ref()), Err(err) => Err(err.clone()), }; #[salsa::tracked(returns(ref), cycle_result = const_eval_cycle_result)] pub(crate) fn const_eval_query<'db>( db: &'db dyn HirDatabase, def: ConstId, subst: StoredGenericArgs, trait_env: Option, ) -> Result { let body = db.monomorphized_mir_body( def.into(), subst, ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) } .store(), )?; let c = interpret_mir(db, body, false, trait_env.as_ref().map(|env| env.as_ref()))?.0?; Ok(c.store()) } pub(crate) fn const_eval_cycle_result( _: &dyn HirDatabase, _: salsa::Id, _: ConstId, _: StoredGenericArgs, _: Option, ) -> Result { Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } } pub(crate) fn const_eval_static<'db>( db: &'db dyn HirDatabase, def: StaticId, ) -> Result, ConstEvalError> { return match const_eval_static_query(db, def) { Ok(konst) => Ok(konst.as_ref()), Err(err) => Err(err.clone()), }; #[salsa::tracked(returns(ref), cycle_result = const_eval_static_cycle_result)] pub(crate) fn const_eval_static_query<'db>( db: &'db dyn HirDatabase, def: StaticId, ) -> Result { let interner = DbInterner::new_no_crate(db); let body = db.monomorphized_mir_body( def.into(), GenericArgs::empty(interner).store(), ParamEnvAndCrate { param_env: db.trait_environment_for_body(def.into()), krate: def.krate(db), } .store(), )?; let c = interpret_mir(db, body, false, None)?.0?; Ok(c.store()) } pub(crate) fn const_eval_static_cycle_result( _: &dyn HirDatabase, _: salsa::Id, _: StaticId, ) -> Result { Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } }