| 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, | |
| }; | |
| use rustc_middle::ty::{self, TyCtxt, TyVid, TypeVisitableExt, TypingMode}; | |
| use rustc_next_trait_solver::delegate::SolverDelegate as _; | |
| use rustc_next_trait_solver::solve::{ | |
| GoalEvaluation, GoalStalledOn, HasChanged, MaybeInfo, SolverDelegateEvalExt as _, | |
| StalledOnCoroutines, | |
| }; | |
| use thin_vec::ThinVec; | |
| use tracing::instrument; | |
| use self::derive_errors::*; | |
| use super::Certainty; | |
| use super::delegate::SolverDelegate; | |
| use crate::traits::{FulfillmentError, ScrubbedTraitError}; | |
| mod derive_errors; | |
| // FIXME: Do we need to use a `ThinVec` here? | |
| type PendingObligations<'tcx> = | |
| ThinVec<(PredicateObligation<'tcx>, Option<GoalStalledOn<TyCtxt<'tcx>>>)>; | |
| /// 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<E>, | |
| } | |
| #[derive(Default, Debug)] | |
| struct ObligationStorage<'tcx> { | |
| /// Obligations which resulted in an overflow in fulfillment itself. | |
| /// | |
| /// We cannot eagerly return these as error so we instead store them here | |
| /// to avoid recomputing them each time `try_evaluate_obligations` is called. | |
| /// This also allows us to return the correct `FulfillmentError` for them. | |
| overflowed: Vec<PredicateObligation<'tcx>>, | |
| pending: PendingObligations<'tcx>, | |
| } | |
| impl<'tcx> ObligationStorage<'tcx> { | |
| fn register( | |
| &mut self, | |
| obligation: PredicateObligation<'tcx>, | |
| stalled_on: Option<GoalStalledOn<TyCtxt<'tcx>>>, | |
| ) { | |
| self.pending.push((obligation, stalled_on)); | |
| } | |
| fn has_pending_obligations(&self) -> bool { | |
| !self.pending.is_empty() || !self.overflowed.is_empty() | |
| } | |
| fn clone_pending(&self) -> PredicateObligations<'tcx> { | |
| let mut obligations: PredicateObligations<'tcx> = | |
| self.pending.iter().map(|(o, _)| o.clone()).collect(); | |
| obligations.extend(self.overflowed.iter().cloned()); | |
| obligations | |
| } | |
| fn clone_pending_potentially_referencing_sub_root( | |
| &self, | |
| infcx: &InferCtxt<'tcx>, | |
| vid: TyVid, | |
| ) -> PredicateObligations<'tcx> { | |
| let mut obligations: PredicateObligations<'tcx> = self | |
| .pending | |
| .iter() | |
| .filter(|(_, 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()).any( | |
| |ty| match *infcx.shallow_resolve(ty).kind() { | |
| ty::Infer(ty::TyVar(tv)) => infcx.sub_unification_table_root_var(tv) == vid, | |
| _ => true, | |
| }, | |
| ) | |
| }) | |
| .map(|(o, _)| o.clone()) | |
| .collect(); | |
| obligations.extend(self.overflowed.iter().cloned()); | |
| obligations | |
| } | |
| fn drain_pending( | |
| &mut self, | |
| cond: impl Fn(&PredicateObligation<'tcx>, &Option<GoalStalledOn<TyCtxt<'tcx>>>) -> bool, | |
| ) -> PendingObligations<'tcx> { | |
| let (unstalled, pending) = | |
| mem::take(&mut self.pending).into_iter().partition(|(o, s)| cond(o, s)); | |
| self.pending = pending; | |
| unstalled | |
| } | |
| fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'tcx>) { | |
| infcx.probe(|_| { | |
| // IMPORTANT: we must not use solve any inference variables in the obligations | |
| // as this is all happening inside of a probe. We use a probe to make sure | |
| // we get all obligations involved in the overflow. We pretty much check: if | |
| // we were to do another step of `try_evaluate_obligations`, which goals would | |
| // change. | |
| self.overflowed.extend( | |
| self.pending | |
| .extract_if(.., |(o, stalled_on)| { | |
| let goal = o.as_goal(); | |
| let result = <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal( | |
| goal, | |
| o.cause.span, | |
| stalled_on.take(), | |
| ); | |
| matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. })) | |
| }) | |
| .map(|(o, _)| o), | |
| ); | |
| }) | |
| } | |
| } | |
| 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( | |
| &self, | |
| infcx: &InferCtxt<'tcx>, | |
| obligation: &PredicateObligation<'tcx>, | |
| result: &Result<GoalEvaluation<TyCtxt<'tcx>>, 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()); | |
| self.obligations.register(obligation, None); | |
| } | |
| fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<E> { | |
| self.obligations | |
| .pending | |
| .drain(..) | |
| .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) | |
| .chain( | |
| self.obligations | |
| .overflowed | |
| .drain(..) | |
| .map(|obligation| NextSolverError::Overflow(obligation)), | |
| ) | |
| .map(|e| E::from_solver_error(infcx, e)) | |
| .collect() | |
| } | |
| fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<E> { | |
| assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); | |
| let mut errors = Vec::new(); | |
| loop { | |
| let mut any_changed = false; | |
| for (mut obligation, stalled_on) in self.obligations.drain_pending(|_, _| true) { | |
| if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) { | |
| self.obligations.on_fulfillment_overflow(infcx); | |
| // Only return true errors that we have accumulated while processing. | |
| return errors; | |
| } | |
| let goal = obligation.as_goal(); | |
| let delegate = <&SolverDelegate<'tcx>>::from(infcx); | |
| if !delegate.disable_trait_solver_fast_paths() | |
| && let Some(certainty) = | |
| delegate.compute_goal_fast_path(goal, obligation.cause.span) | |
| { | |
| match certainty { | |
| // This fast path doesn't depend on region identity so it doesn't | |
| // matter if the goal contains inference variables or not, so we | |
| // don't need to call `push_hir_typeck_potentially_region_dependent_goal` | |
| // here. | |
| // | |
| // Only goals proven via the trait solver should be region dependent. | |
| Certainty::Yes => {} | |
| Certainty::Maybe(_) => { | |
| self.obligations.register(obligation, None); | |
| } | |
| } | |
| continue; | |
| } | |
| let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on); | |
| 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), | |
| )); | |
| continue; | |
| } | |
| }; | |
| // 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 { | |
| // 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); | |
| } | |
| } | |
| Certainty::Maybe(_) => self.obligations.register(obligation, stalled_on), | |
| } | |
| } | |
| 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_potentially_referencing_sub_root(infcx, vid) | |
| } | |
| 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::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_certainty { | |
| Certainty::Maybe(MaybeInfo { | |
| cause: _, | |
| opaque_types_jank: _, | |
| stalled_on_coroutines: StalledOnCoroutines::Yes, | |
| }) => true, | |
| Certainty::Maybe(_) | Certainty::Yes => false, | |
| }) | |
| }) | |
| .into_iter() | |
| .map(|(o, _)| o) | |
| .collect() | |
| } | |
| } | |
| pub enum NextSolverError<'tcx> { | |
| TrueError(PredicateObligation<'tcx>), | |
| Ambiguity(PredicateObligation<'tcx>), | |
| Overflow(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) | |
| } | |
| NextSolverError::Overflow(obligation) => { | |
| fulfillment_error_for_overflow(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(_) | NextSolverError::Overflow(_) => { | |
| ScrubbedTraitError::Ambiguity | |
| } | |
| } | |
| } | |
| } |