diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 004d1df069d6e..c43bd7b2c494e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -179,6 +179,11 @@ pub trait SolverDelegateEvalExt: SolverDelegate { stalled_on: Option>, ) -> Result, NoSolution>; + /// Checks whether a stalled goal would remain stalled if re-evaluated, without consuming + /// `stalled_on`. + fn goal_remains_stalled(&self, stalled_on: &GoalStalledOn) + -> Option; + /// Checks whether evaluating `goal` may hold while treating not-yet-defined /// opaque types as being kind of rigid. /// @@ -260,6 +265,16 @@ where } } + fn goal_remains_stalled( + &self, + stalled_on: &GoalStalledOn, + ) -> Option { + match rerunning_stalled_goal_may_make_progress(self, Some(stalled_on)) { + RerunStalled::WontMakeProgress(certainty) => Some(certainty), + RerunStalled::MayMakeProgress => None, + } + } + #[instrument(level = "debug", skip(self), ret)] fn root_goal_may_hold_opaque_types_jank( &self, diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index b221824c14575..da596fe3b44b0 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -138,7 +138,6 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { } fn inspect_evaluated_obligation( - &self, infcx: &InferCtxt<'tcx>, obligation: &PredicateObligation<'tcx>, result: &Result>, NoSolution>, @@ -196,22 +195,39 @@ 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; - for (mut obligation, stalled_on) in mem::take(&mut self.obligations.pending) { - let goal = obligation.as_goal(); - let delegate = <&SolverDelegate<'tcx>>::from(infcx); + let mut overflowed = false; + + self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| { + if overflowed { + return false; + } - let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on); - self.inspect_evaluated_obligation(infcx, &obligation, &result); + // 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 + && let Some(certainty) = delegate.goal_remains_stalled(stalled_on) + && matches!(certainty, Certainty::Maybe(_)) + { + 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), + NextSolverError::TrueError(obligation.clone()), )); - continue; + return false; } }; @@ -229,9 +245,11 @@ where obligation.recursion_depth += 1; 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; + // At this point we want to stop evaluating goals. We can't break out of + // `retain_mut`, so instead we set this flag which causes all other + // elements to be skipped. + overflowed = true; + return false; } else { any_changed = true; } @@ -253,11 +271,24 @@ where if infcx.in_hir_typeck && (obligation.has_non_region_infer() || obligation.has_free_regions()) { - infcx.push_hir_typeck_potentially_region_dependent_goal(obligation); + 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 } - Certainty::Maybe(_) => self.obligations.register(obligation, stalled_on), } + }); + if overflowed { + self.obligations.on_fulfillment_overflow(infcx); + // Only return true errors that we have accumulated while processing. + return errors; } if !any_changed {