use std::marker::PhantomData; use std::mem; use rustc_data_structures::thinvec::ExtractIf; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::{ FromSolverError, PredicateObligation, PredicateObligations, TraitEngine, }; use rustc_next_trait_solver::solve::{GenerateProofTree, HasChanged, SolverDelegateEvalExt as _}; use tracing::instrument; use self::derive_errors::*; use super::Certainty; use super::delegate::SolverDelegate; use crate::traits::{FulfillmentError, ScrubbedTraitError}; mod derive_errors; /// 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)] 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 `select_where_possible` is called. /// This also allows us to return the correct `FulfillmentError` for them. overflowed: PredicateObligations<'tcx>, pending: PredicateObligations<'tcx>, } impl<'tcx> ObligationStorage<'tcx> { fn register(&mut self, obligation: PredicateObligation<'tcx>) { self.pending.push(obligation); } fn clone_pending(&self) -> PredicateObligations<'tcx> { let mut obligations = self.pending.clone(); obligations.extend(self.overflowed.iter().cloned()); obligations } fn take_pending(&mut self) -> PredicateObligations<'tcx> { let mut obligations = mem::take(&mut self.pending); obligations.append(&mut self.overflowed); obligations } fn unstalled_for_select(&mut self) -> impl Iterator> + 'tcx { mem::take(&mut self.pending).into_iter() } 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 `select_where_possible`, which goals would // change. // FIXME: is merged, this can be removed. self.overflowed.extend(ExtractIf::new(&mut self.pending, |o| { let goal = o.as_goal(); let result = <&SolverDelegate<'tcx>>::from(infcx) .evaluate_root_goal(goal, GenerateProofTree::No, o.cause.span) .0; matches!(result, Ok((HasChanged::Yes, _))) })); }) } } 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<(HasChanged, Certainty), NoSolution>, ) { if let Some(inspector) = infcx.obligation_inspector.get() { let result = match result { Ok((_, c)) => Ok(*c), 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); } fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec { 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 select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec { assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); let mut errors = Vec::new(); for i in 0.. { if !infcx.tcx.recursion_limit().value_within_limit(i) { self.obligations.on_fulfillment_overflow(infcx); // Only return true errors that we have accumulated while processing. return errors; } let mut has_changed = false; for obligation in self.obligations.unstalled_for_select() { let goal = obligation.as_goal(); let result = <&SolverDelegate<'tcx>>::from(infcx) .evaluate_root_goal(goal, GenerateProofTree::No, obligation.cause.span) .0; self.inspect_evaluated_obligation(infcx, &obligation, &result); let (changed, certainty) = match result { Ok(result) => result, Err(NoSolution) => { errors.push(E::from_solver_error( infcx, NextSolverError::TrueError(obligation), )); continue; } }; if changed == HasChanged::Yes { has_changed = true; } match certainty { Certainty::Yes => {} Certainty::Maybe(_) => self.obligations.register(obligation), } } if !has_changed { break; } } errors } fn has_pending_obligations(&self) -> bool { !self.obligations.pending.is_empty() || !self.obligations.overflowed.is_empty() } fn pending_obligations(&self) -> PredicateObligations<'tcx> { self.obligations.clone_pending() } fn drain_unstalled_obligations(&mut self, _: &InferCtxt<'tcx>) -> PredicateObligations<'tcx> { self.obligations.take_pending() } } 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 } } } }