introduce TypingModeEqWrapper and make TypingMode !Eq

This commit is contained in:
Jana Dönszelmann
2026-04-09 13:44:43 +02:00
parent 7659cec4ed
commit 0e0d12ccb3
18 changed files with 205 additions and 46 deletions
@@ -9,7 +9,7 @@
use rustc_hir::LangItem;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, AdtDef, Ty};
use rustc_middle::ty::{self, AdtDef, Ty, TypingModeEqWrapper};
use rustc_middle::{bug, mir};
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
use tracing::instrument;
@@ -104,10 +104,10 @@ fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
// typeck results without causing query cycles, we should use this here instead of defining
// opaque types.
let typing_env = ty::TypingEnv {
typing_mode: ty::TypingMode::analysis_in_body(
typing_mode: TypingModeEqWrapper(ty::TypingMode::analysis_in_body(
cx.tcx,
cx.body.source.def_id().expect_local(),
),
)),
param_env: cx.typing_env.param_env,
};
let (infcx, param_env) = cx.tcx.infer_ctxt().build_with_typing_env(typing_env);
@@ -371,10 +371,20 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
// This shouldn't be used for statics, since statics are conceptually places,
// not values -- so what we do here could break pointer identity.
assert!(key.value.promoted.is_some() || !tcx.is_static(key.value.instance.def_id()));
// Const eval always happens in PostAnalysis mode . See the comment in
// `InterpCx::new` for more details.
debug_assert_eq!(key.typing_env.typing_mode, ty::TypingMode::PostAnalysis);
if cfg!(debug_assertions) {
match key.typing_env.typing_mode.0 {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!(
"Const eval should always happens in PostAnalysis mode. See the comment in `InterpCx::new` for more details."
)
}
}
// Make sure we format the instance even if we do not print it.
// This serves as a regression test against an ICE on printing.
// The next two lines concatenated contain some discussion:
@@ -236,9 +236,18 @@ pub(crate) fn eval_to_valtree<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
cid: GlobalId<'tcx>,
) -> EvalToValTreeResult<'tcx> {
// Const eval always happens in PostAnalysis mode . See the comment in
// `InterpCx::new` for more details.
debug_assert_eq!(typing_env.typing_mode, ty::TypingMode::PostAnalysis);
#[cfg(debug_assertions)]
match typing_env.typing_mode.0 {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!(
"Const eval should always happens in PostAnalysis mode. See the comment in `InterpCx::new` for more details."
)
}
}
let const_alloc = tcx.eval_to_allocation_raw(typing_env.as_query_input(cid))?;
// FIXME Need to provide a span to `eval_to_valtree`
@@ -243,7 +243,21 @@ pub fn new(
// opaque types. This is needed for trivial things like `size_of`, but also for using associated
// types that are not specified in the opaque type. We also use MIR bodies whose opaque types have
// already been revealed, so we'd be able to at least partially observe the hidden types anyways.
debug_assert_matches!(typing_env.typing_mode, ty::TypingMode::PostAnalysis);
debug_assert_matches!(typing_env.typing_mode.0, ty::TypingMode::PostAnalysis);
if cfg!(debug_assertions) {
match typing_env.typing_mode.0 {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
use rustc_middle::bug;
bug!("Const eval should always happens in PostAnalysis mode.")
}
}
}
InterpCx {
machine,
tcx: tcx.at(root_span),
@@ -13,6 +13,7 @@
self, BoundVar, GenericArg, InferConst, List, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeVisitableExt,
};
use rustc_type_ir::TypingModeEqWrapper;
use smallvec::SmallVec;
use tracing::debug;
@@ -72,7 +73,7 @@ pub fn canonicalize_query<V>(
query_state,
)
.unchecked_map(|(param_env, value)| param_env.and(value));
CanonicalQueryInput { canonical, typing_mode: self.typing_mode() }
CanonicalQueryInput { canonical, typing_mode: TypingModeEqWrapper(self.typing_mode()) }
}
/// Canonicalizes a query *response* `V`. When we canonicalize a
+4 -3
View File
@@ -33,6 +33,7 @@
TypeSuperFoldable, TypeVisitable, TypeVisitableExt, TypingEnv, TypingMode, fold_regions,
};
use rustc_span::{DUMMY_SP, Span, Symbol};
use rustc_type_ir::TypingModeEqWrapper;
use snapshot::undo_log::InferCtxtUndoLogs;
use tracing::{debug, instrument};
use type_variable::TypeVariableOrigin;
@@ -564,7 +565,7 @@ pub fn build_with_canonical<T>(
where
T: TypeFoldable<TyCtxt<'tcx>>,
{
let infcx = self.build(input.typing_mode);
let infcx = self.build(input.typing_mode.0);
let (value, args) = infcx.instantiate_canonical(span, &input.canonical);
(infcx, value, args)
}
@@ -573,7 +574,7 @@ pub fn build_with_typing_env(
mut self,
TypingEnv { typing_mode, param_env }: TypingEnv<'tcx>,
) -> (InferCtxt<'tcx>, ty::ParamEnv<'tcx>) {
(self.build(typing_mode), param_env)
(self.build(typing_mode.0), param_env)
}
pub fn build(&mut self, typing_mode: TypingMode<'tcx>) -> InferCtxt<'tcx> {
@@ -1376,7 +1377,7 @@ pub fn typing_env(&self, param_env: ty::ParamEnv<'tcx>) -> ty::TypingEnv<'tcx> {
| ty::TypingMode::PostBorrowckAnalysis { .. }
| ty::TypingMode::PostAnalysis) => mode,
};
ty::TypingEnv { typing_mode, param_env }
ty::TypingEnv { typing_mode: TypingModeEqWrapper(typing_mode), param_env }
}
/// Similar to [`Self::canonicalize_query`], except that it returns
+10 -3
View File
@@ -24,8 +24,12 @@
use rustc_middle::middle::privacy::EffectiveVisibilities;
use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, Printer, with_no_trimmed_paths};
use rustc_middle::ty::{self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode};
use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintExpectationId, LintId};
use rustc_middle::ty::{
self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode, TypingModeEqWrapper,
};
use rustc_session::lint::{
CheckLintNameResult, FutureIncompatibleInfo, Lint, LintExpectationId, LintId,
};
use rustc_session::{DynLintStore, Session};
use rustc_span::edit_distance::find_best_match_for_names;
use rustc_span::{Ident, Span, Symbol, sym};
@@ -637,7 +641,10 @@ pub fn typing_mode(&self) -> TypingMode<'tcx> {
}
pub fn typing_env(&self) -> TypingEnv<'tcx> {
TypingEnv { typing_mode: self.typing_mode(), param_env: self.param_env }
TypingEnv {
typing_mode: TypingModeEqWrapper(self.typing_mode()),
param_env: self.param_env,
}
}
pub fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool {
+2 -2
View File
@@ -33,7 +33,7 @@
use crate::ty::print::{FmtPrinter, Printer, pretty_print_const, with_no_trimmed_paths};
use crate::ty::{
self, GenericArg, GenericArgsRef, Instance, InstanceKind, List, Ty, TyCtxt, TypeVisitableExt,
TypingEnv, UserTypeAnnotationIndex,
TypingEnv, TypingModeEqWrapper, UserTypeAnnotationIndex,
};
mod basic_blocks;
@@ -416,7 +416,7 @@ pub fn typing_env(&self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx> {
match self.phase {
// FIXME(#132279): we should reveal the opaques defined in the body during analysis.
MirPhase::Built | MirPhase::Analysis(_) => TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
typing_mode: TypingModeEqWrapper(ty::TypingMode::non_body_analysis()),
param_env: tcx.param_env(self.source.def_id()),
},
MirPhase::Runtime(_) => TypingEnv::post_analysis(tcx, self.source.def_id()),
+18 -7
View File
@@ -105,7 +105,8 @@
AliasTy, AliasTyKind, Article, Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy,
BoundTyKind, BoundVariableKind, CanonicalPolyFnSig, CoroutineArgsExt, EarlyBinder, FnSig,
InlineConstArgs, InlineConstArgsParts, ParamConst, ParamTy, PlaceholderConst,
PlaceholderRegion, PlaceholderType, PolyFnSig, TyKind, TypeAndMut, TypingMode, UpvarArgs,
PlaceholderRegion, PlaceholderType, PolyFnSig, TyKind, TypeAndMut, TypingMode,
TypingModeEqWrapper, UpvarArgs,
};
pub use self::trait_def::TraitDef;
pub use self::typeck_results::{
@@ -980,7 +981,7 @@ pub struct ParamEnvAnd<'tcx, T> {
pub struct TypingEnv<'tcx> {
#[type_foldable(identity)]
#[type_visitable(ignore)]
pub typing_mode: TypingMode<'tcx>,
pub typing_mode: TypingModeEqWrapper<'tcx>,
pub param_env: ParamEnv<'tcx>,
}
@@ -993,7 +994,10 @@ impl<'tcx> TypingEnv<'tcx> {
/// use `TypingMode::PostAnalysis`, they may still have where-clauses
/// in scope.
pub fn fully_monomorphized() -> TypingEnv<'tcx> {
TypingEnv { typing_mode: TypingMode::PostAnalysis, param_env: ParamEnv::empty() }
TypingEnv {
typing_mode: TypingModeEqWrapper(TypingMode::PostAnalysis),
param_env: ParamEnv::empty(),
}
}
/// Create a typing environment for use during analysis outside of a body.
@@ -1006,7 +1010,10 @@ pub fn non_body_analysis(
def_id: impl IntoQueryKey<DefId>,
) -> TypingEnv<'tcx> {
let def_id = def_id.into_query_key();
TypingEnv { typing_mode: TypingMode::non_body_analysis(), param_env: tcx.param_env(def_id) }
TypingEnv {
typing_mode: TypingModeEqWrapper(TypingMode::non_body_analysis()),
param_env: tcx.param_env(def_id),
}
}
pub fn post_analysis(tcx: TyCtxt<'tcx>, def_id: impl IntoQueryKey<DefId>) -> TypingEnv<'tcx> {
@@ -1018,8 +1025,12 @@ pub fn post_analysis(tcx: TyCtxt<'tcx>, def_id: impl IntoQueryKey<DefId>) -> Typ
/// opaque types in the `param_env`.
pub fn with_post_analysis_normalized(self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx> {
let TypingEnv { typing_mode, param_env } = self;
if let TypingMode::PostAnalysis = typing_mode {
return self;
match typing_mode.0 {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {}
TypingMode::PostAnalysis => return self,
}
// No need to reveal opaques with the new solver enabled,
@@ -1029,7 +1040,7 @@ pub fn with_post_analysis_normalized(self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx>
} else {
ParamEnv::new(tcx.reveal_opaque_types_in_bounds(param_env.caller_bounds()))
};
TypingEnv { typing_mode: TypingMode::PostAnalysis, param_env }
TypingEnv { typing_mode: TypingModeEqWrapper(TypingMode::PostAnalysis), param_env }
}
/// Combine this typing environment with the given `value` to be used by
+1
View File
@@ -40,6 +40,7 @@
pub type Binder<'tcx, T> = ir::Binder<TyCtxt<'tcx>, T>;
pub type EarlyBinder<'tcx, T> = ir::EarlyBinder<TyCtxt<'tcx>, T>;
pub type TypingMode<'tcx> = ir::TypingMode<TyCtxt<'tcx>>;
pub type TypingModeEqWrapper<'tcx> = ir::TypingModeEqWrapper<TyCtxt<'tcx>>;
pub type Placeholder<'tcx, T> = ir::Placeholder<TyCtxt<'tcx>, T>;
pub type PlaceholderRegion<'tcx> = ir::PlaceholderRegion<TyCtxt<'tcx>>;
pub type PlaceholderType<'tcx> = ir::PlaceholderType<TyCtxt<'tcx>>;
@@ -548,7 +548,16 @@ fn move_paths_for_fields(
let subpath = self.elaborator.field_subpath(variant_path, field_idx);
let tcx = self.tcx();
assert_eq!(self.elaborator.typing_env().typing_mode, ty::TypingMode::PostAnalysis);
match self.elaborator.typing_env().typing_mode.0 {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!()
}
}
let field_ty = field.ty(tcx, args);
// We silently leave an unnormalized type here to support polymorphic drop
// elaboration for users of rustc internal APIs
@@ -17,7 +17,7 @@
use rustc_type_ir::relate::solver_relating::RelateExt;
use rustc_type_ir::{
self as ty, Canonical, CanonicalVarKind, CanonicalVarValues, InferCtxtLike, Interner,
TypeFoldable,
TypeFoldable, TypingModeEqWrapper,
};
use tracing::instrument;
@@ -66,7 +66,10 @@ pub(super) fn canonicalize_goal<D, I>(
predefined_opaques_in_body: delegate.cx().mk_predefined_opaques_in_body(opaque_types),
},
);
let query_input = ty::CanonicalQueryInput { canonical, typing_mode: delegate.typing_mode() };
let query_input = ty::CanonicalQueryInput {
canonical,
typing_mode: TypingModeEqWrapper(delegate.typing_mode()),
};
(orig_values, query_input)
}
@@ -466,15 +466,20 @@ pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
// as we may want to weaken inference guidance in the future and don't want
// to worry about causing major performance regressions when doing so.
// See trait-system-refactor-initiative#226 for some ideas here.
if TypingMode::Coherence == self.typing_mode()
|| !candidates.iter().any(|c| {
let assemble_impls = match self.typing_mode() {
TypingMode::Coherence => true,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => !candidates.iter().any(|c| {
matches!(
c.source,
CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)
| CandidateSource::AliasBound(_)
) && has_no_inference_or_external_constraints(c.result)
})
{
}),
};
if assemble_impls {
self.assemble_impl_candidates(goal, &mut candidates);
self.assemble_object_bound_candidates(goal, &mut candidates);
}
@@ -62,7 +62,7 @@ fn initial_provisional_result(
// See `tests/ui/traits/next-solver/cycles/unproductive-in-coherence.rs` for an
// example where this would matter. We likely should change these cycles to `NoSolution`
// even in coherence once this is a bit more settled.
PathKind::Inductive => match input.typing_mode {
PathKind::Inductive => match input.typing_mode.0 {
TypingMode::Coherence => {
response_no_constraints(cx, input, Certainty::overflow(false))
}
@@ -25,11 +25,17 @@ pub fn evaluate_host_effect_obligation<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
if selcx.infcx.typing_mode() == TypingMode::Coherence {
span_bug!(
obligation.cause.span,
"should not select host obligation in old solver in intercrate mode"
);
match selcx.infcx.typing_mode() {
TypingMode::Coherence => {
span_bug!(
obligation.cause.span,
"should not select host obligation in old solver in intercrate mode"
);
}
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => {}
}
let ref obligation = selcx.infcx.resolve_vars_if_possible(obligation.clone());
+1 -1
View File
@@ -154,7 +154,7 @@ fn resolve_associated_item<'tcx>(
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
match typing_env.typing_mode {
match typing_env.typing_mode.0 {
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
+2 -2
View File
@@ -11,7 +11,7 @@
use crate::data_structures::HashMap;
use crate::inherent::*;
use crate::{self as ty, Interner, TypingMode, UniverseIndex};
use crate::{self as ty, Interner, TypingModeEqWrapper, UniverseIndex};
#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, V)]
#[derive_where(Copy; I: Interner, V: Copy)]
@@ -21,7 +21,7 @@
)]
pub struct CanonicalQueryInput<I: Interner, V> {
pub canonical: Canonical<I, V>,
pub typing_mode: TypingMode<I>,
pub typing_mode: TypingModeEqWrapper<I>,
}
impl<I: Interner, V: Eq> Eq for CanonicalQueryInput<I, V> {}
+86 -4
View File
@@ -1,3 +1,5 @@
use std::hash::{Hash, Hasher};
use derive_where::derive_where;
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext};
@@ -18,11 +20,28 @@
///
/// If neither of these functions are available, feel free to reach out to
/// t-types for help.
#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)]
///
/// Because typing rules get subtly different based on what typing mode we're in,
/// subtle enough that changing the behavior of typing modes can sometimes cause
/// changes that we don't even have tests for, we'd like to enforce the rule that
/// any place where we specialize behavior based on the typing mode, we match
/// *exhaustively* on the typing mode. That way, it's easy to determine all the
/// places that must change when anything about typing modes changes.
///
/// Hence, `TypingMode` does not implement `Eq`, though [`TypingModeEqWrapper`] is available
/// in the rare case that you do need this. Most cases where this currently matters is
/// where we pass typing modes through the query system and want to cache based on it.
/// See also `#[rustc_must_match_exhaustively]`, which tries to detect non-exhaustive
/// matches.
///
/// Since matching on typing mode to single out `Coherence` is so common, and `Coherence`
/// is so different from the other modes: see also [`is_coherence`](TypingMode::is_coherence)
#[derive_where(Clone, Copy, Hash, Debug; I: Interner)]
#[cfg_attr(
feature = "nightly",
derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext)
)]
#[cfg_attr(not(bootstrap), rustc_must_match_exhaustively)]
pub enum TypingMode<I: Interner> {
/// When checking whether impls overlap, we check whether any obligations
/// are guaranteed to never hold when unifying the impls. This requires us
@@ -90,9 +109,64 @@ pub enum TypingMode<I: Interner> {
PostAnalysis,
}
impl<I: Interner> Eq for TypingMode<I> {}
/// We want to highly discourage using equality checks on typing modes.
/// Instead you should match, **exhaustively**, so when we ever modify the enum we get a compile
/// error. Only use `TypingModeEqWrapper` when you really really really have to.
/// Prefer unwrapping `TypingModeEqWrapper` in apis that should return a `TypingMode` whenever
/// possible, and if you ever get an `TypingModeEqWrapper`, prefer unwrapping it and matching on it **exhaustively**.
#[derive_where(Clone, Copy, Debug; I: Interner)]
#[cfg_attr(
feature = "nightly",
derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext)
)]
pub struct TypingModeEqWrapper<I: Interner>(pub TypingMode<I>);
impl<I: Interner> Hash for TypingModeEqWrapper<I> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<I: Interner> PartialEq for TypingModeEqWrapper<I> {
fn eq(&self, other: &Self) -> bool {
match (self.0, other.0) {
(TypingMode::Coherence, TypingMode::Coherence) => true,
(
TypingMode::Analysis { defining_opaque_types_and_generators: l },
TypingMode::Analysis { defining_opaque_types_and_generators: r },
) => l == r,
(
TypingMode::Borrowck { defining_opaque_types: l },
TypingMode::Borrowck { defining_opaque_types: r },
) => l == r,
(
TypingMode::PostBorrowckAnalysis { defined_opaque_types: l },
TypingMode::PostBorrowckAnalysis { defined_opaque_types: r },
) => l == r,
(TypingMode::PostAnalysis, TypingMode::PostAnalysis) => true,
_ => false,
}
}
}
impl<I: Interner> Eq for TypingModeEqWrapper<I> {}
impl<I: Interner> TypingMode<I> {
/// There are a bunch of places in the compiler where we single out `Coherence`,
/// and alter behavior. We'd like to *always* match on `TypingMode` exhaustively,
/// but not having this method leads to a bunch of noisy code.
///
/// See also the documentation on [`TypingMode`] about exhaustive matching.
pub fn is_coherence(&self) -> bool {
match self {
TypingMode::Coherence => true,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => false,
}
}
/// Analysis outside of a body does not define any opaque types.
pub fn non_body_analysis() -> TypingMode<I> {
TypingMode::Analysis { defining_opaque_types_and_generators: Default::default() }
@@ -127,6 +201,7 @@ pub fn borrowck(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
pub fn post_borrowck_analysis(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
let defined_opaque_types = cx.opaque_types_defined_by(body_def_id);
if defined_opaque_types.is_empty() {
TypingMode::non_body_analysis()
} else {
@@ -322,6 +397,13 @@ pub fn may_use_unstable_feature<'a, I: Interner, Infcx>(
// Note: `feature_bound_holds_in_crate` does not consider a feature to be enabled
// if we are in std/core even if there is a corresponding `feature` attribute on the crate.
(infcx.typing_mode() == TypingMode::PostAnalysis)
|| infcx.cx().features().feature_bound_holds_in_crate(symbol)
match infcx.typing_mode() {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {
infcx.cx().features().feature_bound_holds_in_crate(symbol)
}
TypingMode::PostAnalysis => true,
}
}