Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/consteval.rs')
| -rw-r--r-- | crates/hir-ty/src/consteval.rs | 304 |
1 files changed, 235 insertions, 69 deletions
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index 0fdd3ff1d9..67e40fa811 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -5,32 +5,34 @@ mod tests; use base_db::Crate; use hir_def::{ - ConstId, EnumVariantId, ExpressionStoreOwnerId, GeneralConstId, GenericDefId, HasModule, - StaticId, + ConstId, EnumVariantId, ExpressionStoreOwnerId, GenericDefId, HasModule, StaticId, attrs::AttrFlags, - expr_store::{Body, ExpressionStore}, + expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{Expr, ExprId, Literal}, + resolver::{Resolver, ValueNs}, }; use hir_expand::Lookup; use rustc_abi::Size; use rustc_apfloat::Float; -use rustc_type_ir::inherent::IntoKind; +use rustc_ast_ir::Mutability; +use rustc_type_ir::inherent::{Const as _, GenericArgs as _, IntoKind, Ty as _}; use stdx::never; use crate::{ - LifetimeElisionKind, ParamEnvAndCrate, TyLoweringContext, - db::HirDatabase, + ParamEnvAndCrate, + db::{AnonConstId, AnonConstLoc, GeneralConstId, HirDatabase}, display::DisplayTarget, - infer::InferenceContext, + generics::Generics, mir::{MirEvalError, MirLowerError, pad16}, next_solver::{ - Allocation, Const, ConstKind, Consts, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, - ScalarInt, StoredAllocation, StoredGenericArgs, Ty, TyKind, ValTreeKind, default_types, + Allocation, Const, ConstKind, Consts, DbInterner, DefaultAny, ErrorGuaranteed, GenericArg, + GenericArgs, ParamConst, ScalarInt, StoredAllocation, StoredEarlyBinder, StoredGenericArgs, + Ty, TyKind, UnevaluatedConst, ValTreeKind, default_types, infer::InferCtxt, }, traits::StoredParamEnvAndCrate, }; -use super::mir::{interpret_mir, lower_body_to_mir}; +use super::mir::interpret_mir; pub fn unknown_const<'db>(_ty: Ty<'db>) -> Const<'db> { Const::new(DbInterner::conjure(), rustc_type_ir::ConstKind::Error(ErrorGuaranteed)) @@ -81,15 +83,13 @@ impl From<MirEvalError> for ConstEvalError { } /// Interns a constant scalar with the given type -pub fn intern_const_ref<'a>( - db: &'a dyn HirDatabase, +fn intern_const_ref<'db>( + interner: DbInterner<'db>, value: &Literal, - ty: Ty<'a>, - krate: Crate, -) -> Const<'a> { - let interner = DbInterner::new_no_crate(db); - let Ok(data_layout) = db.target_data_layout(krate) else { - return Const::error(interner); + ty: Ty<'db>, +) -> Result<Const<'db>, CreateConstError<'db>> { + let Ok(data_layout) = interner.db.target_data_layout(interner.expect_crate()) else { + return Ok(Const::error(interner)); }; let valtree = match (ty.kind(), value) { (TyKind::Uint(uint), Literal::Uint(value, _)) => { @@ -137,14 +137,80 @@ pub fn intern_const_ref<'a>( } (_, Literal::CString(_)) => { // FIXME: - return Const::error(interner); + return Ok(Const::error(interner)); } _ => { never!("mismatching type for literal"); - return Const::error(interner); + let actual = literal_ty( + interner, + value, + |types| types.types.i32, + |types| types.types.u32, + |types| types.types.f64, + ); + return Err(CreateConstError::TypeMismatch { actual }); } }; - Const::new_valtree(interner, ty, valtree) + Ok(Const::new_valtree(interner, ty, valtree)) +} + +pub(crate) fn literal_ty<'db>( + interner: DbInterner<'db>, + value: &Literal, + default_int: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>, + default_uint: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>, + default_float: impl FnOnce(&DefaultAny<'db>) -> Ty<'db>, +) -> Ty<'db> { + let types = interner.default_types(); + match value { + Literal::Bool(..) => types.types.bool, + Literal::String(..) => types.types.static_str_ref, + Literal::ByteString(bs) => { + let byte_type = types.types.u8; + let array_type = Ty::new_array(interner, byte_type, bs.len() as u128); + Ty::new_ref(interner, types.regions.statik, array_type, Mutability::Not) + } + Literal::CString(..) => Ty::new_ref( + interner, + types.regions.statik, + interner.lang_items().CStr.map_or(types.types.error, |strukt| { + Ty::new_adt(interner, strukt.into(), types.empty.generic_args) + }), + Mutability::Not, + ), + Literal::Char(..) => types.types.char, + Literal::Int(_v, ty) => match ty { + Some(int_ty) => match int_ty { + hir_def::builtin_type::BuiltinInt::Isize => types.types.isize, + hir_def::builtin_type::BuiltinInt::I8 => types.types.i8, + hir_def::builtin_type::BuiltinInt::I16 => types.types.i16, + hir_def::builtin_type::BuiltinInt::I32 => types.types.i32, + hir_def::builtin_type::BuiltinInt::I64 => types.types.i64, + hir_def::builtin_type::BuiltinInt::I128 => types.types.i128, + }, + None => default_int(types), + }, + Literal::Uint(_v, ty) => match ty { + Some(int_ty) => match int_ty { + hir_def::builtin_type::BuiltinUint::Usize => types.types.usize, + hir_def::builtin_type::BuiltinUint::U8 => types.types.u8, + hir_def::builtin_type::BuiltinUint::U16 => types.types.u16, + hir_def::builtin_type::BuiltinUint::U32 => types.types.u32, + hir_def::builtin_type::BuiltinUint::U64 => types.types.u64, + hir_def::builtin_type::BuiltinUint::U128 => types.types.u128, + }, + None => default_uint(types), + }, + Literal::Float(_v, ty) => match ty { + Some(float_ty) => match float_ty { + hir_def::builtin_type::BuiltinFloat::F16 => types.types.f16, + hir_def::builtin_type::BuiltinFloat::F32 => types.types.f32, + hir_def::builtin_type::BuiltinFloat::F64 => types.types.f64, + hir_def::builtin_type::BuiltinFloat::F128 => types.types.f128, + }, + None => default_float(types), + }, + } } /// Interns a possibly-unknown target usize @@ -184,7 +250,11 @@ pub fn try_const_usize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<u let ec = db.const_eval_static(id).ok()?; Some(allocation_as_usize(ec)) } - GeneralConstId::AnonConstId(_) => None, + GeneralConstId::AnonConstId(id) => { + let subst = unevaluated_const.args; + let ec = db.anon_const_eval(id, subst, None).ok()?; + Some(allocation_as_usize(ec)) + } }, ConstKind::Value(val) => { if val.ty == default_types(db).types.usize { @@ -202,8 +272,8 @@ pub fn allocation_as_isize(ec: Allocation<'_>) -> i128 { i128::from_le_bytes(pad16(&ec.memory, true)) } -pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: &Const<'db>) -> Option<i128> { - match (*c).kind() { +pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: Const<'db>) -> Option<i128> { + match c.kind() { ConstKind::Param(_) => None, ConstKind::Infer(_) => None, ConstKind::Bound(_, _) => None, @@ -218,7 +288,11 @@ pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: &Const<'db>) -> Option< let ec = db.const_eval_static(id).ok()?; Some(allocation_as_isize(ec)) } - GeneralConstId::AnonConstId(_) => None, + GeneralConstId::AnonConstId(id) => { + let subst = unevaluated_const.args; + let ec = db.anon_const_eval(id, subst, None).ok()?; + Some(allocation_as_isize(ec)) + } }, ConstKind::Value(val) => { if val.ty == default_types(db).types.isize { @@ -232,6 +306,99 @@ pub fn try_const_isize<'db>(db: &'db dyn HirDatabase, c: &Const<'db>) -> Option< } } +#[derive(Debug)] +pub(crate) enum CreateConstError<'db> { + UsedForbiddenParam, + ResolveToNonConst, + DoesNotResolve, + UnderscoreExpr, + TypeMismatch { + #[expect(unused, reason = "will need this for diagnostics")] + actual: Ty<'db>, + }, +} + +pub(crate) fn path_to_const<'a, 'db>( + db: &'db dyn HirDatabase, + resolver: &Resolver<'db>, + generics: &dyn Fn() -> &'a Generics<'db>, + forbid_params_after: Option<u32>, + path: &Path, +) -> Result<Const<'db>, CreateConstError<'db>> { + let interner = DbInterner::new_no_crate(db); + let resolution = resolver + .resolve_path_in_value_ns_fully(db, path, HygieneId::ROOT) + .ok_or(CreateConstError::DoesNotResolve)?; + let konst = match resolution { + ValueNs::ConstId(id) => GeneralConstId::ConstId(id), + ValueNs::StaticId(id) => GeneralConstId::StaticId(id), + ValueNs::GenericParam(param) => { + let index = generics().type_or_const_param_idx(param.into()); + if forbid_params_after.is_some_and(|forbid_after| index >= forbid_after) { + return Err(CreateConstError::UsedForbiddenParam); + } + return Ok(Const::new_param(interner, ParamConst { id: param, index })); + } + // These are not valid as consts. + // FIXME: Report an error? + ValueNs::ImplSelf(_) + | ValueNs::LocalBinding(_) + | ValueNs::FunctionId(_) + | ValueNs::StructId(_) + | ValueNs::EnumVariantId(_) => return Err(CreateConstError::ResolveToNonConst), + }; + let args = GenericArgs::empty(interner); + Ok(Const::new_unevaluated(interner, UnevaluatedConst { def: konst.into(), args })) +} + +pub(crate) fn create_anon_const<'a, 'db>( + interner: DbInterner<'db>, + owner: ExpressionStoreOwnerId, + store: &ExpressionStore, + expr: ExprId, + resolver: &Resolver<'db>, + expected_ty: Ty<'db>, + generics: &dyn Fn() -> &'a Generics<'db>, + infcx: Option<&InferCtxt<'db>>, + forbid_params_after: Option<u32>, +) -> Result<Const<'db>, CreateConstError<'db>> { + match &store[expr] { + Expr::Literal(literal) => intern_const_ref(interner, literal, expected_ty), + Expr::Underscore => match infcx { + Some(infcx) => Ok(infcx.next_const_var(expr.into())), + None => Err(CreateConstError::UnderscoreExpr), + }, + Expr::Path(path) + if let konst = + path_to_const(interner.db, resolver, generics, forbid_params_after, path) + && !matches!(konst, Err(CreateConstError::DoesNotResolve)) => + { + konst + } + _ => { + let allow_using_generic_params = forbid_params_after.is_none(); + let konst = AnonConstId::new( + interner.db, + AnonConstLoc { + owner, + expr, + ty: StoredEarlyBinder::bind(expected_ty.store()), + allow_using_generic_params, + }, + ); + let args = if allow_using_generic_params { + GenericArgs::identity_for_item(interner, owner.generic_def(interner.db).into()) + } else { + GenericArgs::empty(interner) + }; + Ok(Const::new_unevaluated( + interner, + UnevaluatedConst { def: GeneralConstId::AnonConstId(konst).into(), args }, + )) + } + } +} + pub(crate) fn const_eval_discriminant_variant( db: &dyn HirDatabase, variant_id: EnumVariantId, @@ -257,7 +424,7 @@ pub(crate) fn const_eval_discriminant_variant( let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed()); let mir_body = db.monomorphized_mir_body( - def, + def.into(), GenericArgs::empty(interner).store(), ParamEnvAndCrate { param_env: db.trait_environment(def.into()), krate: def.krate(db) } .store(), @@ -267,49 +434,6 @@ pub(crate) fn const_eval_discriminant_variant( 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(store: &ExpressionStore, expr: ExprId) -> bool { - if matches!(store[expr], Expr::Closure { .. }) { - return true; - } - let mut r = false; - store.walk_child_exprs(expr, |idx| r |= has_closure(store, idx)); - r - } - if has_closure(ctx.store, 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.store[expr] { - let mut ctx = TyLoweringContext::new( - ctx.db, - &ctx.resolver, - ctx.store, - ctx.generic_def, - LifetimeElisionKind::Infer, - ); - if let Some(c) = ctx.path_to_const(p) { - return c; - } - } - if let Some(body_owner) = ctx.owner.as_def_with_body() - && let Ok(mir_body) = - lower_body_to_mir(ctx.db, body_owner, Body::of(ctx.db, body_owner), &infer, expr) - && let Ok((Ok(result), _)) = interpret_mir(ctx.db, &mir_body, true, None) - { - return Const::new_from_allocation( - ctx.interner(), - &result, - ParamEnvAndCrate { param_env: ctx.table.param_env, krate: ctx.resolver.krate() }, - ); - } - Const::error(ctx.interner()) -} - pub(crate) fn const_eval_discriminant_cycle_result( _: &dyn HirDatabase, _: salsa::Id, @@ -361,6 +485,48 @@ pub(crate) fn const_eval<'db>( } } +pub(crate) fn anon_const_eval<'db>( + db: &'db dyn HirDatabase, + def: AnonConstId, + subst: GenericArgs<'db>, + trait_env: Option<ParamEnvAndCrate<'db>>, +) -> Result<Allocation<'db>, ConstEvalError> { + return match anon_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 = anon_const_eval_cycle_result)] + pub(crate) fn anon_const_eval_query( + db: &dyn HirDatabase, + def: AnonConstId, + subst: StoredGenericArgs, + trait_env: Option<StoredParamEnvAndCrate>, + ) -> Result<StoredAllocation, ConstEvalError> { + let body = db.monomorphized_mir_body( + def.into(), + subst, + ParamEnvAndCrate { + param_env: db.trait_environment(def.loc(db).owner), + 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 anon_const_eval_cycle_result( + _: &dyn HirDatabase, + _: salsa::Id, + _: AnonConstId, + _: StoredGenericArgs, + _: Option<StoredParamEnvAndCrate>, + ) -> Result<StoredAllocation, ConstEvalError> { + Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) + } +} + pub(crate) fn const_eval_static<'db>( db: &'db dyn HirDatabase, def: StaticId, |