diff options
Diffstat (limited to 'compiler/rustc_trait_selection/src/traits/mod.rs')
| -rw-r--r-- | compiler/rustc_trait_selection/src/traits/mod.rs | 213 |
1 files changed, 209 insertions, 4 deletions
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 436c0fabd29..17636d432ae 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -27,6 +27,7 @@ use std::fmt::Debug; use std::ops::ControlFlow; use rustc_errors::ErrorGuaranteed; +use rustc_hir::def::DefKind; pub use rustc_infer::traits::*; use rustc_middle::query::Providers; use rustc_middle::span_bug; @@ -34,11 +35,11 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; use rustc_middle::ty::{ - self, GenericArgs, GenericArgsRef, Ty, TyCtxt, TypeFolder, TypeSuperVisitable, TypingMode, - Upcast, + self, GenericArgs, GenericArgsRef, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, + TypeSuperVisitable, TypingMode, Upcast, }; -use rustc_span::Span; use rustc_span::def_id::DefId; +use rustc_span::{DUMMY_SP, Span}; use tracing::{debug, instrument}; pub use self::coherence::{ @@ -366,11 +367,23 @@ pub fn normalize_param_env_or_error<'tcx>( if c.has_escaping_bound_vars() { return ty::Const::new_misc_error(self.0); } + // While it is pretty sus to be evaluating things with an empty param env, it // should actually be okay since without `feature(generic_const_exprs)` the only // const arguments that have a non-empty param env are array repeat counts. These // do not appear in the type system though. - c.normalize_internal(self.0, ty::ParamEnv::empty()) + if let ty::ConstKind::Unevaluated(uv) = c.kind() + && self.0.def_kind(uv.def) == DefKind::AnonConst + { + let infcx = self.0.infer_ctxt().build(TypingMode::non_body_analysis()); + let c = evaluate_const(&infcx, c, ty::ParamEnv::empty()); + // We should never wind up with any `infcx` local state when normalizing anon consts + // under min const generics. + assert!(!c.has_infer() && !c.has_placeholders()); + return c; + } + + c } } @@ -474,6 +487,198 @@ pub fn normalize_param_env_or_error<'tcx>( ty::ParamEnv::new(tcx.mk_clauses(&predicates), unnormalized_env.reveal()) } +#[derive(Debug)] +pub enum EvaluateConstErr { + /// The constant being evaluated was either a generic parameter or inference variable, *or*, + /// some unevaluated constant with either generic parameters or inference variables in its + /// generic arguments. + HasGenericsOrInfers, + /// The type this constant evalauted to is not valid for use in const generics. This should + /// always result in an error when checking the constant is correctly typed for the parameter + /// it is an argument to, so a bug is delayed when encountering this. + InvalidConstParamTy(ErrorGuaranteed), + /// CTFE failed to evaluate the constant in some unrecoverable way (e.g. encountered a `panic!`). + /// This is also used when the constant was already tainted by error. + EvaluationFailure(ErrorGuaranteed), +} + +// FIXME(BoxyUwU): Private this once we `generic_const_exprs` isn't doing its own normalization routine +// FIXME(generic_const_exprs): Consider accepting a `ty::UnevaluatedConst` when we are not rolling our own +// normalization scheme +/// Evaluates a type system constant returning a `ConstKind::Error` in cases where CTFE failed and +/// returning the passed in constant if it was not fully concrete (i.e. depended on generic parameters +/// or inference variables) +/// +/// You should not call this function unless you are implementing normalization itself. Prefer to use +/// `normalize_erasing_regions` or the `normalize` functions on `ObligationCtxt`/`FnCtxt`/`InferCtxt`. +pub fn evaluate_const<'tcx>( + infcx: &InferCtxt<'tcx>, + ct: ty::Const<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> ty::Const<'tcx> { + match try_evaluate_const(infcx, ct, param_env) { + Ok(ct) => ct, + Err(EvaluateConstErr::EvaluationFailure(e) | EvaluateConstErr::InvalidConstParamTy(e)) => { + ty::Const::new_error(infcx.tcx, e) + } + Err(EvaluateConstErr::HasGenericsOrInfers) => ct, + } +} + +// FIXME(BoxyUwU): Private this once we `generic_const_exprs` isn't doing its own normalization routine +// FIXME(generic_const_exprs): Consider accepting a `ty::UnevaluatedConst` when we are not rolling our own +// normalization scheme +/// Evaluates a type system constant making sure to not allow constants that depend on generic parameters +/// or inference variables to succeed in evaluating. +/// +/// You should not call this function unless you are implementing normalization itself. Prefer to use +/// `normalize_erasing_regions` or the `normalize` functions on `ObligationCtxt`/`FnCtxt`/`InferCtxt`. +#[instrument(level = "debug", skip(infcx), ret)] +pub fn try_evaluate_const<'tcx>( + infcx: &InferCtxt<'tcx>, + ct: ty::Const<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> Result<ty::Const<'tcx>, EvaluateConstErr> { + let tcx = infcx.tcx; + let ct = infcx.resolve_vars_if_possible(ct); + debug!(?ct); + + match ct.kind() { + ty::ConstKind::Value(..) => Ok(ct), + ty::ConstKind::Error(e) => Err(EvaluateConstErr::EvaluationFailure(e)), + ty::ConstKind::Param(_) + | ty::ConstKind::Infer(_) + | ty::ConstKind::Bound(_, _) + | ty::ConstKind::Placeholder(_) + | ty::ConstKind::Expr(_) => Err(EvaluateConstErr::HasGenericsOrInfers), + ty::ConstKind::Unevaluated(uv) => { + // Postpone evaluation of constants that depend on generic parameters or inference variables. + let (args, param_env) = if tcx.features().generic_const_exprs() + && uv.has_non_region_infer() + { + // `feature(generic_const_exprs)` causes anon consts to inherit all parent generics. This can cause + // inference variables and generic parameters to show up in `ty::Const` even though the anon const + // does not actually make use of them. We handle this case specially and attempt to evaluate anyway. + match tcx.thir_abstract_const(uv.def) { + Ok(Some(ct)) => { + let ct = tcx.expand_abstract_consts(ct.instantiate(tcx, uv.args)); + if let Err(e) = ct.error_reported() { + return Err(EvaluateConstErr::EvaluationFailure(e)); + } else if ct.has_non_region_infer() || ct.has_non_region_param() { + // If the anon const *does* actually use generic parameters or inference variables from + // the generic arguments provided for it, then we should *not* attempt to evaluate it. + return Err(EvaluateConstErr::HasGenericsOrInfers); + } else { + (replace_param_and_infer_args_with_placeholder(tcx, uv.args), param_env) + } + } + Err(_) | Ok(None) => { + let args = GenericArgs::identity_for_item(tcx, uv.def); + let param_env = tcx.param_env(uv.def); + (args, param_env) + } + } + } else { + // FIXME: We don't check anything on stable as the only way we can wind up with + // an unevaluated constant containing generic parameters is through array repeat + // expression counts which have a future compat lint for usage of generic parameters + // instead of a hard error. + // + // This codepath is however also reachable by `generic_const_exprs` and some other + // feature gates which allow constants in the type system to use generic parameters. + // In theory we should be checking for generic parameters here and returning an error + // in such cases. + (uv.args, param_env) + }; + let uv = ty::UnevaluatedConst::new(uv.def, args); + + // It's not *technically* correct to be revealing opaque types here as we could still be + // before borrowchecking. However, CTFE itself uses `Reveal::All` unconditionally even during + // typeck and not doing so has a lot of (undesirable) fallout (#101478, #119821). As a result we + // always use a revealed env when resolving the instance to evaluate. + // + // FIXME: `const_eval_resolve_for_typeck` should probably just set the env to `Reveal::All` + // instead of having this logic here + let env = tcx.erase_regions(param_env).with_reveal_all_normalized(tcx); + let erased_uv = tcx.erase_regions(uv); + + use rustc_middle::mir::interpret::ErrorHandled; + match tcx.const_eval_resolve_for_typeck(env, erased_uv, DUMMY_SP) { + Ok(Ok(val)) => Ok(ty::Const::new_value( + tcx, + val, + tcx.type_of(uv.def).instantiate(tcx, uv.args), + )), + Ok(Err(_)) => { + let e = tcx.dcx().delayed_bug( + "Type system constant with non valtree'able type evaluated but no error emitted", + ); + Err(EvaluateConstErr::InvalidConstParamTy(e)) + } + Err(ErrorHandled::Reported(info, _)) => { + Err(EvaluateConstErr::EvaluationFailure(info.into())) + } + Err(ErrorHandled::TooGeneric(_)) => Err(EvaluateConstErr::HasGenericsOrInfers), + } + } + } +} + +/// Replaces args that reference param or infer variables with suitable +/// placeholders. This function is meant to remove these param and infer +/// args when they're not actually needed to evaluate a constant. +fn replace_param_and_infer_args_with_placeholder<'tcx>( + tcx: TyCtxt<'tcx>, + args: GenericArgsRef<'tcx>, +) -> GenericArgsRef<'tcx> { + struct ReplaceParamAndInferWithPlaceholder<'tcx> { + tcx: TyCtxt<'tcx>, + idx: u32, + } + + impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceParamAndInferWithPlaceholder<'tcx> { + fn cx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { + if let ty::Infer(_) = t.kind() { + let idx = { + let idx = self.idx; + self.idx += 1; + idx + }; + Ty::new_placeholder(self.tcx, ty::PlaceholderType { + universe: ty::UniverseIndex::ROOT, + bound: ty::BoundTy { + var: ty::BoundVar::from_u32(idx), + kind: ty::BoundTyKind::Anon, + }, + }) + } else { + t.super_fold_with(self) + } + } + + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { + if let ty::ConstKind::Infer(_) = c.kind() { + ty::Const::new_placeholder(self.tcx, ty::PlaceholderConst { + universe: ty::UniverseIndex::ROOT, + bound: ty::BoundVar::from_u32({ + let idx = self.idx; + self.idx += 1; + idx + }), + }) + } else { + c.super_fold_with(self) + } + } + } + + args.fold_with(&mut ReplaceParamAndInferWithPlaceholder { tcx, idx: 0 }) +} + /// Normalizes the predicates and checks whether they hold in an empty environment. If this /// returns true, then either normalize encountered an error or one of the predicates did not /// hold. Used when creating vtables to check for unsatisfiable methods. This should not be |
