Handle stalling within ObligationForest
.
It is simpler if `ObligationForest` does this itself, rather than the caller having to manage it.
This commit is contained in:
parent
cdb446fec3
commit
281229a6d3
@ -42,7 +42,7 @@
|
||||
//! now considered to be in error.
|
||||
//!
|
||||
//! When the call to `process_obligations` completes, you get back an `Outcome`,
|
||||
//! which includes three bits of information:
|
||||
//! which includes two bits of information:
|
||||
//!
|
||||
//! - `completed`: a list of obligations where processing was fully
|
||||
//! completed without error (meaning that all transitive subobligations
|
||||
@ -53,13 +53,10 @@
|
||||
//! all the obligations in `C` have been found completed.
|
||||
//! - `errors`: a list of errors that occurred and associated backtraces
|
||||
//! at the time of error, which can be used to give context to the user.
|
||||
//! - `stalled`: if true, then none of the existing obligations were
|
||||
//! *shallowly successful* (that is, no callback returned `Changed(_)`).
|
||||
//! This implies that all obligations were either errors or returned an
|
||||
//! ambiguous result, which means that any further calls to
|
||||
//! `process_obligations` would simply yield back further ambiguous
|
||||
//! results. This is used by the `FulfillmentContext` to decide when it
|
||||
//! has reached a steady state.
|
||||
//!
|
||||
//! Upon completion, none of the existing obligations were *shallowly
|
||||
//! successful* (that is, no callback returned `Changed(_)`). This implies that
|
||||
//! all obligations were either errors or returned an ambiguous result.
|
||||
//!
|
||||
//! ### Implementation details
|
||||
//!
|
||||
@ -260,8 +257,6 @@ pub trait OutcomeTrait {
|
||||
type Obligation;
|
||||
|
||||
fn new() -> Self;
|
||||
fn mark_not_stalled(&mut self);
|
||||
fn is_stalled(&self) -> bool;
|
||||
fn record_completed(&mut self, outcome: &Self::Obligation);
|
||||
fn record_error(&mut self, error: Self::Error);
|
||||
}
|
||||
@ -270,14 +265,6 @@ pub trait OutcomeTrait {
|
||||
pub struct Outcome<O, E> {
|
||||
/// Backtrace of obligations that were found to be in error.
|
||||
pub errors: Vec<Error<O, E>>,
|
||||
|
||||
/// If true, then we saw no successful obligations, which means
|
||||
/// there is no point in further iteration. This is based on the
|
||||
/// assumption that when trait matching returns `Error` or
|
||||
/// `Unchanged`, those results do not affect environmental
|
||||
/// inference state. (Note that if we invoke `process_obligations`
|
||||
/// with no pending obligations, stalled will be true.)
|
||||
pub stalled: bool,
|
||||
}
|
||||
|
||||
impl<O, E> OutcomeTrait for Outcome<O, E> {
|
||||
@ -285,15 +272,7 @@ impl<O, E> OutcomeTrait for Outcome<O, E> {
|
||||
type Obligation = O;
|
||||
|
||||
fn new() -> Self {
|
||||
Self { stalled: true, errors: vec![] }
|
||||
}
|
||||
|
||||
fn mark_not_stalled(&mut self) {
|
||||
self.stalled = false;
|
||||
}
|
||||
|
||||
fn is_stalled(&self) -> bool {
|
||||
self.stalled
|
||||
Self { errors: vec![] }
|
||||
}
|
||||
|
||||
fn record_completed(&mut self, _outcome: &Self::Obligation) {
|
||||
@ -415,10 +394,7 @@ fn insert_into_error_cache(&mut self, index: usize) {
|
||||
.insert(node.obligation.as_cache_key());
|
||||
}
|
||||
|
||||
/// Performs a pass through the obligation list. This must
|
||||
/// be called in a loop until `outcome.stalled` is false.
|
||||
///
|
||||
/// This _cannot_ be unrolled (presently, at least).
|
||||
/// Performs a fixpoint computation over the obligation list.
|
||||
#[inline(never)]
|
||||
pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT
|
||||
where
|
||||
@ -427,55 +403,66 @@ pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT
|
||||
{
|
||||
let mut outcome = OUT::new();
|
||||
|
||||
// Note that the loop body can append new nodes, and those new nodes
|
||||
// will then be processed by subsequent iterations of the loop.
|
||||
//
|
||||
// We can't use an iterator for the loop because `self.nodes` is
|
||||
// appended to and the borrow checker would complain. We also can't use
|
||||
// `for index in 0..self.nodes.len() { ... }` because the range would
|
||||
// be computed with the initial length, and we would miss the appended
|
||||
// nodes. Therefore we use a `while` loop.
|
||||
let mut index = 0;
|
||||
while let Some(node) = self.nodes.get_mut(index) {
|
||||
// `processor.process_obligation` can modify the predicate within
|
||||
// `node.obligation`, and that predicate is the key used for
|
||||
// `self.active_cache`. This means that `self.active_cache` can get
|
||||
// out of sync with `nodes`. It's not very common, but it does
|
||||
// happen, and code in `compress` has to allow for it.
|
||||
if node.state.get() != NodeState::Pending {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
// Fixpoint computation: we repeat until the inner loop stalls.
|
||||
loop {
|
||||
let mut has_changed = false;
|
||||
|
||||
match processor.process_obligation(&mut node.obligation) {
|
||||
ProcessResult::Unchanged => {
|
||||
// No change in state.
|
||||
// Note that the loop body can append new nodes, and those new nodes
|
||||
// will then be processed by subsequent iterations of the loop.
|
||||
//
|
||||
// We can't use an iterator for the loop because `self.nodes` is
|
||||
// appended to and the borrow checker would complain. We also can't use
|
||||
// `for index in 0..self.nodes.len() { ... }` because the range would
|
||||
// be computed with the initial length, and we would miss the appended
|
||||
// nodes. Therefore we use a `while` loop.
|
||||
let mut index = 0;
|
||||
while let Some(node) = self.nodes.get_mut(index) {
|
||||
// `processor.process_obligation` can modify the predicate within
|
||||
// `node.obligation`, and that predicate is the key used for
|
||||
// `self.active_cache`. This means that `self.active_cache` can get
|
||||
// out of sync with `nodes`. It's not very common, but it does
|
||||
// happen, and code in `compress` has to allow for it.
|
||||
if node.state.get() != NodeState::Pending {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
ProcessResult::Changed(children) => {
|
||||
// We are not (yet) stalled.
|
||||
outcome.mark_not_stalled();
|
||||
node.state.set(NodeState::Success);
|
||||
|
||||
for child in children {
|
||||
let st = self.register_obligation_at(child, Some(index));
|
||||
if let Err(()) = st {
|
||||
// Error already reported - propagate it
|
||||
// to our node.
|
||||
self.error_at(index);
|
||||
match processor.process_obligation(&mut node.obligation) {
|
||||
ProcessResult::Unchanged => {
|
||||
// No change in state.
|
||||
}
|
||||
ProcessResult::Changed(children) => {
|
||||
// We are not (yet) stalled.
|
||||
has_changed = true;
|
||||
node.state.set(NodeState::Success);
|
||||
|
||||
for child in children {
|
||||
let st = self.register_obligation_at(child, Some(index));
|
||||
if let Err(()) = st {
|
||||
// Error already reported - propagate it
|
||||
// to our node.
|
||||
self.error_at(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
ProcessResult::Error(err) => {
|
||||
has_changed = true;
|
||||
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
|
||||
}
|
||||
}
|
||||
ProcessResult::Error(err) => {
|
||||
outcome.mark_not_stalled();
|
||||
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// If unchanged, then we saw no successful obligations, which means
|
||||
// there is no point in further iteration. This is based on the
|
||||
// assumption that when trait matching returns `Error` or
|
||||
// `Unchanged`, those results do not affect environmental inference
|
||||
// state. (Note that this will occur if we invoke
|
||||
// `process_obligations` with no pending obligations.)
|
||||
if !has_changed {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// There's no need to perform marking, cycle processing and compression when nothing
|
||||
// changed.
|
||||
if !outcome.is_stalled() {
|
||||
self.mark_successes();
|
||||
self.process_cycles(processor);
|
||||
self.compress(|obl| outcome.record_completed(obl));
|
||||
|
@ -20,7 +20,6 @@ struct ClosureObligationProcessor<OF, BF, O, E> {
|
||||
struct TestOutcome<O, E> {
|
||||
pub completed: Vec<O>,
|
||||
pub errors: Vec<Error<O, E>>,
|
||||
pub stalled: bool,
|
||||
}
|
||||
|
||||
impl<O, E> OutcomeTrait for TestOutcome<O, E>
|
||||
@ -31,15 +30,7 @@ impl<O, E> OutcomeTrait for TestOutcome<O, E>
|
||||
type Obligation = O;
|
||||
|
||||
fn new() -> Self {
|
||||
Self { errors: vec![], stalled: false, completed: vec![] }
|
||||
}
|
||||
|
||||
fn mark_not_stalled(&mut self) {
|
||||
self.stalled = false;
|
||||
}
|
||||
|
||||
fn is_stalled(&self) -> bool {
|
||||
self.stalled
|
||||
Self { errors: vec![], completed: vec![] }
|
||||
}
|
||||
|
||||
fn record_completed(&mut self, outcome: &Self::Obligation) {
|
||||
|
@ -133,27 +133,16 @@ fn select(&mut self, selcx: &mut SelectionContext<'a, 'tcx>) -> Vec<FulfillmentE
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
loop {
|
||||
debug!("select: starting another iteration");
|
||||
// Process pending obligations.
|
||||
let outcome: Outcome<_, _> = self.predicates.process_obligations(&mut FulfillProcessor {
|
||||
selcx,
|
||||
register_region_obligations: self.register_region_obligations,
|
||||
});
|
||||
|
||||
// Process pending obligations.
|
||||
let outcome: Outcome<_, _> =
|
||||
self.predicates.process_obligations(&mut FulfillProcessor {
|
||||
selcx,
|
||||
register_region_obligations: self.register_region_obligations,
|
||||
});
|
||||
debug!("select: outcome={:#?}", outcome);
|
||||
// FIXME: if we kept the original cache key, we could mark projection
|
||||
// obligations as complete for the projection cache here.
|
||||
|
||||
// FIXME: if we kept the original cache key, we could mark projection
|
||||
// obligations as complete for the projection cache here.
|
||||
|
||||
errors.extend(outcome.errors.into_iter().map(to_fulfillment_error));
|
||||
|
||||
// If nothing new was added, no need to keep looping.
|
||||
if outcome.stalled {
|
||||
break;
|
||||
}
|
||||
}
|
||||
errors.extend(outcome.errors.into_iter().map(to_fulfillment_error));
|
||||
|
||||
debug!(
|
||||
"select({} predicates remaining, {} errors) done",
|
||||
|
Loading…
Reference in New Issue
Block a user