Auto merge of #31349 - nikomatsakis:issue-31157-obligation-forest-cache, r=aturon
Have the `ObligationForest` keep some per-tree state (or type `T`) and have it give a mutable reference for use when processing obligations. In this case, it will be a hashmap. This obviously affects the work that @soltanmm has been doing on snapshotting. I partly want to toss this out there for discussion. Fixes #31157. (The test in question goes to approx. 30s instead of 5 minutes for me.) cc #30977. cc @aturon @arielb1 @soltanmm r? @aturon who reviewed original `ObligationForest`
This commit is contained in:
commit
6dc112dbb7
5 changed files with 313 additions and 151 deletions
|
@ -36,6 +36,7 @@ pub struct GlobalFulfilledPredicates<'tcx> {
|
||||||
dep_graph: DepGraph,
|
dep_graph: DepGraph,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LocalFulfilledPredicates<'tcx> {
|
pub struct LocalFulfilledPredicates<'tcx> {
|
||||||
set: FnvHashSet<ty::Predicate<'tcx>>
|
set: FnvHashSet<ty::Predicate<'tcx>>
|
||||||
}
|
}
|
||||||
|
@ -66,7 +67,8 @@ pub struct FulfillmentContext<'tcx> {
|
||||||
|
|
||||||
// A list of all obligations that have been registered with this
|
// A list of all obligations that have been registered with this
|
||||||
// fulfillment context.
|
// fulfillment context.
|
||||||
predicates: ObligationForest<PendingPredicateObligation<'tcx>>,
|
predicates: ObligationForest<PendingPredicateObligation<'tcx>,
|
||||||
|
LocalFulfilledPredicates<'tcx>>,
|
||||||
|
|
||||||
// A set of constraints that regionck must validate. Each
|
// A set of constraints that regionck must validate. Each
|
||||||
// constraint has the form `T:'a`, meaning "some type `T` must
|
// constraint has the form `T:'a`, meaning "some type `T` must
|
||||||
|
@ -192,7 +194,7 @@ impl<'tcx> FulfillmentContext<'tcx> {
|
||||||
obligation: obligation,
|
obligation: obligation,
|
||||||
stalled_on: vec![]
|
stalled_on: vec![]
|
||||||
};
|
};
|
||||||
self.predicates.push_root(obligation);
|
self.predicates.push_tree(obligation, LocalFulfilledPredicates::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_obligations(&self,
|
pub fn region_obligations(&self,
|
||||||
|
@ -278,10 +280,11 @@ impl<'tcx> FulfillmentContext<'tcx> {
|
||||||
let outcome = {
|
let outcome = {
|
||||||
let region_obligations = &mut self.region_obligations;
|
let region_obligations = &mut self.region_obligations;
|
||||||
self.predicates.process_obligations(
|
self.predicates.process_obligations(
|
||||||
|obligation, backtrace| process_predicate(selcx,
|
|obligation, tree, backtrace| process_predicate(selcx,
|
||||||
obligation,
|
tree,
|
||||||
backtrace,
|
obligation,
|
||||||
region_obligations))
|
backtrace,
|
||||||
|
region_obligations))
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("select_where_possible: outcome={:?}", outcome);
|
debug!("select_where_possible: outcome={:?}", outcome);
|
||||||
|
@ -315,61 +318,97 @@ impl<'tcx> FulfillmentContext<'tcx> {
|
||||||
|
|
||||||
/// Like `process_predicate1`, but wrap result into a pending predicate.
|
/// Like `process_predicate1`, but wrap result into a pending predicate.
|
||||||
fn process_predicate<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
fn process_predicate<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
||||||
|
tree_cache: &mut LocalFulfilledPredicates<'tcx>,
|
||||||
pending_obligation: &mut PendingPredicateObligation<'tcx>,
|
pending_obligation: &mut PendingPredicateObligation<'tcx>,
|
||||||
backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
|
mut backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
|
||||||
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>)
|
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>)
|
||||||
-> Result<Option<Vec<PendingPredicateObligation<'tcx>>>,
|
-> Result<Option<Vec<PendingPredicateObligation<'tcx>>>,
|
||||||
FulfillmentErrorCode<'tcx>>
|
FulfillmentErrorCode<'tcx>>
|
||||||
{
|
{
|
||||||
match process_predicate1(selcx, pending_obligation, backtrace, region_obligations) {
|
match process_predicate1(selcx, pending_obligation, backtrace.clone(), region_obligations) {
|
||||||
Ok(Some(v)) => {
|
Ok(Some(v)) => {
|
||||||
// FIXME(#30977) the right thing to do here, I think, is to permit
|
// FIXME(#30977) The code below is designed to detect (and
|
||||||
// DAGs. That is, we should detect whenever this predicate
|
// permit) DAGs, while still ensuring that the reasoning
|
||||||
// has appeared somewhere in the current tree./ If it's a
|
// is acyclic. However, it does a few things
|
||||||
// parent, that's a cycle, and we should either error out
|
// suboptimally. For example, it refreshes type variables
|
||||||
// or consider it ok. But if it's NOT a parent, we can
|
// a lot, probably more than needed, but also less than
|
||||||
// ignore it, since it will be proven (or not) separately.
|
// you might want.
|
||||||
// However, this is a touch tricky, so I'm doing something
|
//
|
||||||
// a bit hackier for now so that the `huge-struct.rs` passes.
|
// - more than needed: I want to be very sure we don't
|
||||||
|
// accidentally treat a cycle as a DAG, so I am
|
||||||
|
// refreshing type variables as we walk the ancestors;
|
||||||
|
// but we are going to repeat this a lot, which is
|
||||||
|
// sort of silly, and it would be nicer to refresh
|
||||||
|
// them *in place* so that later predicate processing
|
||||||
|
// can benefit from the same work;
|
||||||
|
// - less than you might want: we only add items in the cache here,
|
||||||
|
// but maybe we learn more about type variables and could add them into
|
||||||
|
// the cache later on.
|
||||||
|
|
||||||
let tcx = selcx.tcx();
|
let tcx = selcx.tcx();
|
||||||
|
|
||||||
let retain_vec: Vec<_> = {
|
// Compute a little FnvHashSet for the ancestors. We only
|
||||||
let mut dedup = FnvHashSet();
|
// do this the first time that we care.
|
||||||
v.iter()
|
let mut cache = None;
|
||||||
.map(|o| {
|
let mut is_ancestor = |predicate: &ty::Predicate<'tcx>| {
|
||||||
|
if cache.is_none() {
|
||||||
|
let mut c = FnvHashSet();
|
||||||
|
for ancestor in backtrace.by_ref() {
|
||||||
|
// Ugh. This just feels ridiculously
|
||||||
|
// inefficient. But we need to compare
|
||||||
|
// predicates without being concerned about
|
||||||
|
// the vagaries of type inference, so for now
|
||||||
|
// just ensure that they are always
|
||||||
|
// up-to-date. (I suppose we could just use a
|
||||||
|
// snapshot and check if they are unifiable?)
|
||||||
|
let resolved_predicate =
|
||||||
|
selcx.infcx().resolve_type_vars_if_possible(
|
||||||
|
&ancestor.obligation.predicate);
|
||||||
|
c.insert(resolved_predicate);
|
||||||
|
}
|
||||||
|
cache = Some(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.as_ref().unwrap().contains(predicate)
|
||||||
|
};
|
||||||
|
|
||||||
|
let pending_predicate_obligations: Vec<_> =
|
||||||
|
v.into_iter()
|
||||||
|
.filter_map(|obligation| {
|
||||||
|
// Probably silly, but remove any inference
|
||||||
|
// variables. This is actually crucial to the
|
||||||
|
// ancestor check below, but it's not clear that
|
||||||
|
// it makes sense to ALWAYS do it.
|
||||||
|
let obligation = selcx.infcx().resolve_type_vars_if_possible(&obligation);
|
||||||
|
|
||||||
// Screen out obligations that we know globally
|
// Screen out obligations that we know globally
|
||||||
// are true. This should really be the DAG check
|
// are true. This should really be the DAG check
|
||||||
// mentioned above.
|
// mentioned above.
|
||||||
if tcx.fulfilled_predicates.borrow().check_duplicate(&o.predicate) {
|
if tcx.fulfilled_predicates.borrow().check_duplicate(&obligation.predicate) {
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we see two siblings that are exactly the
|
// Check whether this obligation appears somewhere else in the tree.
|
||||||
// same, no need to add them twice.
|
if tree_cache.is_duplicate_or_add(&obligation.predicate) {
|
||||||
if !dedup.insert(&o.predicate) {
|
// If the obligation appears as a parent,
|
||||||
return false;
|
// allow it, because that is a cycle.
|
||||||
|
// Otherwise though we can just ignore
|
||||||
|
// it. Note that we have to be careful around
|
||||||
|
// inference variables here -- for the
|
||||||
|
// purposes of the ancestor check, we retain
|
||||||
|
// the invariant that all type variables are
|
||||||
|
// fully refreshed.
|
||||||
|
if !(&mut is_ancestor)(&obligation.predicate) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
Some(PendingPredicateObligation {
|
||||||
|
obligation: obligation,
|
||||||
|
stalled_on: vec![]
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
};
|
|
||||||
|
|
||||||
let pending_predicate_obligations =
|
|
||||||
v.into_iter()
|
|
||||||
.zip(retain_vec)
|
|
||||||
.flat_map(|(o, retain)| {
|
|
||||||
if retain {
|
|
||||||
Some(PendingPredicateObligation {
|
|
||||||
obligation: o,
|
|
||||||
stalled_on: vec![]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Some(pending_predicate_obligations))
|
Ok(Some(pending_predicate_obligations))
|
||||||
}
|
}
|
||||||
|
@ -405,7 +444,7 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
||||||
pending_obligation.stalled_on = vec![];
|
pending_obligation.stalled_on = vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
let obligation = &pending_obligation.obligation;
|
let obligation = &mut pending_obligation.obligation;
|
||||||
|
|
||||||
// If we exceed the recursion limit, take a moment to look for a
|
// If we exceed the recursion limit, take a moment to look for a
|
||||||
// cycle so we can give a better error report from here, where we
|
// cycle so we can give a better error report from here, where we
|
||||||
|
@ -417,8 +456,16 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obligation.predicate.has_infer_types() {
|
||||||
|
obligation.predicate = selcx.infcx().resolve_type_vars_if_possible(&obligation.predicate);
|
||||||
|
}
|
||||||
|
|
||||||
match obligation.predicate {
|
match obligation.predicate {
|
||||||
ty::Predicate::Trait(ref data) => {
|
ty::Predicate::Trait(ref data) => {
|
||||||
|
if selcx.tcx().fulfilled_predicates.borrow().check_duplicate_trait(data) {
|
||||||
|
return Ok(Some(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
if coinductive_match(selcx, obligation, data, &backtrace) {
|
if coinductive_match(selcx, obligation, data, &backtrace) {
|
||||||
return Ok(Some(vec![]));
|
return Ok(Some(vec![]));
|
||||||
}
|
}
|
||||||
|
@ -426,9 +473,14 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
||||||
let trait_obligation = obligation.with(data.clone());
|
let trait_obligation = obligation.with(data.clone());
|
||||||
match selcx.select(&trait_obligation) {
|
match selcx.select(&trait_obligation) {
|
||||||
Ok(Some(vtable)) => {
|
Ok(Some(vtable)) => {
|
||||||
|
info!("selecting trait `{:?}` at depth {} yielded Ok(Some)",
|
||||||
|
data, obligation.recursion_depth);
|
||||||
Ok(Some(vtable.nested_obligations()))
|
Ok(Some(vtable.nested_obligations()))
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
|
info!("selecting trait `{:?}` at depth {} yielded Ok(None)",
|
||||||
|
data, obligation.recursion_depth);
|
||||||
|
|
||||||
// This is a bit subtle: for the most part, the
|
// This is a bit subtle: for the most part, the
|
||||||
// only reason we can fail to make progress on
|
// only reason we can fail to make progress on
|
||||||
// trait selection is because we don't have enough
|
// trait selection is because we don't have enough
|
||||||
|
@ -457,6 +509,8 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Err(selection_err) => {
|
Err(selection_err) => {
|
||||||
|
info!("selecting trait `{:?}` at depth {} yielded Err",
|
||||||
|
data, obligation.recursion_depth);
|
||||||
Err(CodeSelectionError(selection_err))
|
Err(CodeSelectionError(selection_err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -642,18 +696,28 @@ impl<'tcx> GlobalFulfilledPredicates<'tcx> {
|
||||||
|
|
||||||
pub fn check_duplicate(&self, key: &ty::Predicate<'tcx>) -> bool {
|
pub fn check_duplicate(&self, key: &ty::Predicate<'tcx>) -> bool {
|
||||||
if let ty::Predicate::Trait(ref data) = *key {
|
if let ty::Predicate::Trait(ref data) = *key {
|
||||||
// For the global predicate registry, when we find a match, it
|
self.check_duplicate_trait(data)
|
||||||
// may have been computed by some other task, so we want to
|
} else {
|
||||||
// add a read from the node corresponding to the predicate
|
false
|
||||||
// processing to make sure we get the transitive dependencies.
|
|
||||||
if self.set.contains(data) {
|
|
||||||
debug_assert!(data.is_global());
|
|
||||||
self.dep_graph.read(data.dep_node());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
pub fn check_duplicate_trait(&self, data: &ty::PolyTraitPredicate<'tcx>) -> bool {
|
||||||
|
// For the global predicate registry, when we find a match, it
|
||||||
|
// may have been computed by some other task, so we want to
|
||||||
|
// add a read from the node corresponding to the predicate
|
||||||
|
// processing to make sure we get the transitive dependencies.
|
||||||
|
if self.set.contains(data) {
|
||||||
|
debug_assert!(data.is_global());
|
||||||
|
self.dep_graph.read(data.dep_node());
|
||||||
|
debug!("check_duplicate: global predicate `{:?}` already proved elsewhere", data);
|
||||||
|
|
||||||
|
info!("check_duplicate_trait hit: `{:?}`", data);
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_if_global(&mut self, key: &ty::Predicate<'tcx>) {
|
fn add_if_global(&mut self, key: &ty::Predicate<'tcx>) {
|
||||||
|
@ -663,7 +727,10 @@ impl<'tcx> GlobalFulfilledPredicates<'tcx> {
|
||||||
// already has the required read edges, so we don't need
|
// already has the required read edges, so we don't need
|
||||||
// to add any more edges here.
|
// to add any more edges here.
|
||||||
if data.is_global() {
|
if data.is_global() {
|
||||||
self.set.insert(data.clone());
|
if self.set.insert(data.clone()) {
|
||||||
|
debug!("add_if_global: global predicate `{:?}` added", data);
|
||||||
|
info!("check_duplicate_trait entry: `{:?}`", data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,18 @@ place).
|
||||||
`ObligationForest` supports two main public operations (there are a
|
`ObligationForest` supports two main public operations (there are a
|
||||||
few others not discussed here):
|
few others not discussed here):
|
||||||
|
|
||||||
1. Add a new root obligation (`push_root`).
|
1. Add a new root obligations (`push_tree`).
|
||||||
2. Process the pending obligations (`process_obligations`).
|
2. Process the pending obligations (`process_obligations`).
|
||||||
|
|
||||||
When a new obligation `N` is added, it becomes the root of an
|
When a new obligation `N` is added, it becomes the root of an
|
||||||
obligation tree. This tree is a singleton to start, so `N` is both the
|
obligation tree. This tree can also carry some per-tree state `T`,
|
||||||
root and the only leaf. Each time the `process_obligations` method is
|
which is given at the same time. This tree is a singleton to start, so
|
||||||
called, it will invoke its callback with every pending obligation (so
|
`N` is both the root and the only leaf. Each time the
|
||||||
that will include `N`, the first time). The callback shoud process the
|
`process_obligations` method is called, it will invoke its callback
|
||||||
obligation `O` that it is given and return one of three results:
|
with every pending obligation (so that will include `N`, the first
|
||||||
|
time). The callback also receives a (mutable) reference to the
|
||||||
|
per-tree state `T`. The callback should process the obligation `O`
|
||||||
|
that it is given and return one of three results:
|
||||||
|
|
||||||
- `Ok(None)` -> ambiguous result. Obligation was neither a success
|
- `Ok(None)` -> ambiguous result. Obligation was neither a success
|
||||||
nor a failure. It is assumed that further attempts to process the
|
nor a failure. It is assumed that further attempts to process the
|
||||||
|
|
|
@ -19,11 +19,16 @@ use std::fmt::Debug;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
mod node_index;
|
mod node_index;
|
||||||
|
use self::node_index::NodeIndex;
|
||||||
|
|
||||||
|
mod tree_index;
|
||||||
|
use self::tree_index::TreeIndex;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
pub struct ObligationForest<O> {
|
pub struct ObligationForest<O,T> {
|
||||||
/// The list of obligations. In between calls to
|
/// The list of obligations. In between calls to
|
||||||
/// `process_obligations`, this list only contains nodes in the
|
/// `process_obligations`, this list only contains nodes in the
|
||||||
/// `Pending` or `Success` state (with a non-zero number of
|
/// `Pending` or `Success` state (with a non-zero number of
|
||||||
|
@ -37,6 +42,7 @@ pub struct ObligationForest<O> {
|
||||||
/// at a higher index than its parent. This is needed by the
|
/// at a higher index than its parent. This is needed by the
|
||||||
/// backtrace iterator (which uses `split_at`).
|
/// backtrace iterator (which uses `split_at`).
|
||||||
nodes: Vec<Node<O>>,
|
nodes: Vec<Node<O>>,
|
||||||
|
trees: Vec<Tree<T>>,
|
||||||
snapshots: Vec<usize>
|
snapshots: Vec<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +50,15 @@ pub struct Snapshot {
|
||||||
len: usize,
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::node_index::NodeIndex;
|
struct Tree<T> {
|
||||||
|
root: NodeIndex,
|
||||||
|
state: T,
|
||||||
|
}
|
||||||
|
|
||||||
struct Node<O> {
|
struct Node<O> {
|
||||||
state: NodeState<O>,
|
state: NodeState<O>,
|
||||||
parent: Option<NodeIndex>,
|
parent: Option<NodeIndex>,
|
||||||
root: NodeIndex, // points to the root, which may be the current node
|
tree: TreeIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state of one node in some tree within the forest. This
|
/// The state of one node in some tree within the forest. This
|
||||||
|
@ -99,9 +108,10 @@ pub struct Error<O,E> {
|
||||||
pub backtrace: Vec<O>,
|
pub backtrace: Vec<O>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<O: Debug> ObligationForest<O> {
|
impl<O: Debug, T: Debug> ObligationForest<O, T> {
|
||||||
pub fn new() -> ObligationForest<O> {
|
pub fn new() -> ObligationForest<O, T> {
|
||||||
ObligationForest {
|
ObligationForest {
|
||||||
|
trees: vec![],
|
||||||
nodes: vec![],
|
nodes: vec![],
|
||||||
snapshots: vec![]
|
snapshots: vec![]
|
||||||
}
|
}
|
||||||
|
@ -114,30 +124,39 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_snapshot(&mut self) -> Snapshot {
|
pub fn start_snapshot(&mut self) -> Snapshot {
|
||||||
self.snapshots.push(self.nodes.len());
|
self.snapshots.push(self.trees.len());
|
||||||
Snapshot { len: self.snapshots.len() }
|
Snapshot { len: self.snapshots.len() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit_snapshot(&mut self, snapshot: Snapshot) {
|
pub fn commit_snapshot(&mut self, snapshot: Snapshot) {
|
||||||
assert_eq!(snapshot.len, self.snapshots.len());
|
assert_eq!(snapshot.len, self.snapshots.len());
|
||||||
let nodes_len = self.snapshots.pop().unwrap();
|
let trees_len = self.snapshots.pop().unwrap();
|
||||||
assert!(self.nodes.len() >= nodes_len);
|
assert!(self.trees.len() >= trees_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rollback_snapshot(&mut self, snapshot: Snapshot) {
|
pub fn rollback_snapshot(&mut self, snapshot: Snapshot) {
|
||||||
// Check that we are obeying stack discipline.
|
// Check that we are obeying stack discipline.
|
||||||
assert_eq!(snapshot.len, self.snapshots.len());
|
assert_eq!(snapshot.len, self.snapshots.len());
|
||||||
let nodes_len = self.snapshots.pop().unwrap();
|
let trees_len = self.snapshots.pop().unwrap();
|
||||||
|
|
||||||
// The only action permitted while in a snapshot is to push
|
// If nothing happened in snapshot, done.
|
||||||
// new root obligations. Because no processing will have been
|
if self.trees.len() == trees_len {
|
||||||
// done, those roots should still be in the pending state.
|
return;
|
||||||
debug_assert!(self.nodes[nodes_len..].iter().all(|n| match n.state {
|
}
|
||||||
NodeState::Pending { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.nodes.truncate(nodes_len);
|
// Find root of first tree; because nothing can happen in a
|
||||||
|
// snapshot but pushing trees, all nodes after that should be
|
||||||
|
// roots of other trees as well
|
||||||
|
let first_root_index = self.trees[trees_len].root.get();
|
||||||
|
debug_assert!(
|
||||||
|
self.nodes[first_root_index..]
|
||||||
|
.iter()
|
||||||
|
.zip(first_root_index..)
|
||||||
|
.all(|(root, root_index)| self.trees[root.tree.get()].root.get() == root_index));
|
||||||
|
|
||||||
|
// Pop off tree/root pairs pushed during snapshot.
|
||||||
|
self.trees.truncate(trees_len);
|
||||||
|
self.nodes.truncate(first_root_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn in_snapshot(&self) -> bool {
|
pub fn in_snapshot(&self) -> bool {
|
||||||
|
@ -147,9 +166,11 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
/// Adds a new tree to the forest.
|
/// Adds a new tree to the forest.
|
||||||
///
|
///
|
||||||
/// This CAN be done during a snapshot.
|
/// This CAN be done during a snapshot.
|
||||||
pub fn push_root(&mut self, obligation: O) {
|
pub fn push_tree(&mut self, obligation: O, tree_state: T) {
|
||||||
let index = NodeIndex::new(self.nodes.len());
|
let index = NodeIndex::new(self.nodes.len());
|
||||||
self.nodes.push(Node::new(index, None, obligation));
|
let tree = TreeIndex::new(self.trees.len());
|
||||||
|
self.trees.push(Tree { root: index, state: tree_state });
|
||||||
|
self.nodes.push(Node::new(tree, None, obligation));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert all remaining obligations to the given error.
|
/// Convert all remaining obligations to the given error.
|
||||||
|
@ -186,7 +207,7 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
///
|
///
|
||||||
/// This CANNOT be unrolled (presently, at least).
|
/// This CANNOT be unrolled (presently, at least).
|
||||||
pub fn process_obligations<E,F>(&mut self, mut action: F) -> Outcome<O,E>
|
pub fn process_obligations<E,F>(&mut self, mut action: F) -> Outcome<O,E>
|
||||||
where E: Debug, F: FnMut(&mut O, Backtrace<O>) -> Result<Option<Vec<O>>, E>
|
where E: Debug, F: FnMut(&mut O, &mut T, Backtrace<O>) -> Result<Option<Vec<O>>, E>
|
||||||
{
|
{
|
||||||
debug!("process_obligations(len={})", self.nodes.len());
|
debug!("process_obligations(len={})", self.nodes.len());
|
||||||
assert!(!self.in_snapshot()); // cannot unroll this action
|
assert!(!self.in_snapshot()); // cannot unroll this action
|
||||||
|
@ -210,7 +231,7 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
index, self.nodes[index].state);
|
index, self.nodes[index].state);
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
let parent = self.nodes[index].parent;
|
let Node { tree, parent, .. } = self.nodes[index];
|
||||||
let (prefix, suffix) = self.nodes.split_at_mut(index);
|
let (prefix, suffix) = self.nodes.split_at_mut(index);
|
||||||
let backtrace = Backtrace::new(prefix, parent);
|
let backtrace = Backtrace::new(prefix, parent);
|
||||||
match suffix[0].state {
|
match suffix[0].state {
|
||||||
|
@ -218,7 +239,7 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
NodeState::Success { .. } =>
|
NodeState::Success { .. } =>
|
||||||
continue,
|
continue,
|
||||||
NodeState::Pending { ref mut obligation } =>
|
NodeState::Pending { ref mut obligation } =>
|
||||||
action(obligation, backtrace),
|
action(obligation, &mut self.trees[tree.get()].state, backtrace),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -268,11 +289,11 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
self.update_parent(index);
|
self.update_parent(index);
|
||||||
} else {
|
} else {
|
||||||
// create child work
|
// create child work
|
||||||
let root_index = self.nodes[index].root;
|
let tree_index = self.nodes[index].tree;
|
||||||
let node_index = NodeIndex::new(index);
|
let node_index = NodeIndex::new(index);
|
||||||
self.nodes.extend(
|
self.nodes.extend(
|
||||||
children.into_iter()
|
children.into_iter()
|
||||||
.map(|o| Node::new(root_index, Some(node_index), o)));
|
.map(|o| Node::new(tree_index, Some(node_index), o)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// change state from `Pending` to `Success`, temporarily swapping in `Error`
|
// change state from `Pending` to `Success`, temporarily swapping in `Error`
|
||||||
|
@ -311,8 +332,9 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
/// skip the remaining obligations from a tree once some other
|
/// skip the remaining obligations from a tree once some other
|
||||||
/// node in the tree is found to be in error.
|
/// node in the tree is found to be in error.
|
||||||
fn inherit_error(&mut self, child: usize) {
|
fn inherit_error(&mut self, child: usize) {
|
||||||
let root = self.nodes[child].root.get();
|
let tree = self.nodes[child].tree;
|
||||||
if let NodeState::Error = self.nodes[root].state {
|
let root = self.trees[tree.get()].root;
|
||||||
|
if let NodeState::Error = self.nodes[root.get()].state {
|
||||||
self.nodes[child].state = NodeState::Error;
|
self.nodes[child].state = NodeState::Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +375,8 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
/// indices. Cannot be used during a transaction.
|
/// indices. Cannot be used during a transaction.
|
||||||
fn compress(&mut self) -> Vec<O> {
|
fn compress(&mut self) -> Vec<O> {
|
||||||
assert!(!self.in_snapshot()); // didn't write code to unroll this action
|
assert!(!self.in_snapshot()); // didn't write code to unroll this action
|
||||||
let mut rewrites: Vec<_> = (0..self.nodes.len()).collect();
|
let mut node_rewrites: Vec<_> = (0..self.nodes.len()).collect();
|
||||||
|
let mut tree_rewrites: Vec<_> = (0..self.trees.len()).collect();
|
||||||
|
|
||||||
// Finish propagating error state. Note that in this case we
|
// Finish propagating error state. Note that in this case we
|
||||||
// only have to check immediate parents, rather than all
|
// only have to check immediate parents, rather than all
|
||||||
|
@ -366,43 +389,69 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine which trees to remove by checking if their root
|
||||||
|
// is popped.
|
||||||
|
let mut dead_trees = 0;
|
||||||
|
let trees_len = self.trees.len();
|
||||||
|
for i in 0..trees_len {
|
||||||
|
let root_node = self.trees[i].root;
|
||||||
|
if self.nodes[root_node.get()].is_popped() {
|
||||||
|
dead_trees += 1;
|
||||||
|
} else if dead_trees > 0 {
|
||||||
|
self.trees.swap(i, i - dead_trees);
|
||||||
|
tree_rewrites[i] -= dead_trees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now go through and move all nodes that are either
|
// Now go through and move all nodes that are either
|
||||||
// successful or which have an error over into to the end of
|
// successful or which have an error over into to the end of
|
||||||
// the list, preserving the relative order of the survivors
|
// the list, preserving the relative order of the survivors
|
||||||
// (which is important for the `inherit_error` logic).
|
// (which is important for the `inherit_error` logic).
|
||||||
let mut dead = 0;
|
let mut dead_nodes = 0;
|
||||||
for i in 0..nodes_len {
|
for i in 0..nodes_len {
|
||||||
if self.nodes[i].is_popped() {
|
if self.nodes[i].is_popped() {
|
||||||
dead += 1;
|
dead_nodes += 1;
|
||||||
} else if dead > 0 {
|
} else if dead_nodes > 0 {
|
||||||
self.nodes.swap(i, i - dead);
|
self.nodes.swap(i, i - dead_nodes);
|
||||||
rewrites[i] -= dead;
|
node_rewrites[i] -= dead_nodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No compression needed.
|
||||||
|
if dead_nodes == 0 && dead_trees == 0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop off the trees we killed.
|
||||||
|
self.trees.truncate(trees_len - dead_trees);
|
||||||
|
|
||||||
// Pop off all the nodes we killed and extract the success
|
// Pop off all the nodes we killed and extract the success
|
||||||
// stories.
|
// stories.
|
||||||
let successful =
|
let successful =
|
||||||
(0 .. dead).map(|_| self.nodes.pop().unwrap())
|
(0 .. dead_nodes)
|
||||||
.flat_map(|node| match node.state {
|
.map(|_| self.nodes.pop().unwrap())
|
||||||
NodeState::Error => None,
|
.flat_map(|node| match node.state {
|
||||||
NodeState::Pending { .. } => unreachable!(),
|
NodeState::Error => None,
|
||||||
NodeState::Success { obligation, num_incomplete_children } => {
|
NodeState::Pending { .. } => unreachable!(),
|
||||||
assert_eq!(num_incomplete_children, 0);
|
NodeState::Success { obligation, num_incomplete_children } => {
|
||||||
Some(obligation)
|
assert_eq!(num_incomplete_children, 0);
|
||||||
}
|
Some(obligation)
|
||||||
})
|
}
|
||||||
.collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Adjust the parent indices, since we compressed things.
|
// Adjust the various indices, since we compressed things.
|
||||||
|
for tree in &mut self.trees {
|
||||||
|
tree.root = NodeIndex::new(node_rewrites[tree.root.get()]);
|
||||||
|
}
|
||||||
for node in &mut self.nodes {
|
for node in &mut self.nodes {
|
||||||
if let Some(ref mut index) = node.parent {
|
if let Some(ref mut index) = node.parent {
|
||||||
let new_index = rewrites[index.get()];
|
let new_index = node_rewrites[index.get()];
|
||||||
debug_assert!(new_index < (nodes_len - dead));
|
debug_assert!(new_index < (nodes_len - dead_nodes));
|
||||||
*index = NodeIndex::new(new_index);
|
*index = NodeIndex::new(new_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.root = NodeIndex::new(rewrites[node.root.get()]);
|
node.tree = TreeIndex::new(tree_rewrites[node.tree.get()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
successful
|
successful
|
||||||
|
@ -410,11 +459,11 @@ impl<O: Debug> ObligationForest<O> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<O> Node<O> {
|
impl<O> Node<O> {
|
||||||
fn new(root: NodeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
|
fn new(tree: TreeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
|
||||||
Node {
|
Node {
|
||||||
parent: parent,
|
parent: parent,
|
||||||
state: NodeState::Pending { obligation: obligation },
|
state: NodeState::Pending { obligation: obligation },
|
||||||
root: root
|
tree: tree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,22 +13,24 @@ use super::{ObligationForest, Outcome, Error};
|
||||||
#[test]
|
#[test]
|
||||||
fn push_pop() {
|
fn push_pop() {
|
||||||
let mut forest = ObligationForest::new();
|
let mut forest = ObligationForest::new();
|
||||||
forest.push_root("A");
|
forest.push_tree("A", "A");
|
||||||
forest.push_root("B");
|
forest.push_tree("B", "B");
|
||||||
forest.push_root("C");
|
forest.push_tree("C", "C");
|
||||||
|
|
||||||
// first round, B errors out, A has subtasks, and C completes, creating this:
|
// first round, B errors out, A has subtasks, and C completes, creating this:
|
||||||
// A |-> A.1
|
// A |-> A.1
|
||||||
// |-> A.2
|
// |-> A.2
|
||||||
// |-> A.3
|
// |-> A.3
|
||||||
let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
match *obligation {
|
forest.process_obligations(|obligation, tree, _| {
|
||||||
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
"B" => Err("B is for broken"),
|
match *obligation {
|
||||||
"C" => Ok(Some(vec![])),
|
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
||||||
_ => unreachable!(),
|
"B" => Err("B is for broken"),
|
||||||
}
|
"C" => Ok(Some(vec![])),
|
||||||
});
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
assert_eq!(ok, vec!["C"]);
|
assert_eq!(ok, vec!["C"]);
|
||||||
assert_eq!(err, vec![Error {error: "B is for broken",
|
assert_eq!(err, vec![Error {error: "B is for broken",
|
||||||
backtrace: vec!["B"]}]);
|
backtrace: vec!["B"]}]);
|
||||||
|
@ -39,9 +41,10 @@ fn push_pop() {
|
||||||
// |-> A.3 |-> A.3.i
|
// |-> A.3 |-> A.3.i
|
||||||
// D |-> D.1
|
// D |-> D.1
|
||||||
// |-> D.2
|
// |-> D.2
|
||||||
forest.push_root("D");
|
forest.push_tree("D", "D");
|
||||||
let Outcome { completed: ok, errors: err, .. }: Outcome<&'static str, ()> =
|
let Outcome { completed: ok, errors: err, .. }: Outcome<&'static str, ()> =
|
||||||
forest.process_obligations(|obligation, _| {
|
forest.process_obligations(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A.1" => Ok(None),
|
"A.1" => Ok(None),
|
||||||
"A.2" => Ok(None),
|
"A.2" => Ok(None),
|
||||||
|
@ -58,26 +61,30 @@ fn push_pop() {
|
||||||
// propagates to A.3.i, but not D.1 or D.2.
|
// propagates to A.3.i, but not D.1 or D.2.
|
||||||
// D |-> D.1 |-> D.1.i
|
// D |-> D.1 |-> D.1.i
|
||||||
// |-> D.2 |-> D.2.i
|
// |-> D.2 |-> D.2.i
|
||||||
let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
match *obligation {
|
forest.process_obligations(|obligation, tree, _| {
|
||||||
"A.1" => Ok(Some(vec![])),
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
"A.2" => Err("A is for apple"),
|
match *obligation {
|
||||||
"D.1" => Ok(Some(vec!["D.1.i"])),
|
"A.1" => Ok(Some(vec![])),
|
||||||
"D.2" => Ok(Some(vec!["D.2.i"])),
|
"A.2" => Err("A is for apple"),
|
||||||
_ => unreachable!(),
|
"D.1" => Ok(Some(vec!["D.1.i"])),
|
||||||
}
|
"D.2" => Ok(Some(vec!["D.2.i"])),
|
||||||
});
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
assert_eq!(ok, vec!["A.1"]);
|
assert_eq!(ok, vec!["A.1"]);
|
||||||
assert_eq!(err, vec![Error { error: "A is for apple",
|
assert_eq!(err, vec![Error { error: "A is for apple",
|
||||||
backtrace: vec!["A.2", "A"] }]);
|
backtrace: vec!["A.2", "A"] }]);
|
||||||
|
|
||||||
// fourth round: error in D.1.i that should propagate to D.2.i
|
// fourth round: error in D.1.i that should propagate to D.2.i
|
||||||
let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
match *obligation {
|
forest.process_obligations(|obligation, tree, _| {
|
||||||
"D.1.i" => Err("D is for dumb"),
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
_ => panic!("unexpected obligation {:?}", obligation),
|
match *obligation {
|
||||||
}
|
"D.1.i" => Err("D is for dumb"),
|
||||||
});
|
_ => panic!("unexpected obligation {:?}", obligation),
|
||||||
|
}
|
||||||
|
});
|
||||||
assert_eq!(ok, Vec::<&'static str>::new());
|
assert_eq!(ok, Vec::<&'static str>::new());
|
||||||
assert_eq!(err, vec![Error { error: "D is for dumb",
|
assert_eq!(err, vec![Error { error: "D is for dumb",
|
||||||
backtrace: vec!["D.1.i", "D.1", "D"] }]);
|
backtrace: vec!["D.1.i", "D.1", "D"] }]);
|
||||||
|
@ -94,10 +101,11 @@ fn push_pop() {
|
||||||
#[test]
|
#[test]
|
||||||
fn success_in_grandchildren() {
|
fn success_in_grandchildren() {
|
||||||
let mut forest = ObligationForest::new();
|
let mut forest = ObligationForest::new();
|
||||||
forest.push_root("A");
|
forest.push_tree("A", "A");
|
||||||
|
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, _| {
|
forest.process_obligations::<(),_>(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -107,7 +115,8 @@ fn success_in_grandchildren() {
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
|
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, _| {
|
forest.process_obligations::<(),_>(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A.1" => Ok(Some(vec![])),
|
"A.1" => Ok(Some(vec![])),
|
||||||
"A.2" => Ok(Some(vec!["A.2.i", "A.2.ii"])),
|
"A.2" => Ok(Some(vec!["A.2.i", "A.2.ii"])),
|
||||||
|
@ -119,7 +128,8 @@ fn success_in_grandchildren() {
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
|
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, _| {
|
forest.process_obligations::<(),_>(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A.2.i" => Ok(Some(vec!["A.2.i.a"])),
|
"A.2.i" => Ok(Some(vec!["A.2.i.a"])),
|
||||||
"A.2.ii" => Ok(Some(vec![])),
|
"A.2.ii" => Ok(Some(vec![])),
|
||||||
|
@ -130,7 +140,8 @@ fn success_in_grandchildren() {
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
|
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, _| {
|
forest.process_obligations::<(),_>(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A.2.i.a" => Ok(Some(vec![])),
|
"A.2.i.a" => Ok(Some(vec![])),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -140,7 +151,7 @@ fn success_in_grandchildren() {
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
|
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|_, _| unreachable!());
|
forest.process_obligations::<(),_>(|_, _, _| unreachable!());
|
||||||
assert!(ok.is_empty());
|
assert!(ok.is_empty());
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
}
|
}
|
||||||
|
@ -150,9 +161,10 @@ fn to_errors_no_throw() {
|
||||||
// check that converting multiple children with common parent (A)
|
// check that converting multiple children with common parent (A)
|
||||||
// only yields one of them (and does not panic, in particular).
|
// only yields one of them (and does not panic, in particular).
|
||||||
let mut forest = ObligationForest::new();
|
let mut forest = ObligationForest::new();
|
||||||
forest.push_root("A");
|
forest.push_tree("A", "A");
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, _| {
|
forest.process_obligations::<(),_>(|obligation, tree, _| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -168,10 +180,11 @@ fn to_errors_no_throw() {
|
||||||
fn backtrace() {
|
fn backtrace() {
|
||||||
// check that converting multiple children with common parent (A)
|
// check that converting multiple children with common parent (A)
|
||||||
// only yields one of them (and does not panic, in particular).
|
// only yields one of them (and does not panic, in particular).
|
||||||
let mut forest: ObligationForest<&'static str> = ObligationForest::new();
|
let mut forest = ObligationForest::new();
|
||||||
forest.push_root("A");
|
forest.push_tree("A", "A");
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
|
forest.process_obligations::<(),_>(|obligation, tree, mut backtrace| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
assert!(backtrace.next().is_none());
|
assert!(backtrace.next().is_none());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
"A" => Ok(Some(vec!["A.1"])),
|
"A" => Ok(Some(vec!["A.1"])),
|
||||||
|
@ -181,7 +194,8 @@ fn backtrace() {
|
||||||
assert!(ok.is_empty());
|
assert!(ok.is_empty());
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
|
forest.process_obligations::<(),_>(|obligation, tree, mut backtrace| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
assert!(backtrace.next().unwrap() == &"A");
|
assert!(backtrace.next().unwrap() == &"A");
|
||||||
assert!(backtrace.next().is_none());
|
assert!(backtrace.next().is_none());
|
||||||
match *obligation {
|
match *obligation {
|
||||||
|
@ -192,7 +206,8 @@ fn backtrace() {
|
||||||
assert!(ok.is_empty());
|
assert!(ok.is_empty());
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty());
|
||||||
let Outcome { completed: ok, errors: err, .. } =
|
let Outcome { completed: ok, errors: err, .. } =
|
||||||
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
|
forest.process_obligations::<(),_>(|obligation, tree, mut backtrace| {
|
||||||
|
assert_eq!(obligation.chars().next(), tree.chars().next());
|
||||||
assert!(backtrace.next().unwrap() == &"A.1");
|
assert!(backtrace.next().unwrap() == &"A.1");
|
||||||
assert!(backtrace.next().unwrap() == &"A");
|
assert!(backtrace.next().unwrap() == &"A");
|
||||||
assert!(backtrace.next().is_none());
|
assert!(backtrace.next().is_none());
|
||||||
|
|
28
src/librustc_data_structures/obligation_forest/tree_index.rs
Normal file
28
src/librustc_data_structures/obligation_forest/tree_index.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use std::u32;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct TreeIndex {
|
||||||
|
index: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeIndex {
|
||||||
|
pub fn new(value: usize) -> TreeIndex {
|
||||||
|
assert!(value < (u32::MAX as usize));
|
||||||
|
TreeIndex { index: value as u32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self) -> usize {
|
||||||
|
self.index as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue