From d04e0a492a92c58c41adf03319e3a92aa2d39a43 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Sun, 4 Oct 2026 14:53:53 +0330 Subject: [PATCH 1/2] copy fulfillment implementation into rustc_next_trait_solver Signed-off-by: Amirhossein Akhlaghpour --- .../src/solve/fulfill.rs | 414 ++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 compiler/rustc_next_trait_solver/src/solve/fulfill.rs diff --git a/compiler/rustc_next_trait_solver/src/solve/fulfill.rs b/compiler/rustc_next_trait_solver/src/solve/fulfill.rs new file mode 100644 index 0000000000000..5e834c8c4059d --- /dev/null +++ b/compiler/rustc_next_trait_solver/src/solve/fulfill.rs @@ -0,0 +1,414 @@ +use std::marker::PhantomData; +use std::mem; + +use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::{ + FromSolverError, PredicateObligation, PredicateObligations, TraitEngine, TraitErrors, +}; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode}; +use rustc_next_trait_solver::solve::fast_path::compute_goal_fast_path; +use rustc_next_trait_solver::solve::{ + GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt as _, StalledOnCoroutines, +}; +use thin_vec::ThinVec; +use tracing::instrument; + +use self::derive_errors::*; +use super::Certainty; +use super::delegate::SolverDelegate; +use crate::error_reporting::InferCtxtErrorExt; +use crate::traits::{FulfillmentError, ScrubbedTraitError}; + +mod derive_errors; + +// `ThinVec` is important for performance, but not for the usual memory layout reasons. +// `try_evaluate_obligations` is extremely hot and uses `retain_mut`. `ThinVec::retain_mut` is +// simple and sub-optimal in terms of how it moves elements, but it can be inlined. +// `Vec::retain_mut` is more sophisticated and minimizes element moves, but also contains more code +// and doesn't get inlined in `try_evaluate_obligations`, giving worse performance overall. +type PendingObligations<'tcx> = + ThinVec<(PredicateObligation<'tcx>, Option>>)>; + +/// A trait engine using the new trait solver. +/// +/// This is mostly identical to how `evaluate_all` works inside of the +/// solver, except that the requirements are slightly different. +/// +/// Unlike `evaluate_all` it is possible to add new obligations later on +/// and we also have to track diagnostics information by using `Obligation` +/// instead of `Goal`. +/// +/// It is also likely that we want to use slightly different datastructures +/// here as this will have to deal with far more root goals than `evaluate_all`. +pub struct FulfillmentCtxt<'tcx, E: 'tcx> { + obligations: ObligationStorage<'tcx>, + + /// The snapshot in which this context was created. Using the context + /// outside of this snapshot leads to subtle bugs if the snapshot + /// gets rolled back. Because of this we explicitly check that we only + /// use the context in exactly this snapshot. + usable_in_snapshot: usize, + _errors: PhantomData, +} + +#[derive(Default, Debug)] +struct ObligationStorage<'tcx> { + pending: PendingObligations<'tcx>, +} + +impl<'tcx> ObligationStorage<'tcx> { + fn register( + &mut self, + obligation: PredicateObligation<'tcx>, + stalled_on: Option>>, + ) { + self.pending.push((obligation, stalled_on)); + } + + fn has_pending_obligations(&self) -> bool { + !self.pending.is_empty() + } + + fn clone_pending(&self) -> PredicateObligations<'tcx> { + self.pending.iter().map(|(o, _)| o.clone()).collect() + } + + fn clone_pending_filtered(&self, f: F) -> PredicateObligations<'tcx> + where + F: FnMut(&&(PredicateObligation<'tcx>, Option>>)) -> bool, + { + self.pending.iter().filter(f).map(|(o, _)| o.clone()).collect() + } + + fn drain_pending( + &mut self, + cond: impl Fn(&PredicateObligation<'tcx>, &Option>>) -> bool, + ) -> PendingObligations<'tcx> { + let (unstalled, pending) = + mem::take(&mut self.pending).into_iter().partition(|(o, s)| cond(o, s)); + self.pending = pending; + unstalled + } +} + +impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { + pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx, E> { + assert!( + infcx.next_trait_solver(), + "new trait solver fulfillment context created when \ + infcx is set up for old trait solver" + ); + FulfillmentCtxt { + obligations: Default::default(), + usable_in_snapshot: infcx.num_open_snapshots(), + _errors: PhantomData, + } + } + + fn inspect_evaluated_obligation( + infcx: &InferCtxt<'tcx>, + obligation: &PredicateObligation<'tcx>, + result: &Result>, NoSolution>, + ) { + if let Some(inspector) = infcx.obligation_inspector.get() { + let result = match result { + Ok(GoalEvaluation { certainty, .. }) => Ok(*certainty), + Err(NoSolution) => Err(NoSolution), + }; + (inspector)(infcx, &obligation, result); + } + } +} + +impl<'tcx, E> TraitEngine<'tcx, E> for FulfillmentCtxt<'tcx, E> +where + E: FromSolverError<'tcx, NextSolverError<'tcx>>, +{ + #[instrument(level = "trace", skip(self, infcx))] + fn register_predicate_obligation( + &mut self, + infcx: &InferCtxt<'tcx>, + obligation: PredicateObligation<'tcx>, + ) { + assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); + + let delegate = <&SolverDelegate<'tcx>>::from(infcx); + if let Some(GoalEvaluation { goal: _, certainty, has_changed: _, stalled_on }) = + compute_goal_fast_path(delegate, obligation.as_goal(), obligation.cause.span) + { + // If we can take the fast path, don't even bother adding the goal to obligations, + // or if `Certainty::Maybe`, add it with precise stalled_on information. + match certainty { + Certainty::Yes => {} + Certainty::Maybe(_) => { + self.obligations.register(obligation, stalled_on); + } + } + } else { + self.obligations.register(obligation, None); + } + } + + #[inline] + fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { + if self.obligations.pending.is_empty() { + // Typically in more than 99.9% of cases this condition is true, therefore we outline + // the other case. + TraitErrors::NoErrors + } else { + TraitErrors::HasErrors(collect_remaining_errors_impl(self, infcx)) + } + } + + fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { + assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); + let mut errors = TraitErrors::NoErrors; + let delegate = <&SolverDelegate<'tcx>>::from(infcx); + loop { + let mut any_changed = false; + + self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| { + // Common case: still stalled; keep the obligation. This path is extremely hot in + // some cases; there can be thousands of pending obligations. + if let Some(stalled_on) = opt_stalled_on + && delegate.goal_remains_stalled(stalled_on) + { + return true; + } + + let result = delegate.evaluate_root_goal( + obligation.as_goal(), + obligation.cause.span, + opt_stalled_on.take(), + ); + Self::inspect_evaluated_obligation(infcx, &obligation, &result); + let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result { + Ok(result) => result, + Err(NoSolution) => { + errors.push(E::from_solver_error( + infcx, + NextSolverError::TrueError(obligation.clone()), + )); + return false; + } + }; + + // We've resolved the goal in `evaluate_root_goal`, avoid redoing this work + // in the next iteration. This does not resolve the inference variables + // constrained by evaluating the goal. + obligation.predicate = goal.predicate; + if has_changed == HasChanged::Yes { + if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) { + // We limit the total count of inference progress to avoid hang so we don't + // try to recover from this. + // It's more complicated to collect all overflows thus we stopped doing that. + // Eager aborting is also what the old solver does. + // + // Note: it's incredibly rare to actually encounter fulfillment overflow + // as a single obligation would have to result in different inference progress + // a `recursion_depth` number of times. This mostly happens in bugs or with + // `Subtype` obligations because we no longer use the `sub_unification_table` + // in generalization. + infcx.err_ctxt().report_overflow_obligation(obligation, true); + } else { + // We increment the recursion depth here to track the number of times + // this goal has resulted in inference progress. This doesn't precisely + // model the way that we track recursion depth in the old solver due + // to the fact that we only process root obligations, but it is a good + // approximation and should only result in fulfillment overflow in + // pathological cases. + obligation.recursion_depth += 1; + any_changed = true; + } + } + + match certainty { + Certainty::Yes => { + // Goals may depend on structural identity. Region uniquification at the + // start of MIR borrowck may cause things to no longer be so, potentially + // causing an ICE. + // + // While we uniquify root goals in HIR this does not handle cases where + // regions are hidden inside of a type or const inference variable. + // + // FIXME(-Znext-solver): This does not handle inference variables hidden + // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the + // storage, we can also rely on structural identity of `?x` even if we + // later uniquify it in MIR borrowck. + if infcx.in_hir_typeck + && (obligation.has_non_region_infer() || obligation.has_free_regions()) + { + infcx.push_hir_typeck_potentially_region_dependent_goal( + obligation.clone(), + ); + } + false + } + Certainty::Maybe(_) => { + // Update `opt_stalled_on` goal, for the next retain_mut, because we are + // running until a fixpoint. + *opt_stalled_on = stalled_on; + true + } + } + }); + + if !any_changed { + break; + } + } + + errors + } + + fn has_pending_obligations(&self) -> bool { + self.obligations.has_pending_obligations() + } + + fn pending_obligations(&self) -> PredicateObligations<'tcx> { + self.obligations.clone_pending() + } + + fn pending_obligations_potentially_referencing_sub_root( + &self, + infcx: &InferCtxt<'tcx>, + vid: ty::TyVid, + ) -> PredicateObligations<'tcx> { + // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. + if infcx.tcx.disable_trait_solver_fast_paths() { + return self.obligations.clone_pending(); + } + self.obligations.clone_pending_filtered(|(_, stalled_on)| { + let Some(stalled_on) = stalled_on else { return true }; + // Don't reuse the sub-unification roots cached on `stalled_on`: + // a later sub-unification merge can have changed which root + // each stalled var belongs to, so the cached info can be stale. + // Walk `stalled_vars` and recompute the current root instead. + // + // Conservative here: if a stalled var no longer resolves to an + // infer var, some unification happened, so the goal is no longer + // stalled. Include it to be re-evaluated downstream. + stalled_on.stalled_vars.iter().filter_map(|arg| arg.as_type(infcx.tcx)).any(|ty| { + match *infcx.shallow_resolve(ty).kind() { + ty::Infer(ty::TyVar(tv)) => infcx.sub_unification_table_root_var(tv) == vid, + _ => true, + } + }) + }) + } + + fn pending_obligations_potentially_referencing_float_infer( + &self, + infcx: &InferCtxt<'tcx>, + ) -> PredicateObligations<'tcx> { + // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. + if infcx.tcx.disable_trait_solver_fast_paths() { + return self.obligations.clone_pending(); + } + + self.obligations.clone_pending_filtered(|(_, stalled_on)| { + let Some(stalled_on) = stalled_on else { return true }; + // If the stalled vars don't have float infers, the nested goals won't + // have them either. We only create float infers for user written literals. + stalled_on + .stalled_vars + .iter() + .filter_map(|arg| arg.as_type(infcx.tcx)) + .any(|ty| matches!(infcx.shallow_resolve(ty).kind(), ty::Infer(ty::FloatVar(_)))) + }) + } + + fn drain_stalled_obligations_for_coroutines( + &mut self, + infcx: &InferCtxt<'tcx>, + ) -> PredicateObligations<'tcx> { + let stalled_coroutines = match infcx.typing_mode_raw().assert_not_erased() { + TypingMode::Typeck { defining_opaque_types_and_generators } => { + defining_opaque_types_and_generators + } + TypingMode::Coherence + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } + | TypingMode::PostBorrowck { defined_opaque_types: _ } + | TypingMode::Reflection + | TypingMode::PostAnalysis + | TypingMode::Codegen => return Default::default(), + }; + + if stalled_coroutines.is_empty() { + return Default::default(); + } + + self.obligations + .drain_pending(|_, stalled_on| { + stalled_on.as_ref().is_some_and(|s| { + match s.stalled_maybe_info.stalled_on_coroutines { + StalledOnCoroutines::Yes => true, + StalledOnCoroutines::No => false, + } + }) + }) + .into_iter() + .map(|(o, _)| o) + .collect() + } +} + +#[cold] +#[inline(never)] +fn collect_remaining_errors_impl<'tcx, E>( + cx: &mut FulfillmentCtxt<'tcx, E>, + infcx: &InferCtxt<'tcx>, +) -> ThinVec +where + E: FromSolverError<'tcx, NextSolverError<'tcx>>, +{ + cx.obligations + .pending + .drain(..) + .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) + .map(|e| E::from_solver_error(infcx, e)) + .collect() +} + +pub enum NextSolverError<'tcx> { + TrueError(PredicateObligation<'tcx>), + Ambiguity(PredicateObligation<'tcx>), +} + +impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tcx> { + fn from_solver_error(infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self { + match error { + NextSolverError::TrueError(obligation) => { + fulfillment_error_for_no_solution(infcx, obligation) + } + NextSolverError::Ambiguity(obligation) => { + fulfillment_error_for_stalled(infcx, obligation) + } + } + } +} + +impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for ScrubbedTraitError<'tcx> { + fn from_solver_error(_infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self { + match error { + NextSolverError::TrueError(_) => ScrubbedTraitError::TrueError, + NextSolverError::Ambiguity(_) => ScrubbedTraitError::Ambiguity, + } + } +} + +// Some types are used a lot. Make sure they don't unintentionally get bigger. +#[cfg(target_pointer_width = "64")] +mod size_asserts { + use rustc_data_structures::static_assert_size; + + use super::*; + // tidy-alphabetical-start + // Before #160005 this pair was greater than 128 bytes, which triggered the use of (slow) + // `memcpy` for moving elements of `PendingObligations`. Then #160479 greatly reduced the + // number of `memcpy` operations in `try_evaluate_obligations`. So the size of this pair is + // much less important than it was, but still shouldn't be changed without some thought. + static_assert_size!((PredicateObligation<'_>, Option>>), 104); + // tidy-alphabetical-end +} From 562220e6de7c7a2a93bc310c291e22c6711e24b8 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Sun, 4 Oct 2026 15:07:01 +0330 Subject: [PATCH 2/2] share fulfillment with rustc_next_trait_solver Signed-off-by: Amirhossein Akhlaghpour --- .../src/diagnostics/region_errors.rs | 5 +- compiler/rustc_infer/src/traits/mod.rs | 150 +-------- .../src/traits/structural_impls.rs | 16 - compiler/rustc_infer/src/traits/util.rs | 44 +-- .../src/ty/context/impl_interner.rs | 13 + .../rustc_middle/src/ty/elaborate_impl.rs | 45 +++ .../src/solve/fulfill.rs | 293 +++++++----------- .../rustc_next_trait_solver/src/solve/mod.rs | 2 + .../src/error_reporting/traits/suggestions.rs | 10 +- .../src/solve/fulfill.rs | 293 +++--------------- .../src/traits/select/candidate_assembly.rs | 41 ++- .../src/traits/select/confirmation.rs | 24 +- .../src/traits/select/mod.rs | 2 +- compiler/rustc_type_ir/src/inherent.rs | 9 + compiler/rustc_type_ir/src/interner.rs | 1 + compiler/rustc_type_ir/src/solve/fulfill.rs | 121 ++++++++ compiler/rustc_type_ir/src/solve/mod.rs | 2 + 17 files changed, 398 insertions(+), 673 deletions(-) create mode 100644 compiler/rustc_type_ir/src/solve/fulfill.rs diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index c101191102979..9ff35d951802e 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -1327,10 +1327,9 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { let ocx = ObligationCtxt::new(&self.infcx); ocx.register_obligations(clauses.iter().map(|(clause, span)| { trace!(?clause); - Obligation::misc( + Obligation::new( tcx, - span, - self.mir_def_id(), + rustc_middle::traits::ObligationCause::misc(span, self.mir_def_id()), self.infcx.param_env, clause.skip_norm_wip(), ) diff --git a/compiler/rustc_infer/src/traits/mod.rs b/compiler/rustc_infer/src/traits/mod.rs index b3552bc68d517..0f7dbbd3a1e93 100644 --- a/compiler/rustc_infer/src/traits/mod.rs +++ b/compiler/rustc_infer/src/traits/mod.rs @@ -7,17 +7,10 @@ mod project; mod structural_impls; pub mod util; -use std::cmp; -use std::hash::{Hash, Hasher}; - -use hir::def_id::LocalDefId; -use rustc_hir as hir; -use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::Certainty; pub use rustc_middle::traits::*; -use rustc_middle::ty::{self, Ty, TyCtxt, Upcast}; -use rustc_span::Span; +use rustc_middle::ty::{self, TyCtxt}; use thin_vec::ThinVec; pub use self::engine::{FromSolverError, ScrubbedTraitError, TraitEngine, TraitErrors}; @@ -28,63 +21,9 @@ pub use self::project::{ }; use crate::infer::InferCtxt; -/// An `Obligation` represents some trait reference (e.g., `i32: Eq`) for -/// which the "impl_source" must be found. The process of finding an "impl_source" is -/// called "resolving" the `Obligation`. This process consists of -/// either identifying an `impl` (e.g., `impl Eq for i32`) that -/// satisfies the obligation, or else finding a bound that is in -/// scope. The eventual result is usually a `Selection` (defined below). -#[derive(Clone, TypeFoldable, TypeVisitable)] -pub struct Obligation<'tcx, T> { - /// The reason we have to prove this thing. - /// FIXME: we shouldn't ignore the cause but instead change the affected visitors - /// to only visit predicates manually. - #[type_foldable(identity)] - #[type_visitable(ignore)] - pub cause: ObligationCause<'tcx>, - - /// The environment in which we should prove this thing. - pub param_env: ty::ParamEnv<'tcx>, - - /// The thing we are trying to prove. - pub predicate: T, - - /// If we started proving this as a result of trying to prove - /// something else, track the total depth to ensure termination. - /// If this goes over a certain threshold, we abort compilation -- - /// in such cases, we can not say whether or not the predicate - /// holds for certain. Stupid halting problem; such a drag. - #[type_foldable(identity)] - #[type_visitable(ignore)] - pub recursion_depth: usize, -} - -impl<'tcx, T: Copy> Obligation<'tcx, T> { - pub fn as_goal(&self) -> solve::Goal<'tcx, T> { - solve::Goal { param_env: self.param_env, predicate: self.predicate } - } -} - -impl<'tcx, T: PartialEq> PartialEq> for Obligation<'tcx, T> { - #[inline] - fn eq(&self, other: &Obligation<'tcx, T>) -> bool { - // Ignore `cause` and `recursion_depth`. This is a small performance - // win for a few crates, and a huge performance win for the crate in - // https://github.com/rust-lang/rustc-perf/pull/1680, which greatly - // stresses the trait system. - self.param_env == other.param_env && self.predicate == other.predicate - } -} - -impl Eq for Obligation<'_, T> {} - -impl Hash for Obligation<'_, T> { - fn hash(&self, state: &mut H) -> () { - // See the comment on `Obligation::eq`. - self.param_env.hash(state); - self.predicate.hash(state); - } -} +/// An obligation represents a predicate which must be proven in a +/// particular parameter environment. +pub type Obligation<'tcx, T> = rustc_type_ir::solve::Obligation, T>; pub type PredicateObligation<'tcx> = Obligation<'tcx, ty::Predicate<'tcx>>; pub type TraitObligation<'tcx> = Obligation<'tcx, ty::TraitClause<'tcx>>; @@ -92,29 +31,6 @@ pub type PolyTraitObligation<'tcx> = Obligation<'tcx, ty::PolyTraitClause<'tcx>> pub type PredicateObligations<'tcx> = ThinVec>; -impl<'tcx> PredicateObligation<'tcx> { - /// Flips the polarity of the inner predicate. - /// - /// Given `T: Trait` predicate it returns `T: !Trait` and given `T: !Trait` returns `T: Trait`. - pub fn flip_polarity(&self, tcx: TyCtxt<'tcx>) -> Option> { - Some(PredicateObligation { - cause: self.cause.clone(), - param_env: self.param_env, - predicate: self.predicate.flip_polarity(tcx)?, - recursion_depth: self.recursion_depth, - }) - } -} - -impl<'tcx> PolyTraitObligation<'tcx> { - pub fn derived_cause( - &self, - variant: impl FnOnce(DerivedCause<'tcx>) -> ObligationCauseCode<'tcx>, - ) -> ObligationCause<'tcx> { - self.cause.clone().derived_cause(self.predicate, variant) - } -} - // `PredicateObligation` is used a lot. Make sure it doesn't unintentionally get bigger. #[cfg(target_pointer_width = "64")] rustc_data_structures::static_assert_size!(PredicateObligation<'_>, 48); @@ -125,61 +41,3 @@ pub type Selection<'tcx> = ImplSource<'tcx, PredicateObligation<'tcx>>; /// of root obligations. pub type ObligationInspector<'tcx> = fn(&InferCtxt<'tcx>, &PredicateObligation<'tcx>, Result); - -impl<'tcx, O> Obligation<'tcx, O> { - pub fn new( - tcx: TyCtxt<'tcx>, - cause: ObligationCause<'tcx>, - param_env: ty::ParamEnv<'tcx>, - predicate: impl Upcast, O>, - ) -> Obligation<'tcx, O> { - Self::with_depth(tcx, cause, 0, param_env, predicate) - } - - /// We often create nested obligations without setting the correct depth. - /// - /// To deal with this evaluate and fulfill explicitly update the depth - /// of nested obligations using this function. - pub fn set_depth_from_parent(&mut self, parent_depth: usize) { - self.recursion_depth = cmp::max(parent_depth + 1, self.recursion_depth); - } - - pub fn with_depth( - tcx: TyCtxt<'tcx>, - cause: ObligationCause<'tcx>, - recursion_depth: usize, - param_env: ty::ParamEnv<'tcx>, - predicate: impl Upcast, O>, - ) -> Obligation<'tcx, O> { - let predicate = predicate.upcast(tcx); - Obligation { cause, param_env, recursion_depth, predicate } - } - - pub fn misc( - tcx: TyCtxt<'tcx>, - span: Span, - body_def_id: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - trait_ref: impl Upcast, O>, - ) -> Obligation<'tcx, O> { - Obligation::new(tcx, ObligationCause::misc(span, body_def_id), param_env, trait_ref) - } - - pub fn with

( - &self, - tcx: TyCtxt<'tcx>, - value: impl Upcast, P>, - ) -> Obligation<'tcx, P> { - Obligation::with_depth(tcx, self.cause.clone(), self.recursion_depth, self.param_env, value) - } -} - -impl<'tcx> PolyTraitObligation<'tcx> { - pub fn polarity(&self) -> ty::ClausePolarity { - self.predicate.skip_binder().polarity - } - - pub fn self_ty(&self) -> ty::Binder<'tcx, Ty<'tcx>> { - self.predicate.map_bound(|p| p.self_ty()) - } -} diff --git a/compiler/rustc_infer/src/traits/structural_impls.rs b/compiler/rustc_infer/src/traits/structural_impls.rs index 03661ebf7ec53..b705f2d6eb2d5 100644 --- a/compiler/rustc_infer/src/traits/structural_impls.rs +++ b/compiler/rustc_infer/src/traits/structural_impls.rs @@ -1,7 +1,5 @@ use std::fmt; -use rustc_middle::ty; - use crate::traits; use crate::traits::project::Normalized; @@ -13,20 +11,6 @@ impl<'tcx, T: fmt::Debug> fmt::Debug for Normalized<'tcx, T> { } } -impl<'tcx, O: fmt::Debug> fmt::Debug for traits::Obligation<'tcx, O> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if ty::tls::with(|tcx| tcx.sess.verbose_internals()) { - write!( - f, - "Obligation(predicate={:?}, cause={:?}, param_env={:?}, depth={})", - self.predicate, self.cause, self.param_env, self.recursion_depth - ) - } else { - write!(f, "Obligation(predicate={:?}, depth={})", self.predicate, self.recursion_depth) - } - } -} - impl<'tcx> fmt::Debug for traits::MismatchedProjectionTypes<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MismatchedProjectionTypes({:?})", self.err) diff --git a/compiler/rustc_infer/src/traits/util.rs b/compiler/rustc_infer/src/traits/util.rs index 0380488caf96f..714297c16ae01 100644 --- a/compiler/rustc_infer/src/traits/util.rs +++ b/compiler/rustc_infer/src/traits/util.rs @@ -1,9 +1,7 @@ use rustc_data_structures::fx::FxHashSet; pub use rustc_middle::ty::elaborate::*; use rustc_middle::ty::{self, TyCtxt, Unnormalized}; -use rustc_span::{Ident, Span}; - -use crate::traits::{self, Obligation, ObligationCauseCode, PredicateObligation}; +use rustc_span::Ident; pub fn anonymize_predicate<'tcx>( tcx: TyCtxt<'tcx>, @@ -63,46 +61,6 @@ impl<'tcx> Extend> for PredicateSet<'tcx> { } } -/// For [`Obligation`], a sub-obligation is combined with the current obligation's -/// param-env and cause code. -impl<'tcx> Elaboratable> for PredicateObligation<'tcx> { - fn predicate(&self) -> ty::Predicate<'tcx> { - self.predicate - } - - fn child(&self, clause: ty::Clause<'tcx>) -> Self { - Obligation { - cause: self.cause.clone(), - param_env: self.param_env, - recursion_depth: 0, - predicate: clause.as_predicate(), - } - } - - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - span: Span, - parent_trait_pred: ty::PolyTraitClause<'tcx>, - index: usize, - ) -> Self { - let cause = self.cause.clone().derived_cause(parent_trait_pred, |derived| { - ObligationCauseCode::ImplDerived(Box::new(traits::ImplDerivedCause { - derived, - impl_or_alias_def_id: parent_trait_pred.def_id(), - impl_def_clause_index: Some(index), - span, - })) - }); - Obligation { - cause, - param_env: self.param_env, - recursion_depth: 0, - predicate: clause.as_predicate(), - } - } -} - /// A specialized variant of `elaborate` that only elaborates trait references that may /// define the given associated item with the name `assoc_name`. It uses the /// `explicit_supertraits_containing_assoc_item` query to avoid enumerating super-predicates that diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index a6688b668aab3..8862a9a02e977 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -27,6 +27,18 @@ use crate::ty::{ Region, RegionKind, RequiredDepth, Ty, TyCtxt, }; +impl<'tcx> rustc_type_ir::inherent::ObligationCause> + for crate::traits::ObligationCause<'tcx> +{ + fn span(&self) -> Span { + self.span + } + + fn debug_with_internals() -> bool { + crate::ty::tls::with(|tcx| tcx.sess.verbose_internals()) + } +} + #[allow(rustc::usage_of_ty_tykind)] impl<'tcx> Interner for TyCtxt<'tcx> { fn next_trait_solver_globally(self) -> bool { @@ -59,6 +71,7 @@ impl<'tcx> Interner for TyCtxt<'tcx> { type InherentAssocConstId = DefId; type InherentAssocTermId = DefId; type Span = Span; + type ObligationCause = crate::traits::ObligationCause<'tcx>; type GenericArgs = ty::GenericArgsRef<'tcx>; diff --git a/compiler/rustc_middle/src/ty/elaborate_impl.rs b/compiler/rustc_middle/src/ty/elaborate_impl.rs index cd76d49cefecb..407f5e7fd21bf 100644 --- a/compiler/rustc_middle/src/ty/elaborate_impl.rs +++ b/compiler/rustc_middle/src/ty/elaborate_impl.rs @@ -1,8 +1,53 @@ use rustc_span::Span; use rustc_type_ir::elaborate::Elaboratable; +use rustc_type_ir::solve::Obligation; +use crate::traits::{ImplDerivedCause, ObligationCauseCode}; use crate::ty::{self, TyCtxt}; +type PredicateObligation<'tcx> = Obligation, ty::Predicate<'tcx>>; + +/// For `Obligation`, a sub-obligation is combined with the current +/// obligation's param-env and cause code. +impl<'tcx> Elaboratable> for PredicateObligation<'tcx> { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.predicate + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + Obligation { + cause: self.cause.clone(), + param_env: self.param_env, + recursion_depth: 0, + predicate: clause.as_predicate(), + } + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + span: Span, + parent_trait_pred: ty::PolyTraitClause<'tcx>, + index: usize, + ) -> Self { + let cause = self.cause.clone().derived_cause(parent_trait_pred, |derived| { + ObligationCauseCode::ImplDerived(Box::new(ImplDerivedCause { + derived, + impl_or_alias_def_id: parent_trait_pred.def_id(), + impl_def_clause_index: Some(index), + span, + })) + }); + + Obligation { + cause, + param_env: self.param_env, + recursion_depth: 0, + predicate: clause.as_predicate(), + } + } +} + impl<'tcx> Elaboratable> for ty::Clause<'tcx> { fn predicate(&self) -> ty::Predicate<'tcx> { self.as_predicate() diff --git a/compiler/rustc_next_trait_solver/src/solve/fulfill.rs b/compiler/rustc_next_trait_solver/src/solve/fulfill.rs index 5e834c8c4059d..6f9c361572630 100644 --- a/compiler/rustc_next_trait_solver/src/solve/fulfill.rs +++ b/compiler/rustc_next_trait_solver/src/solve/fulfill.rs @@ -1,34 +1,26 @@ -use std::marker::PhantomData; use std::mem; -use rustc_infer::infer::InferCtxt; -use rustc_infer::traits::query::NoSolution; -use rustc_infer::traits::{ - FromSolverError, PredicateObligation, PredicateObligations, TraitEngine, TraitErrors, -}; -use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode}; -use rustc_next_trait_solver::solve::fast_path::compute_goal_fast_path; -use rustc_next_trait_solver::solve::{ - GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt as _, StalledOnCoroutines, -}; +use rustc_type_ir::inherent::*; +use rustc_type_ir::solve::{NoSolution, Obligation}; +use rustc_type_ir::{self as ty, InferCtxtLike as _, Interner, TypingMode}; use thin_vec::ThinVec; use tracing::instrument; -use self::derive_errors::*; -use super::Certainty; -use super::delegate::SolverDelegate; -use crate::error_reporting::InferCtxtErrorExt; -use crate::traits::{FulfillmentError, ScrubbedTraitError}; - -mod derive_errors; +use super::fast_path::compute_goal_fast_path; +use super::{ + Certainty, GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt as _, + StalledOnCoroutines, +}; +use crate::delegate::SolverDelegate; // `ThinVec` is important for performance, but not for the usual memory layout reasons. // `try_evaluate_obligations` is extremely hot and uses `retain_mut`. `ThinVec::retain_mut` is // simple and sub-optimal in terms of how it moves elements, but it can be inlined. // `Vec::retain_mut` is more sophisticated and minimizes element moves, but also contains more code // and doesn't get inlined in `try_evaluate_obligations`, giving worse performance overall. -type PendingObligations<'tcx> = - ThinVec<(PredicateObligation<'tcx>, Option>>)>; +type PredicateObligation = Obligation::Predicate>; +type PredicateObligations = ThinVec>; +type PendingObligations = ThinVec<(PredicateObligation, Option>)>; /// A trait engine using the new trait solver. /// @@ -41,27 +33,32 @@ type PendingObligations<'tcx> = /// /// It is also likely that we want to use slightly different datastructures /// here as this will have to deal with far more root goals than `evaluate_all`. -pub struct FulfillmentCtxt<'tcx, E: 'tcx> { - obligations: ObligationStorage<'tcx>, +pub struct FulfillmentCtxt { + obligations: ObligationStorage, /// The snapshot in which this context was created. Using the context /// outside of this snapshot leads to subtle bugs if the snapshot /// gets rolled back. Because of this we explicitly check that we only /// use the context in exactly this snapshot. usable_in_snapshot: usize, - _errors: PhantomData, } -#[derive(Default, Debug)] -struct ObligationStorage<'tcx> { - pending: PendingObligations<'tcx>, +#[derive(Debug)] +struct ObligationStorage { + pending: PendingObligations, +} + +impl Default for ObligationStorage { + fn default() -> Self { + Self { pending: ThinVec::new() } + } } -impl<'tcx> ObligationStorage<'tcx> { +impl ObligationStorage { fn register( &mut self, - obligation: PredicateObligation<'tcx>, - stalled_on: Option>>, + obligation: PredicateObligation, + stalled_on: Option>, ) { self.pending.push((obligation, stalled_on)); } @@ -70,21 +67,21 @@ impl<'tcx> ObligationStorage<'tcx> { !self.pending.is_empty() } - fn clone_pending(&self) -> PredicateObligations<'tcx> { + fn clone_pending(&self) -> PredicateObligations { self.pending.iter().map(|(o, _)| o.clone()).collect() } - fn clone_pending_filtered(&self, f: F) -> PredicateObligations<'tcx> + fn clone_pending_filtered(&self, f: F) -> PredicateObligations where - F: FnMut(&&(PredicateObligation<'tcx>, Option>>)) -> bool, + F: FnMut(&&(PredicateObligation, Option>)) -> bool, { self.pending.iter().filter(f).map(|(o, _)| o.clone()).collect() } fn drain_pending( &mut self, - cond: impl Fn(&PredicateObligation<'tcx>, &Option>>) -> bool, - ) -> PendingObligations<'tcx> { + cond: impl Fn(&PredicateObligation, &Option>) -> bool, + ) -> PendingObligations { let (unstalled, pending) = mem::take(&mut self.pending).into_iter().partition(|(o, s)| cond(o, s)); self.pending = pending; @@ -92,50 +89,32 @@ impl<'tcx> ObligationStorage<'tcx> { } } -impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { - pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx, E> { +impl FulfillmentCtxt { + pub fn new(delegate: &D, usable_in_snapshot: usize) -> Self + where + D: SolverDelegate, + { assert!( - infcx.next_trait_solver(), + delegate.next_trait_solver(), "new trait solver fulfillment context created when \ infcx is set up for old trait solver" ); - FulfillmentCtxt { - obligations: Default::default(), - usable_in_snapshot: infcx.num_open_snapshots(), - _errors: PhantomData, - } + FulfillmentCtxt { obligations: Default::default(), usable_in_snapshot } } - fn inspect_evaluated_obligation( - infcx: &InferCtxt<'tcx>, - obligation: &PredicateObligation<'tcx>, - result: &Result>, NoSolution>, - ) { - if let Some(inspector) = infcx.obligation_inspector.get() { - let result = match result { - Ok(GoalEvaluation { certainty, .. }) => Ok(*certainty), - Err(NoSolution) => Err(NoSolution), - }; - (inspector)(infcx, &obligation, result); - } - } -} - -impl<'tcx, E> TraitEngine<'tcx, E> for FulfillmentCtxt<'tcx, E> -where - E: FromSolverError<'tcx, NextSolverError<'tcx>>, -{ - #[instrument(level = "trace", skip(self, infcx))] - fn register_predicate_obligation( + #[instrument(level = "trace", skip(self, delegate))] + pub fn register( &mut self, - infcx: &InferCtxt<'tcx>, - obligation: PredicateObligation<'tcx>, - ) { - assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); + delegate: &D, + current_snapshot: usize, + obligation: PredicateObligation, + ) where + D: SolverDelegate, + { + assert_eq!(self.usable_in_snapshot, current_snapshot); - let delegate = <&SolverDelegate<'tcx>>::from(infcx); if let Some(GoalEvaluation { goal: _, certainty, has_changed: _, stalled_on }) = - compute_goal_fast_path(delegate, obligation.as_goal(), obligation.cause.span) + compute_goal_fast_path(delegate, obligation.as_goal(), obligation.cause.span()) { // If we can take the fast path, don't even bother adding the goal to obligations, // or if `Certainty::Maybe`, add it with precise stalled_on information. @@ -150,21 +129,22 @@ where } } - #[inline] - fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { - if self.obligations.pending.is_empty() { - // Typically in more than 99.9% of cases this condition is true, therefore we outline - // the other case. - TraitErrors::NoErrors - } else { - TraitErrors::HasErrors(collect_remaining_errors_impl(self, infcx)) - } - } - - fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { - assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); - let mut errors = TraitErrors::NoErrors; - let delegate = <&SolverDelegate<'tcx>>::from(infcx); + pub fn try_evaluate_obligations( + &mut self, + delegate: &D, + current_snapshot: usize, + mut inspect: Inspect, + mut on_error: OnError, + mut on_success: OnSuccess, + mut on_overflow: OnOverflow, + ) where + D: SolverDelegate, + Inspect: FnMut(&PredicateObligation, &Result, NoSolution>), + OnError: FnMut(PredicateObligation), + OnSuccess: FnMut(&PredicateObligation), + OnOverflow: FnMut(&PredicateObligation), + { + assert_eq!(self.usable_in_snapshot, current_snapshot); loop { let mut any_changed = false; @@ -179,17 +159,14 @@ where let result = delegate.evaluate_root_goal( obligation.as_goal(), - obligation.cause.span, + obligation.cause.span(), opt_stalled_on.take(), ); - Self::inspect_evaluated_obligation(infcx, &obligation, &result); + inspect(obligation, &result); let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result { Ok(result) => result, Err(NoSolution) => { - errors.push(E::from_solver_error( - infcx, - NextSolverError::TrueError(obligation.clone()), - )); + on_error(obligation.clone()); return false; } }; @@ -199,7 +176,7 @@ where // constrained by evaluating the goal. obligation.predicate = goal.predicate; if has_changed == HasChanged::Yes { - if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) { + if obligation.recursion_depth > delegate.cx().recursion_limit() { // We limit the total count of inference progress to avoid hang so we don't // try to recover from this. // It's more complicated to collect all overflows thus we stopped doing that. @@ -210,7 +187,8 @@ where // a `recursion_depth` number of times. This mostly happens in bugs or with // `Subtype` obligations because we no longer use the `sub_unification_table` // in generalization. - infcx.err_ctxt().report_overflow_obligation(obligation, true); + on_overflow(obligation); + unreachable!("fulfillment overflow handler must not return"); } else { // We increment the recursion depth here to track the number of times // this goal has resulted in inference progress. This doesn't precisely @@ -225,24 +203,7 @@ where match certainty { Certainty::Yes => { - // Goals may depend on structural identity. Region uniquification at the - // start of MIR borrowck may cause things to no longer be so, potentially - // causing an ICE. - // - // While we uniquify root goals in HIR this does not handle cases where - // regions are hidden inside of a type or const inference variable. - // - // FIXME(-Znext-solver): This does not handle inference variables hidden - // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the - // storage, we can also rely on structural identity of `?x` even if we - // later uniquify it in MIR borrowck. - if infcx.in_hir_typeck - && (obligation.has_non_region_infer() || obligation.has_free_regions()) - { - infcx.push_hir_typeck_potentially_region_dependent_goal( - obligation.clone(), - ); - } + on_success(obligation); false } Certainty::Maybe(_) => { @@ -258,25 +219,26 @@ where break; } } - - errors } - fn has_pending_obligations(&self) -> bool { + pub fn has_pending_obligations(&self) -> bool { self.obligations.has_pending_obligations() } - fn pending_obligations(&self) -> PredicateObligations<'tcx> { + pub fn pending_obligations(&self) -> PredicateObligations { self.obligations.clone_pending() } - fn pending_obligations_potentially_referencing_sub_root( + pub fn pending_obligations_potentially_referencing_sub_root( &self, - infcx: &InferCtxt<'tcx>, + delegate: &D, vid: ty::TyVid, - ) -> PredicateObligations<'tcx> { + ) -> PredicateObligations + where + D: SolverDelegate, + { // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. - if infcx.tcx.disable_trait_solver_fast_paths() { + if delegate.disable_trait_solver_fast_paths() { return self.obligations.clone_pending(); } self.obligations.clone_pending_filtered(|(_, stalled_on)| { @@ -289,21 +251,24 @@ where // Conservative here: if a stalled var no longer resolves to an // infer var, some unification happened, so the goal is no longer // stalled. Include it to be re-evaluated downstream. - stalled_on.stalled_vars.iter().filter_map(|arg| arg.as_type(infcx.tcx)).any(|ty| { - match *infcx.shallow_resolve(ty).kind() { - ty::Infer(ty::TyVar(tv)) => infcx.sub_unification_table_root_var(tv) == vid, + stalled_on.stalled_vars.iter().filter_map(|arg| arg.as_type(delegate.cx())).any(|ty| { + match delegate.shallow_resolve(ty).kind() { + ty::Infer(ty::TyVar(tv)) => delegate.sub_unification_table_root_var(tv) == vid, _ => true, } }) }) } - fn pending_obligations_potentially_referencing_float_infer( + pub fn pending_obligations_potentially_referencing_float_infer( &self, - infcx: &InferCtxt<'tcx>, - ) -> PredicateObligations<'tcx> { + delegate: &D, + ) -> PredicateObligations + where + D: SolverDelegate, + { // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. - if infcx.tcx.disable_trait_solver_fast_paths() { + if delegate.disable_trait_solver_fast_paths() { return self.obligations.clone_pending(); } @@ -314,16 +279,19 @@ where stalled_on .stalled_vars .iter() - .filter_map(|arg| arg.as_type(infcx.tcx)) - .any(|ty| matches!(infcx.shallow_resolve(ty).kind(), ty::Infer(ty::FloatVar(_)))) + .filter_map(|arg| arg.as_type(delegate.cx())) + .any(|ty| matches!(delegate.shallow_resolve(ty).kind(), ty::Infer(ty::FloatVar(_)))) }) } - fn drain_stalled_obligations_for_coroutines( + pub fn drain_stalled_obligations_for_coroutines( &mut self, - infcx: &InferCtxt<'tcx>, - ) -> PredicateObligations<'tcx> { - let stalled_coroutines = match infcx.typing_mode_raw().assert_not_erased() { + delegate: &D, + ) -> PredicateObligations + where + D: SolverDelegate, + { + let stalled_coroutines = match delegate.typing_mode_raw().assert_not_erased() { TypingMode::Typeck { defining_opaque_types_and_generators } => { defining_opaque_types_and_generators } @@ -349,66 +317,13 @@ where }) }) .into_iter() - .map(|(o, _)| o) + .map(|(obligation, _)| obligation) .collect() } -} - -#[cold] -#[inline(never)] -fn collect_remaining_errors_impl<'tcx, E>( - cx: &mut FulfillmentCtxt<'tcx, E>, - infcx: &InferCtxt<'tcx>, -) -> ThinVec -where - E: FromSolverError<'tcx, NextSolverError<'tcx>>, -{ - cx.obligations - .pending - .drain(..) - .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) - .map(|e| E::from_solver_error(infcx, e)) - .collect() -} - -pub enum NextSolverError<'tcx> { - TrueError(PredicateObligation<'tcx>), - Ambiguity(PredicateObligation<'tcx>), -} - -impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tcx> { - fn from_solver_error(infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self { - match error { - NextSolverError::TrueError(obligation) => { - fulfillment_error_for_no_solution(infcx, obligation) - } - NextSolverError::Ambiguity(obligation) => { - fulfillment_error_for_stalled(infcx, obligation) - } - } - } -} -impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for ScrubbedTraitError<'tcx> { - fn from_solver_error(_infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self { - match error { - NextSolverError::TrueError(_) => ScrubbedTraitError::TrueError, - NextSolverError::Ambiguity(_) => ScrubbedTraitError::Ambiguity, - } + pub fn drain_remaining_obligations( + &mut self, + ) -> impl Iterator> + '_ { + self.obligations.pending.drain(..).map(|(obligation, _)| obligation) } } - -// Some types are used a lot. Make sure they don't unintentionally get bigger. -#[cfg(target_pointer_width = "64")] -mod size_asserts { - use rustc_data_structures::static_assert_size; - - use super::*; - // tidy-alphabetical-start - // Before #160005 this pair was greater than 128 bytes, which triggered the use of (slow) - // `memcpy` for moving elements of `PendingObligations`. Then #160479 greatly reduced the - // number of `memcpy` operations in `try_evaluate_obligations`. So the size of this pair is - // much less important than it was, but still shouldn't be changed without some thought. - static_assert_size!((PredicateObligation<'_>, Option>>), 104); - // tidy-alphabetical-end -} diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 099c755d78427..2a3c93d250ea8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -14,6 +14,7 @@ mod assembly; mod effect_goals; mod eval_ctxt; +mod fulfill; pub mod inspect; mod normalizes_to; mod project_goals; @@ -30,6 +31,7 @@ pub use self::eval_ctxt::{ EvalCtxt, GenerateProofTree, SolverDelegateEvalExt, evaluate_root_goal_for_proof_tree_raw_provider, fast_path, }; +pub use self::fulfill::FulfillmentCtxt; use crate::delegate::SolverDelegate; use crate::solve::assembly::Candidate; diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 76755eb481db7..637fa7ea59220 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -5267,10 +5267,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { && let Some(ty) = typeck_results.node_type_opt(rcvr.hir_id) && let Some(failed_pred) = failed_pred.as_trait_clause() && let pred = failed_pred.map_bound(|pred| pred.with_replaced_self_ty(tcx, ty)) - && self.predicate_must_hold_modulo_regions(&Obligation::misc( + && self.predicate_must_hold_modulo_regions(&Obligation::new( tcx, - expr.span, - body_def_id, + rustc_middle::traits::ObligationCause::misc(expr.span, body_def_id), param_env, pred, )) @@ -5924,10 +5923,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { )); let body_def_id = self.tcx.hir_enclosing_body_owner(body_id); // Add `::Item = _` obligation. - ocx.register_obligation(Obligation::misc( + ocx.register_obligation(Obligation::new( self.tcx, - span, - body_def_id, + rustc_middle::traits::ObligationCause::misc(span, body_def_id), param_env, projection, )); diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 5e834c8c4059d..d36157ee43856 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -1,35 +1,22 @@ use std::marker::PhantomData; -use std::mem; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::{ FromSolverError, PredicateObligation, PredicateObligations, TraitEngine, TraitErrors, }; -use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode}; -use rustc_next_trait_solver::solve::fast_path::compute_goal_fast_path; -use rustc_next_trait_solver::solve::{ - GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt as _, StalledOnCoroutines, -}; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; +use rustc_next_trait_solver::solve::{FulfillmentCtxt as SolverFulfillmentCtxt, GoalEvaluation}; use thin_vec::ThinVec; use tracing::instrument; use self::derive_errors::*; -use super::Certainty; use super::delegate::SolverDelegate; use crate::error_reporting::InferCtxtErrorExt; use crate::traits::{FulfillmentError, ScrubbedTraitError}; mod derive_errors; -// `ThinVec` is important for performance, but not for the usual memory layout reasons. -// `try_evaluate_obligations` is extremely hot and uses `retain_mut`. `ThinVec::retain_mut` is -// simple and sub-optimal in terms of how it moves elements, but it can be inlined. -// `Vec::retain_mut` is more sophisticated and minimizes element moves, but also contains more code -// and doesn't get inlined in `try_evaluate_obligations`, giving worse performance overall. -type PendingObligations<'tcx> = - ThinVec<(PredicateObligation<'tcx>, Option>>)>; - /// A trait engine using the new trait solver. /// /// This is mostly identical to how `evaluate_all` works inside of the @@ -42,66 +29,15 @@ type PendingObligations<'tcx> = /// It is also likely that we want to use slightly different datastructures /// here as this will have to deal with far more root goals than `evaluate_all`. pub struct FulfillmentCtxt<'tcx, E: 'tcx> { - obligations: ObligationStorage<'tcx>, - - /// The snapshot in which this context was created. Using the context - /// outside of this snapshot leads to subtle bugs if the snapshot - /// gets rolled back. Because of this we explicitly check that we only - /// use the context in exactly this snapshot. - usable_in_snapshot: usize, + core: SolverFulfillmentCtxt>, _errors: PhantomData, } -#[derive(Default, Debug)] -struct ObligationStorage<'tcx> { - pending: PendingObligations<'tcx>, -} - -impl<'tcx> ObligationStorage<'tcx> { - fn register( - &mut self, - obligation: PredicateObligation<'tcx>, - stalled_on: Option>>, - ) { - self.pending.push((obligation, stalled_on)); - } - - fn has_pending_obligations(&self) -> bool { - !self.pending.is_empty() - } - - fn clone_pending(&self) -> PredicateObligations<'tcx> { - self.pending.iter().map(|(o, _)| o.clone()).collect() - } - - fn clone_pending_filtered(&self, f: F) -> PredicateObligations<'tcx> - where - F: FnMut(&&(PredicateObligation<'tcx>, Option>>)) -> bool, - { - self.pending.iter().filter(f).map(|(o, _)| o.clone()).collect() - } - - fn drain_pending( - &mut self, - cond: impl Fn(&PredicateObligation<'tcx>, &Option>>) -> bool, - ) -> PendingObligations<'tcx> { - let (unstalled, pending) = - mem::take(&mut self.pending).into_iter().partition(|(o, s)| cond(o, s)); - self.pending = pending; - unstalled - } -} - impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx, E> { - assert!( - infcx.next_trait_solver(), - "new trait solver fulfillment context created when \ - infcx is set up for old trait solver" - ); + let delegate = <&SolverDelegate<'tcx>>::from(infcx); FulfillmentCtxt { - obligations: Default::default(), - usable_in_snapshot: infcx.num_open_snapshots(), + core: SolverFulfillmentCtxt::new(delegate, infcx.num_open_snapshots()), _errors: PhantomData, } } @@ -131,28 +67,13 @@ where infcx: &InferCtxt<'tcx>, obligation: PredicateObligation<'tcx>, ) { - assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); - let delegate = <&SolverDelegate<'tcx>>::from(infcx); - if let Some(GoalEvaluation { goal: _, certainty, has_changed: _, stalled_on }) = - compute_goal_fast_path(delegate, obligation.as_goal(), obligation.cause.span) - { - // If we can take the fast path, don't even bother adding the goal to obligations, - // or if `Certainty::Maybe`, add it with precise stalled_on information. - match certainty { - Certainty::Yes => {} - Certainty::Maybe(_) => { - self.obligations.register(obligation, stalled_on); - } - } - } else { - self.obligations.register(obligation, None); - } + self.core.register(delegate, infcx.num_open_snapshots(), obligation); } #[inline] fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { - if self.obligations.pending.is_empty() { + if !self.core.has_pending_obligations() { // Typically in more than 99.9% of cases this condition is true, therefore we outline // the other case. TraitErrors::NoErrors @@ -162,112 +83,48 @@ where } fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { - assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); let mut errors = TraitErrors::NoErrors; let delegate = <&SolverDelegate<'tcx>>::from(infcx); - loop { - let mut any_changed = false; - self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| { - // Common case: still stalled; keep the obligation. This path is extremely hot in - // some cases; there can be thousands of pending obligations. - if let Some(stalled_on) = opt_stalled_on - && delegate.goal_remains_stalled(stalled_on) + self.core.try_evaluate_obligations( + delegate, + infcx.num_open_snapshots(), + |obligation, result| { + Self::inspect_evaluated_obligation(infcx, obligation, result); + }, + |obligation| { + errors.push(E::from_solver_error(infcx, NextSolverError::TrueError(obligation))); + }, + |obligation| { + // Goals may depend on structural identity. Region uniquification at the + // start of MIR borrowck may cause things to no longer be so, potentially + // causing an ICE. + // + // While we uniquify root goals in HIR this does not handle cases where + // regions are hidden inside of a type or const inference variable. + // + // FIXME(-Znext-solver): This does not handle inference variables hidden + // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the + // storage, we can also rely on structural identity of `?x` even if we + // later uniquify it in MIR borrowck. + if infcx.in_hir_typeck + && (obligation.has_non_region_infer() || obligation.has_free_regions()) { - return true; - } - - let result = delegate.evaluate_root_goal( - obligation.as_goal(), - obligation.cause.span, - opt_stalled_on.take(), - ); - Self::inspect_evaluated_obligation(infcx, &obligation, &result); - let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result { - Ok(result) => result, - Err(NoSolution) => { - errors.push(E::from_solver_error( - infcx, - NextSolverError::TrueError(obligation.clone()), - )); - return false; - } - }; - - // We've resolved the goal in `evaluate_root_goal`, avoid redoing this work - // in the next iteration. This does not resolve the inference variables - // constrained by evaluating the goal. - obligation.predicate = goal.predicate; - if has_changed == HasChanged::Yes { - if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) { - // We limit the total count of inference progress to avoid hang so we don't - // try to recover from this. - // It's more complicated to collect all overflows thus we stopped doing that. - // Eager aborting is also what the old solver does. - // - // Note: it's incredibly rare to actually encounter fulfillment overflow - // as a single obligation would have to result in different inference progress - // a `recursion_depth` number of times. This mostly happens in bugs or with - // `Subtype` obligations because we no longer use the `sub_unification_table` - // in generalization. - infcx.err_ctxt().report_overflow_obligation(obligation, true); - } else { - // We increment the recursion depth here to track the number of times - // this goal has resulted in inference progress. This doesn't precisely - // model the way that we track recursion depth in the old solver due - // to the fact that we only process root obligations, but it is a good - // approximation and should only result in fulfillment overflow in - // pathological cases. - obligation.recursion_depth += 1; - any_changed = true; - } - } - - match certainty { - Certainty::Yes => { - // Goals may depend on structural identity. Region uniquification at the - // start of MIR borrowck may cause things to no longer be so, potentially - // causing an ICE. - // - // While we uniquify root goals in HIR this does not handle cases where - // regions are hidden inside of a type or const inference variable. - // - // FIXME(-Znext-solver): This does not handle inference variables hidden - // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the - // storage, we can also rely on structural identity of `?x` even if we - // later uniquify it in MIR borrowck. - if infcx.in_hir_typeck - && (obligation.has_non_region_infer() || obligation.has_free_regions()) - { - infcx.push_hir_typeck_potentially_region_dependent_goal( - obligation.clone(), - ); - } - false - } - Certainty::Maybe(_) => { - // Update `opt_stalled_on` goal, for the next retain_mut, because we are - // running until a fixpoint. - *opt_stalled_on = stalled_on; - true - } + infcx.push_hir_typeck_potentially_region_dependent_goal(obligation.clone()); } - }); - - if !any_changed { - break; - } - } + }, + |obligation| infcx.err_ctxt().report_overflow_obligation(obligation, true), + ); errors } fn has_pending_obligations(&self) -> bool { - self.obligations.has_pending_obligations() + self.core.has_pending_obligations() } fn pending_obligations(&self) -> PredicateObligations<'tcx> { - self.obligations.clone_pending() + self.core.pending_obligations() } fn pending_obligations_potentially_referencing_sub_root( @@ -275,82 +132,24 @@ where infcx: &InferCtxt<'tcx>, vid: ty::TyVid, ) -> PredicateObligations<'tcx> { - // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. - if infcx.tcx.disable_trait_solver_fast_paths() { - return self.obligations.clone_pending(); - } - self.obligations.clone_pending_filtered(|(_, stalled_on)| { - let Some(stalled_on) = stalled_on else { return true }; - // Don't reuse the sub-unification roots cached on `stalled_on`: - // a later sub-unification merge can have changed which root - // each stalled var belongs to, so the cached info can be stale. - // Walk `stalled_vars` and recompute the current root instead. - // - // Conservative here: if a stalled var no longer resolves to an - // infer var, some unification happened, so the goal is no longer - // stalled. Include it to be re-evaluated downstream. - stalled_on.stalled_vars.iter().filter_map(|arg| arg.as_type(infcx.tcx)).any(|ty| { - match *infcx.shallow_resolve(ty).kind() { - ty::Infer(ty::TyVar(tv)) => infcx.sub_unification_table_root_var(tv) == vid, - _ => true, - } - }) - }) + let delegate = <&SolverDelegate<'tcx>>::from(infcx); + self.core.pending_obligations_potentially_referencing_sub_root(delegate, vid) } fn pending_obligations_potentially_referencing_float_infer( &self, infcx: &InferCtxt<'tcx>, ) -> PredicateObligations<'tcx> { - // `-Zdisable-fast-paths`: same gate as the other new-solver fast paths. - if infcx.tcx.disable_trait_solver_fast_paths() { - return self.obligations.clone_pending(); - } - - self.obligations.clone_pending_filtered(|(_, stalled_on)| { - let Some(stalled_on) = stalled_on else { return true }; - // If the stalled vars don't have float infers, the nested goals won't - // have them either. We only create float infers for user written literals. - stalled_on - .stalled_vars - .iter() - .filter_map(|arg| arg.as_type(infcx.tcx)) - .any(|ty| matches!(infcx.shallow_resolve(ty).kind(), ty::Infer(ty::FloatVar(_)))) - }) + let delegate = <&SolverDelegate<'tcx>>::from(infcx); + self.core.pending_obligations_potentially_referencing_float_infer(delegate) } fn drain_stalled_obligations_for_coroutines( &mut self, infcx: &InferCtxt<'tcx>, ) -> PredicateObligations<'tcx> { - let stalled_coroutines = match infcx.typing_mode_raw().assert_not_erased() { - TypingMode::Typeck { defining_opaque_types_and_generators } => { - defining_opaque_types_and_generators - } - TypingMode::Coherence - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } - | TypingMode::PostBorrowck { defined_opaque_types: _ } - | TypingMode::Reflection - | TypingMode::PostAnalysis - | TypingMode::Codegen => return Default::default(), - }; - - if stalled_coroutines.is_empty() { - return Default::default(); - } - - self.obligations - .drain_pending(|_, stalled_on| { - stalled_on.as_ref().is_some_and(|s| { - match s.stalled_maybe_info.stalled_on_coroutines { - StalledOnCoroutines::Yes => true, - StalledOnCoroutines::No => false, - } - }) - }) - .into_iter() - .map(|(o, _)| o) - .collect() + let delegate = <&SolverDelegate<'tcx>>::from(infcx); + self.core.drain_stalled_obligations_for_coroutines(delegate) } } @@ -363,10 +162,9 @@ fn collect_remaining_errors_impl<'tcx, E>( where E: FromSolverError<'tcx, NextSolverError<'tcx>>, { - cx.obligations - .pending - .drain(..) - .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) + cx.core + .drain_remaining_obligations() + .map(NextSolverError::Ambiguity) .map(|e| E::from_solver_error(infcx, e)) .collect() } @@ -402,6 +200,7 @@ impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for ScrubbedTraitError<' #[cfg(target_pointer_width = "64")] mod size_asserts { use rustc_data_structures::static_assert_size; + use rustc_next_trait_solver::solve::GoalStalledOn; use super::*; // tidy-alphabetical-start diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 9ebfb4d5ecda0..3209559b64274 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -57,7 +57,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let mut candidates = SelectionCandidateSet { vec: Vec::new(), ambiguous: false }; // Negative trait predicates have different rules than positive trait predicates. - if obligation.polarity() == ty::ClausePolarity::Negative { + if obligation.predicate.skip_binder().polarity == ty::ClausePolarity::Negative { self.assemble_candidates_for_trait_alias(obligation, &mut candidates); self.assemble_candidates_from_impls(obligation, &mut candidates); self.assemble_candidates_from_caller_bounds(stack, &mut candidates)?; @@ -317,7 +317,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Okay to skip binder because the args on coroutine types never // touch bound regions, they just capture the in-scope // type/region parameters. - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); match self_ty.kind() { // `async`/`gen` constructs get lowered to a special kind of coroutine that // should *not* `impl Coroutine`. @@ -339,7 +339,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); if let ty::Coroutine(did, ..) = self_ty.kind() { // async constructs get lowered to a special kind of coroutine that // should directly `impl Future`. @@ -356,7 +356,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); // gen constructs get lowered to a special kind of coroutine that // should directly `impl Iterator`. if let ty::Coroutine(did, ..) = self_ty.kind() @@ -373,7 +373,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - if self.coroutine_is_gen(obligation.self_ty().skip_binder()) { + if self.coroutine_is_gen(obligation.predicate.map_bound(|p| p.self_ty()).skip_binder()) { candidates.vec.push(BuiltinCandidate); } } @@ -383,7 +383,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); if let ty::Coroutine(did, args) = *self_ty.kind() { // gen constructs get lowered to a special kind of coroutine that // should directly `impl AsyncIterator`. @@ -422,7 +422,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Okay to skip binder because the args on closure types never // touch bound regions, they just capture the in-scope // type/region parameters - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); match *self_ty.kind() { ty::Closure(def_id, _) => { let is_const = self.tcx().is_const_fn(def_id); @@ -483,8 +483,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let goal_kind = self.tcx().async_fn_trait_kind_from_def_id(obligation.predicate.def_id()).unwrap(); - debug!("self_ty = {:?}", obligation.self_ty().skip_binder().kind()); - match *obligation.self_ty().skip_binder().kind() { + debug!( + "self_ty = {:?}", + obligation.predicate.map_bound(|p| p.self_ty()).skip_binder().kind() + ); + match *obligation.predicate.map_bound(|p| p.self_ty()).skip_binder().kind() { ty::CoroutineClosure(def_id, args) => { if let Some(closure_kind) = args.as_coroutine_closure().kind_ty().to_opt_closure_kind() @@ -542,7 +545,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); let target_kind_ty = obligation.predicate.skip_binder().trait_ref.args.type_at(1); // `to_opt_closure_kind` is kind of ICEy when it sees non-int types. @@ -573,7 +576,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // until the old solver (and thus this function) is removed. // Okay to skip binder because what we are inspecting doesn't involve bound regions. - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); match *self_ty.kind() { ty::Infer(ty::TyVar(_)) => { debug!("assemble_fn_pointer_candidates: ambiguous self-type"); @@ -675,7 +678,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { continue; } - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); match self_ty.kind() { // Fast path to avoid evaluating an obligation that trivially holds. // There may be more bounds, but these are checked by the regular path. @@ -744,7 +747,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { candidates: &mut SelectionCandidateSet<'tcx>, ) { // Okay to skip binder here because the tests we do below do not involve bound regions. - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); debug!(?self_ty, "assemble_candidates_from_auto_impls"); let def_id = obligation.predicate.def_id(); @@ -916,7 +919,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { candidates: &mut SelectionCandidateSet<'tcx>, ) { debug!( - self_ty = ?obligation.self_ty().skip_binder(), + self_ty = ?obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(), "assemble_candidates_from_object_ty", ); @@ -1134,7 +1137,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { candidates: &mut SelectionCandidateSet<'tcx>, ) { // Okay to skip binder here because the tests we do below do not involve bound regions. - let self_ty = obligation.self_ty().skip_binder(); + let self_ty = obligation.predicate.map_bound(|p| p.self_ty()).skip_binder(); debug!(?self_ty); let def_id = obligation.predicate.def_id(); @@ -1330,7 +1333,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = self.infcx.shallow_resolve(obligation.self_ty().skip_binder()); + let self_ty = self + .infcx + .shallow_resolve(obligation.predicate.map_bound(|p| p.self_ty()).skip_binder()); match self_ty.kind() { ty::Tuple(_) => { candidates.vec.push(BuiltinCandidate); @@ -1374,7 +1379,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - let self_ty = self.infcx.deeply_resolve_ignoring_regions(obligation.self_ty()); + let self_ty = self + .infcx + .deeply_resolve_ignoring_regions(obligation.predicate.map_bound(|p| p.self_ty())); match self_ty.skip_binder().kind() { ty::FnPtr(..) => candidates.vec.push(BuiltinCandidate), diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 81517955181ee..2ad4645b8eab0 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -270,7 +270,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }; let types = self.infcx.enter_forall_and_leak_universe(types); - let cause = obligation.derived_cause(ObligationCauseCode::BuiltinDerived); + let cause = obligation + .cause + .clone() + .derived_cause(obligation.predicate, ObligationCauseCode::BuiltinDerived); self.collect_predicates_for_types( obligation.param_env, cause, @@ -393,7 +396,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let constituents = self.constituent_types_for_auto_trait(self_ty)?; let constituents = self.infcx.enter_forall_and_leak_universe(constituents); - let cause = obligation.derived_cause(ObligationCauseCode::BuiltinDerived); + let cause = obligation + .cause + .clone() + .derived_cause(obligation.predicate, ObligationCauseCode::BuiltinDerived); let mut obligations = self.collect_predicates_for_types( obligation.param_env, cause.clone(), @@ -613,7 +619,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let mut nested = self.equate_trait_refs(obligation.with(tcx, placeholder_predicate), trait_ref)?; - let cause = obligation.derived_cause(ObligationCauseCode::BuiltinDerived); + let cause = obligation + .cause + .clone() + .derived_cause(obligation.predicate, ObligationCauseCode::BuiltinDerived); // Confirm the `type Output: Sized;` bound that is present on `FnOnce` let output_ty = normalize_with_depth_to( @@ -915,7 +924,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } else { nested.push(Obligation::new( self.tcx(), - obligation.derived_cause(ObligationCauseCode::BuiltinDerived), + obligation + .cause + .clone() + .derived_cause(obligation.predicate, ObligationCauseCode::BuiltinDerived), obligation.param_env, ty::TraitRef::new( self.tcx(), @@ -1038,7 +1050,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // `assemble_candidates_for_unsizing` should ensure there are no late-bound // regions here. See the comment there for more details. - let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap()); + let source = self.infcx.shallow_resolve( + obligation.predicate.map_bound(|p| p.self_ty()).no_bound_vars().unwrap(), + ); let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1); let target = self.infcx.shallow_resolve(target); debug!(?source, ?target, "confirm_builtin_unsize_candidate"); diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 4846d75339cd6..9d047c50d1e31 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -1416,7 +1416,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { candidates.retain(|candidate| { if let &ImplCandidate(def_id) = candidate { - match (tcx.impl_polarity(def_id), obligation.polarity()) { + match (tcx.impl_polarity(def_id), obligation.predicate.skip_binder().polarity) { (ty::ImplPolarity::Positive, ty::ClausePolarity::Positive) | (ty::ImplPolarity::Negative, ty::ClausePolarity::Negative) => true, diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index cb68b8032f7e1..a1ebfdec44217 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -639,6 +639,15 @@ pub trait Span: Copy + Debug + Hash + Eq + TypeFoldable { fn dummy() -> Self; } +#[rust_analyzer::prefer_underscore_import] +pub trait ObligationCause>: Clone + Debug { + fn span(&self) -> I::Span; + + fn debug_with_internals() -> bool { + false + } +} + #[rust_analyzer::prefer_underscore_import] pub trait OpaqueTypeStorageEntries: Debug + Copy + Default { /// Whether the number of opaques has changed in a way that necessitates diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index e3bf97dfebd26..fffb7b7483d57 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -110,6 +110,7 @@ pub trait Interner: type InherentAssocConstId: SpecificDefId + Into; type InherentAssocTermId: SpecificDefId; type Span: Span; + type ObligationCause: ObligationCause; type GenericArgs: GenericArgs; type GenericArgsSlice: Copy + Debug + Hash + Eq + SliceLike; diff --git a/compiler/rustc_type_ir/src/solve/fulfill.rs b/compiler/rustc_type_ir/src/solve/fulfill.rs new file mode 100644 index 0000000000000..9b8af292ba3a2 --- /dev/null +++ b/compiler/rustc_type_ir/src/solve/fulfill.rs @@ -0,0 +1,121 @@ +use std::hash::{Hash, Hasher}; +use std::{cmp, fmt}; + +use derive_where::derive_where; +use rustc_type_ir_macros::{GenericTypeVisitable, TypeFoldable_Generic, TypeVisitable_Generic}; + +use super::Goal; +use crate::{Interner, Upcast}; + +/// An `Obligation` represents some trait reference (e.g., `i32: Eq`) for +/// which the "impl_source" must be found. The process of finding an "impl_source" is +/// called "resolving" the `Obligation`. This process consists of +/// either identifying an `impl` (e.g., `impl Eq for i32`) that +/// satisfies the obligation, or else finding a bound that is in +/// scope. +#[derive_where(Clone; I: Interner, T)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +pub struct Obligation { + /// The reason we have to prove this thing. + /// FIXME: we shouldn't ignore the cause but instead change the affected visitors + /// to only visit predicates manually. + #[type_foldable(identity)] + #[type_visitable(ignore)] + pub cause: I::ObligationCause, + + /// The environment in which we should prove this thing. + pub param_env: I::ParamEnv, + + /// The thing we are trying to prove. + pub predicate: T, + + /// If we started proving this as a result of trying to prove + /// something else, track the total depth to ensure termination. + /// If this goes over a certain threshold, we abort compilation -- + /// in such cases, we can not say whether or not the predicate + /// holds for certain. Stupid halting problem; such a drag. + #[type_foldable(identity)] + #[type_visitable(ignore)] + pub recursion_depth: usize, +} + +impl fmt::Debug for Obligation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if >::debug_with_internals() { + write!( + f, + "Obligation(predicate={:?}, cause={:?}, param_env={:?}, depth={})", + self.predicate, self.cause, self.param_env, self.recursion_depth + ) + } else { + write!(f, "Obligation(predicate={:?}, depth={})", self.predicate, self.recursion_depth) + } + } +} + +impl Obligation { + pub fn as_goal(&self) -> Goal { + Goal { param_env: self.param_env, predicate: self.predicate } + } +} + +impl PartialEq for Obligation { + #[inline] + fn eq(&self, other: &Self) -> bool { + // Ignore `cause` and `recursion_depth`. This is a small performance + // win for a few crates, and a huge performance win for the crate in + // https://github.com/rust-lang/rustc-perf/pull/1680, which greatly + // stresses the trait system. + self.param_env == other.param_env && self.predicate == other.predicate + } +} + +impl Eq for Obligation {} + +impl Hash for Obligation { + fn hash(&self, state: &mut H) { + // See the comment on `Obligation::eq`. + self.param_env.hash(state); + self.predicate.hash(state); + } +} + +impl Obligation { + pub fn new( + interner: I, + cause: I::ObligationCause, + param_env: I::ParamEnv, + predicate: impl Upcast, + ) -> Self { + Self::with_depth(interner, cause, 0, param_env, predicate) + } + + /// We often create nested obligations without setting the correct depth. + /// + /// To deal with this evaluate and fulfill explicitly update the depth + /// of nested obligations using this function. + pub fn set_depth_from_parent(&mut self, parent_depth: usize) { + self.recursion_depth = cmp::max(parent_depth + 1, self.recursion_depth); + } + + pub fn with_depth( + interner: I, + cause: I::ObligationCause, + recursion_depth: usize, + param_env: I::ParamEnv, + predicate: impl Upcast, + ) -> Self { + let predicate = predicate.upcast(interner); + Obligation { cause, param_env, recursion_depth, predicate } + } + + pub fn with

(&self, interner: I, value: impl Upcast) -> Obligation { + Obligation::with_depth( + interner, + self.cause.clone(), + self.recursion_depth, + self.param_env, + value, + ) + } +} diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 5d1472d5941f2..b3942094db802 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -1,3 +1,5 @@ +mod fulfill; +pub use fulfill::Obligation; pub mod inspect; use std::convert::Infallible;