stash overflowing obligations in fulfill

This commit is contained in:
lcnr 2024-02-23 11:25:57 +01:00
parent dd4be4cb2f
commit 3605a09ca2

View File

@ -24,7 +24,7 @@ use super::{Certainty, InferCtxtEvalExt};
/// It is also likely that we want to use slightly different datastructures /// 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`. /// here as this will have to deal with far more root goals than `evaluate_all`.
pub struct FulfillmentCtxt<'tcx> { pub struct FulfillmentCtxt<'tcx> {
obligations: Vec<PredicateObligation<'tcx>>, obligations: ObligationStorage<'tcx>,
/// The snapshot in which this context was created. Using the context /// The snapshot in which this context was created. Using the context
/// outside of this snapshot leads to subtle bugs if the snapshot /// outside of this snapshot leads to subtle bugs if the snapshot
@ -33,6 +33,57 @@ pub struct FulfillmentCtxt<'tcx> {
usable_in_snapshot: usize, usable_in_snapshot: usize,
} }
#[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: Vec<PredicateObligation<'tcx>>,
pending: Vec<PredicateObligation<'tcx>>,
}
impl<'tcx> ObligationStorage<'tcx> {
fn register(&mut self, obligation: PredicateObligation<'tcx>) {
self.pending.push(obligation);
}
fn clone_pending(&self) -> Vec<PredicateObligation<'tcx>> {
let mut obligations = self.pending.clone();
obligations.extend(self.overflowed.iter().cloned());
obligations
}
fn take_pending(&mut self) -> Vec<PredicateObligation<'tcx>> {
let mut obligations = mem::take(&mut self.pending);
obligations.extend(self.overflowed.drain(..));
obligations
}
fn unstalled_for_select(&mut self) -> impl Iterator<Item = PredicateObligation<'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.
self.overflowed.extend(self.pending.extract_if(|o| {
let goal = o.clone().into();
let result = infcx.evaluate_root_goal(goal, GenerateProofTree::Never).0;
match result {
Ok((has_changed, _)) => has_changed,
_ => false,
}
}));
})
}
}
impl<'tcx> FulfillmentCtxt<'tcx> { impl<'tcx> FulfillmentCtxt<'tcx> {
pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx> { pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx> {
assert!( assert!(
@ -40,7 +91,10 @@ impl<'tcx> FulfillmentCtxt<'tcx> {
"new trait solver fulfillment context created when \ "new trait solver fulfillment context created when \
infcx is set up for old trait solver" infcx is set up for old trait solver"
); );
FulfillmentCtxt { obligations: Vec::new(), usable_in_snapshot: infcx.num_open_snapshots() } FulfillmentCtxt {
obligations: Default::default(),
usable_in_snapshot: infcx.num_open_snapshots(),
}
} }
fn inspect_evaluated_obligation( fn inspect_evaluated_obligation(
@ -67,14 +121,24 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
obligation: PredicateObligation<'tcx>, obligation: PredicateObligation<'tcx>,
) { ) {
assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
self.obligations.push(obligation); self.obligations.register(obligation);
} }
fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> { fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
self.obligations let mut errors: Vec<_> = self
.obligations
.pending
.drain(..) .drain(..)
.map(|obligation| fulfillment_error_for_stalled(infcx, obligation)) .map(|obligation| fulfillment_error_for_stalled(infcx, obligation))
.collect() .collect();
errors.extend(self.obligations.overflowed.drain(..).map(|obligation| FulfillmentError {
root_obligation: obligation.clone(),
code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) },
obligation,
}));
errors
} }
fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> { fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
@ -82,14 +146,13 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
let mut errors = Vec::new(); let mut errors = Vec::new();
for i in 0.. { for i in 0.. {
if !infcx.tcx.recursion_limit().value_within_limit(i) { if !infcx.tcx.recursion_limit().value_within_limit(i) {
// Only return true errors that we have accumulated while processing; self.obligations.on_fulfillment_overflow(infcx);
// keep ambiguities around, *including overflows*, because they shouldn't // Only return true errors that we have accumulated while processing.
// be considered true errors.
return errors; return errors;
} }
let mut has_changed = false; let mut has_changed = false;
for obligation in mem::take(&mut self.obligations) { for obligation in self.obligations.unstalled_for_select() {
let goal = obligation.clone().into(); let goal = obligation.clone().into();
let result = infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0; let result = infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0;
self.inspect_evaluated_obligation(infcx, &obligation, &result); self.inspect_evaluated_obligation(infcx, &obligation, &result);
@ -103,7 +166,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
has_changed |= changed; has_changed |= changed;
match certainty { match certainty {
Certainty::Yes => {} Certainty::Yes => {}
Certainty::Maybe(_) => self.obligations.push(obligation), Certainty::Maybe(_) => self.obligations.register(obligation),
} }
} }
@ -116,14 +179,14 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
} }
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> { fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
self.obligations.clone() self.obligations.clone_pending()
} }
fn drain_unstalled_obligations( fn drain_unstalled_obligations(
&mut self, &mut self,
_: &InferCtxt<'tcx>, _: &InferCtxt<'tcx>,
) -> Vec<PredicateObligation<'tcx>> { ) -> Vec<PredicateObligation<'tcx>> {
std::mem::take(&mut self.obligations) self.obligations.take_pending()
} }
} }