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.rs304
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,