about summary refs log tree commit diff
path: root/src/librustc_data_structures
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-05-16 18:39:59 -0700
committerbors <bors@rust-lang.org>2016-05-16 18:39:59 -0700
commit786b26d7b48accffc638f8c2767a6e4a6f1991a5 (patch)
tree32f3c04dd6a7bc2eb5a4988764afee6392d3e0cd /src/librustc_data_structures
parentcd6a400175cc230008a5094a8bbb44a3794f0465 (diff)
parent65ad935737138eb307fdd01279ba5553a047bb6c (diff)
Auto merge of #33491 - arielb1:obligation-jungle, r=nikomatsakis
Replace the obligation forest with a graph

In the presence of caching, arbitrary nodes in the obligation forest can be merged, which makes it a general graph. Handle it as such, using cycle-detection algorithms in the processing.

I should do performance measurements sometime.

This was pretty much written as a proof-of-concept. Please help me write this in a less-ugly way. I should also add comments explaining what is going on.

r? @nikomatsakis
Diffstat (limited to 'src/librustc_data_structures')
-rw-r--r--src/librustc_data_structures/lib.rs2
-rw-r--r--src/librustc_data_structures/obligation_forest/mod.rs647
-rw-r--r--src/librustc_data_structures/obligation_forest/test.rs376
-rw-r--r--src/librustc_data_structures/obligation_forest/tree_index.rs27
4 files changed, 641 insertions, 411 deletions
diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs
index 2234325aa01..926ee85230a 100644
--- a/src/librustc_data_structures/lib.rs
+++ b/src/librustc_data_structures/lib.rs
@@ -28,6 +28,8 @@
 #![feature(nonzero)]
 #![feature(rustc_private)]
 #![feature(staged_api)]
+#![feature(unboxed_closures)]
+#![feature(fn_traits)]
 
 #![cfg_attr(test, feature(test))]
 
diff --git a/src/librustc_data_structures/obligation_forest/mod.rs b/src/librustc_data_structures/obligation_forest/mod.rs
index 4f6d0d7e405..b713b2285a6 100644
--- a/src/librustc_data_structures/obligation_forest/mod.rs
+++ b/src/librustc_data_structures/obligation_forest/mod.rs
@@ -15,20 +15,45 @@
 //! in the first place). See README.md for a general overview of how
 //! to use this class.
 
+use fnv::{FnvHashMap, FnvHashSet};
+
+use std::cell::Cell;
+use std::collections::hash_map::Entry;
 use std::fmt::Debug;
-use std::mem;
+use std::hash;
+use std::marker::PhantomData;
 
 mod node_index;
 use self::node_index::NodeIndex;
 
-mod tree_index;
-use self::tree_index::TreeIndex;
-
-
 #[cfg(test)]
 mod test;
 
-pub struct ObligationForest<O, T> {
+pub trait ForestObligation : Clone + Debug {
+    type Predicate : Clone + hash::Hash + Eq + Debug;
+
+    fn as_predicate(&self) -> &Self::Predicate;
+}
+
+pub trait ObligationProcessor {
+    type Obligation : ForestObligation;
+    type Error : Debug;
+
+    fn process_obligation(&mut self,
+                          obligation: &mut Self::Obligation)
+                          -> Result<Option<Vec<Self::Obligation>>, Self::Error>;
+
+    fn process_backedge<'c, I>(&mut self, cycle: I,
+                               _marker: PhantomData<&'c Self::Obligation>)
+        where I: Clone + Iterator<Item=&'c Self::Obligation>;
+}
+
+struct SnapshotData {
+    node_len: usize,
+    cache_list_len: usize,
+}
+
+pub struct ObligationForest<O: ForestObligation> {
     /// The list of obligations. In between calls to
     /// `process_obligations`, this list only contains nodes in the
     /// `Pending` or `Success` state (with a non-zero number of
@@ -42,51 +67,66 @@ pub struct ObligationForest<O, T> {
     /// at a higher index than its parent. This is needed by the
     /// backtrace iterator (which uses `split_at`).
     nodes: Vec<Node<O>>,
-    trees: Vec<Tree<T>>,
-    snapshots: Vec<usize>,
+    /// A cache of predicates that have been successfully completed.
+    done_cache: FnvHashSet<O::Predicate>,
+    /// An cache of the nodes in `nodes`, indexed by predicate.
+    waiting_cache: FnvHashMap<O::Predicate, NodeIndex>,
+    /// A list of the obligations added in snapshots, to allow
+    /// for their removal.
+    cache_list: Vec<O::Predicate>,
+    snapshots: Vec<SnapshotData>,
+    scratch: Option<Vec<usize>>,
 }
 
 pub struct Snapshot {
     len: usize,
 }
 
-struct Tree<T> {
-    root: NodeIndex,
-    state: T,
-}
-
+#[derive(Debug)]
 struct Node<O> {
-    state: NodeState<O>,
+    obligation: O,
+    state: Cell<NodeState>,
+
+    /// Obligations that depend on this obligation for their
+    /// completion. They must all be in a non-pending state.
+    dependents: Vec<NodeIndex>,
+    /// The parent of a node - the original obligation of
+    /// which it is a subobligation. Except for error reporting,
+    /// this is just another member of `dependents`.
     parent: Option<NodeIndex>,
-    tree: TreeIndex,
 }
 
 /// The state of one node in some tree within the forest. This
 /// represents the current state of processing for the obligation (of
 /// type `O`) associated with this node.
-#[derive(Debug)]
-enum NodeState<O> {
-    /// Obligation not yet resolved to success or error.
-    Pending {
-        obligation: O,
-    },
-
-    /// Obligation resolved to success; `num_incomplete_children`
-    /// indicates the number of children still in an "incomplete"
-    /// state. Incomplete means that either the child is still
-    /// pending, or it has children which are incomplete. (Basically,
-    /// there is pending work somewhere in the subtree of the child.)
-    ///
-    /// Once all children have completed, success nodes are removed
-    /// from the vector by the compression step.
-    Success {
-        obligation: O,
-        num_incomplete_children: usize,
-    },
+///
+/// Outside of ObligationForest methods, nodes should be either Pending
+/// or Waiting.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum NodeState {
+    /// Obligations for which selection had not yet returned a
+    /// non-ambiguous result.
+    Pending,
+
+    /// This obligation was selected successfuly, but may or
+    /// may not have subobligations.
+    Success,
+
+    /// This obligation was selected sucessfully, but it has
+    /// a pending subobligation.
+    Waiting,
+
+    /// This obligation, along with its subobligations, are complete,
+    /// and will be removed in the next collection.
+    Done,
 
     /// This obligation was resolved to an error. Error nodes are
     /// removed from the vector by the compression step.
     Error,
+
+    /// This is a temporary state used in DFS loops to detect cycles,
+    /// it should not exist outside of these DFSes.
+    OnDfsStack,
 }
 
 #[derive(Debug)]
@@ -113,12 +153,15 @@ pub struct Error<O, E> {
     pub backtrace: Vec<O>,
 }
 
-impl<O: Debug, T: Debug> ObligationForest<O, T> {
-    pub fn new() -> ObligationForest<O, T> {
+impl<O: ForestObligation> ObligationForest<O> {
+    pub fn new() -> ObligationForest<O> {
         ObligationForest {
-            trees: vec![],
             nodes: vec![],
             snapshots: vec![],
+            done_cache: FnvHashSet(),
+            waiting_cache: FnvHashMap(),
+            cache_list: vec![],
+            scratch: Some(vec![]),
         }
     }
 
@@ -129,57 +172,69 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
     }
 
     pub fn start_snapshot(&mut self) -> Snapshot {
-        self.snapshots.push(self.trees.len());
+        self.snapshots.push(SnapshotData {
+            node_len: self.nodes.len(),
+            cache_list_len: self.cache_list.len()
+        });
         Snapshot { len: self.snapshots.len() }
     }
 
     pub fn commit_snapshot(&mut self, snapshot: Snapshot) {
         assert_eq!(snapshot.len, self.snapshots.len());
-        let trees_len = self.snapshots.pop().unwrap();
-        assert!(self.trees.len() >= trees_len);
+        let info = self.snapshots.pop().unwrap();
+        assert!(self.nodes.len() >= info.node_len);
+        assert!(self.cache_list.len() >= info.cache_list_len);
     }
 
     pub fn rollback_snapshot(&mut self, snapshot: Snapshot) {
         // Check that we are obeying stack discipline.
         assert_eq!(snapshot.len, self.snapshots.len());
-        let trees_len = self.snapshots.pop().unwrap();
+        let info = self.snapshots.pop().unwrap();
 
-        // If nothing happened in snapshot, done.
-        if self.trees.len() == trees_len {
-            return;
+        for entry in &self.cache_list[info.cache_list_len..] {
+            self.done_cache.remove(entry);
+            self.waiting_cache.remove(entry);
         }
 
-        // 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);
+        self.nodes.truncate(info.node_len);
+        self.cache_list.truncate(info.cache_list_len);
     }
 
     pub fn in_snapshot(&self) -> bool {
         !self.snapshots.is_empty()
     }
 
-    /// Adds a new tree to the forest.
+    /// Registers an obligation
     ///
-    /// This CAN be done during a snapshot.
-    pub fn push_tree(&mut self, obligation: O, tree_state: T) {
-        let index = NodeIndex::new(self.nodes.len());
-        let tree = TreeIndex::new(self.trees.len());
-        self.trees.push(Tree {
-            root: index,
-            state: tree_state,
-        });
-        self.nodes.push(Node::new(tree, None, obligation));
+    /// This CAN be done in a snapshot
+    pub fn register_obligation(&mut self, obligation: O) {
+        self.register_obligation_at(obligation, None)
+    }
+
+    fn register_obligation_at(&mut self, obligation: O, parent: Option<NodeIndex>) {
+        if self.done_cache.contains(obligation.as_predicate()) { return }
+
+        match self.waiting_cache.entry(obligation.as_predicate().clone()) {
+            Entry::Occupied(o) => {
+                debug!("register_obligation_at({:?}, {:?}) - duplicate of {:?}!",
+                       obligation, parent, o.get());
+                if let Some(parent) = parent {
+                    if self.nodes[o.get().get()].dependents.contains(&parent) {
+                        debug!("register_obligation_at({:?}, {:?}) - duplicate subobligation",
+                               obligation, parent);
+                    } else {
+                        self.nodes[o.get().get()].dependents.push(parent);
+                    }
+                }
+            }
+            Entry::Vacant(v) => {
+                debug!("register_obligation_at({:?}, {:?}) - ok",
+                       obligation, parent);
+                v.insert(NodeIndex::new(self.nodes.len()));
+                self.cache_list.push(obligation.as_predicate().clone());
+                self.nodes.push(Node::new(parent, obligation));
+            }
+        };
     }
 
     /// Convert all remaining obligations to the given error.
@@ -189,10 +244,8 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
         assert!(!self.in_snapshot());
         let mut errors = vec![];
         for index in 0..self.nodes.len() {
-            debug_assert!(!self.nodes[index].is_popped());
-            self.inherit_error(index);
-            if let NodeState::Pending { .. } = self.nodes[index].state {
-                let backtrace = self.backtrace(index);
+            if let NodeState::Pending = self.nodes[index].state.get() {
+                let backtrace = self.error_at(index);
                 errors.push(Error {
                     error: error.clone(),
                     backtrace: backtrace,
@@ -210,22 +263,17 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
     {
         self.nodes
             .iter()
-            .filter_map(|n| {
-                match n.state {
-                    NodeState::Pending { ref obligation } => Some(obligation),
-                    _ => None,
-                }
-            })
-            .cloned()
+            .filter(|n| n.state.get() == NodeState::Pending)
+            .map(|n| n.obligation.clone())
             .collect()
     }
 
-    /// Process the obligations.
+    /// Perform 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).
-    pub fn process_obligations<E, F>(&mut self, mut action: F) -> Outcome<O, E>
-        where E: Debug,
-              F: FnMut(&mut O, &mut T, Backtrace<O>) -> Result<Option<Vec<O>>, E>
+    pub fn process_obligations<P>(&mut self, processor: &mut P) -> Outcome<O, P::Error>
+        where P: ObligationProcessor<Obligation=O>
     {
         debug!("process_obligations(len={})", self.nodes.len());
         assert!(!self.in_snapshot()); // cannot unroll this action
@@ -233,33 +281,18 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
         let mut errors = vec![];
         let mut stalled = true;
 
-        // We maintain the invariant that the list is in pre-order, so
-        // parents occur before their children. Also, whenever an
-        // error occurs, we propagate it from the child all the way to
-        // the root of the tree. Together, these two facts mean that
-        // when we visit a node, we can check if its root is in error,
-        // and we will find out if any prior node within this forest
-        // encountered an error.
-
         for index in 0..self.nodes.len() {
-            debug_assert!(!self.nodes[index].is_popped());
-            self.inherit_error(index);
-
             debug!("process_obligations: node {} == {:?}",
                    index,
-                   self.nodes[index].state);
-
-            let result = {
-                let Node { tree, parent, .. } = self.nodes[index];
-                let (prefix, suffix) = self.nodes.split_at_mut(index);
-                let backtrace = Backtrace::new(prefix, parent);
-                match suffix[0].state {
-                    NodeState::Error |
-                    NodeState::Success { .. } => continue,
-                    NodeState::Pending { ref mut obligation } => {
-                        action(obligation, &mut self.trees[tree.get()].state, backtrace)
-                    }
+                   self.nodes[index]);
+
+            let result = match self.nodes[index] {
+                Node { state: ref _state, ref mut obligation, .. }
+                    if _state.get() == NodeState::Pending =>
+                {
+                    processor.process_obligation(obligation)
                 }
+                _ => continue
             };
 
             debug!("process_obligations: node {} got result {:?}",
@@ -273,10 +306,15 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
                 Ok(Some(children)) => {
                     // if we saw a Some(_) result, we are not (yet) stalled
                     stalled = false;
-                    self.success(index, children);
+                    for child in children {
+                        self.register_obligation_at(child,
+                                                    Some(NodeIndex::new(index)));
+                    }
+
+                    self.nodes[index].state.set(NodeState::Success);
                 }
                 Err(err) => {
-                    let backtrace = self.backtrace(index);
+                    let backtrace = self.error_at(index);
                     errors.push(Error {
                         error: err,
                         backtrace: backtrace,
@@ -285,259 +323,292 @@ impl<O: Debug, T: Debug> ObligationForest<O, T> {
             }
         }
 
+        self.mark_as_waiting();
+        self.process_cycles(processor);
+
         // Now we have to compress the result
-        let successful_obligations = self.compress();
+        let completed_obligations = self.compress();
 
         debug!("process_obligations: complete");
 
         Outcome {
-            completed: successful_obligations,
+            completed: completed_obligations,
             errors: errors,
             stalled: stalled,
         }
     }
 
-    /// Indicates that node `index` has been processed successfully,
-    /// yielding `children` as the derivative work. If children is an
-    /// empty vector, this will update the ref count on the parent of
-    /// `index` to indicate that a child has completed
-    /// successfully. Otherwise, adds new nodes to represent the child
-    /// work.
-    fn success(&mut self, index: usize, children: Vec<O>) {
-        debug!("success(index={}, children={:?})", index, children);
-
-        let num_incomplete_children = children.len();
-
-        if num_incomplete_children == 0 {
-            // if there is no work left to be done, decrement parent's ref count
-            self.update_parent(index);
-        } else {
-            // create child work
-            let tree_index = self.nodes[index].tree;
-            let node_index = NodeIndex::new(index);
-            self.nodes.extend(children.into_iter()
-                                      .map(|o| Node::new(tree_index, Some(node_index), o)));
+    /// Mark all NodeState::Success nodes as NodeState::Done and
+    /// report all cycles between them. This should be called
+    /// after `mark_as_waiting` marks all nodes with pending
+    /// subobligations as NodeState::Waiting.
+    fn process_cycles<P>(&mut self, processor: &mut P)
+        where P: ObligationProcessor<Obligation=O>
+    {
+        let mut stack = self.scratch.take().unwrap();
+
+        for node in 0..self.nodes.len() {
+            self.find_cycles_from_node(&mut stack, processor, node);
         }
 
-        // change state from `Pending` to `Success`, temporarily swapping in `Error`
-        let state = mem::replace(&mut self.nodes[index].state, NodeState::Error);
-        self.nodes[index].state = match state {
-            NodeState::Pending { obligation } => {
-                NodeState::Success {
-                    obligation: obligation,
-                    num_incomplete_children: num_incomplete_children,
-                }
-            }
-            NodeState::Success { .. } |
-            NodeState::Error => unreachable!(),
-        };
+        self.scratch = Some(stack);
     }
 
-    /// Decrements the ref count on the parent of `child`; if the
-    /// parent's ref count then reaches zero, proceeds recursively.
-    fn update_parent(&mut self, child: usize) {
-        debug!("update_parent(child={})", child);
-        if let Some(parent) = self.nodes[child].parent {
-            let parent = parent.get();
-            match self.nodes[parent].state {
-                NodeState::Success { ref mut num_incomplete_children, .. } => {
-                    *num_incomplete_children -= 1;
-                    if *num_incomplete_children > 0 {
-                        return;
+    fn find_cycles_from_node<P>(&self, stack: &mut Vec<usize>,
+                                processor: &mut P, index: usize)
+        where P: ObligationProcessor<Obligation=O>
+    {
+        let node = &self.nodes[index];
+        let state = node.state.get();
+        match state {
+            NodeState::OnDfsStack => {
+                let index =
+                    stack.iter().rposition(|n| *n == index).unwrap();
+                // I need a Clone closure
+                #[derive(Clone)]
+                struct GetObligation<'a, O: 'a>(&'a [Node<O>]);
+                impl<'a, 'b, O> FnOnce<(&'b usize,)> for GetObligation<'a, O> {
+                    type Output = &'a O;
+                    extern "rust-call" fn call_once(self, args: (&'b usize,)) -> &'a O {
+                        &self.0[*args.0].obligation
+                    }
+                }
+                impl<'a, 'b, O> FnMut<(&'b usize,)> for GetObligation<'a, O> {
+                    extern "rust-call" fn call_mut(&mut self, args: (&'b usize,)) -> &'a O {
+                        &self.0[*args.0].obligation
                     }
                 }
-                _ => unreachable!(),
-            }
-            self.update_parent(parent);
-        }
-    }
 
-    /// If the root of `child` is in an error state, places `child`
-    /// into an error state. This is used during processing so that we
-    /// skip the remaining obligations from a tree once some other
-    /// node in the tree is found to be in error.
-    fn inherit_error(&mut self, child: usize) {
-        let tree = self.nodes[child].tree;
-        let root = self.trees[tree.get()].root;
-        if let NodeState::Error = self.nodes[root.get()].state {
-            self.nodes[child].state = NodeState::Error;
-        }
+                processor.process_backedge(stack[index..].iter().map(GetObligation(&self.nodes)),
+                                           PhantomData);
+            }
+            NodeState::Success => {
+                node.state.set(NodeState::OnDfsStack);
+                stack.push(index);
+                if let Some(parent) = node.parent {
+                    self.find_cycles_from_node(stack, processor, parent.get());
+                }
+                for dependent in &node.dependents {
+                    self.find_cycles_from_node(stack, processor, dependent.get());
+                }
+                stack.pop();
+                node.state.set(NodeState::Done);
+            },
+            NodeState::Waiting | NodeState::Pending => {
+                // this node is still reachable from some pending node. We
+                // will get to it when they are all processed.
+            }
+            NodeState::Done | NodeState::Error => {
+                // already processed that node
+            }
+        };
     }
 
     /// Returns a vector of obligations for `p` and all of its
     /// ancestors, putting them into the error state in the process.
-    /// The fact that the root is now marked as an error is used by
-    /// `inherit_error` above to propagate the error state to the
-    /// remainder of the tree.
-    fn backtrace(&mut self, mut p: usize) -> Vec<O> {
+    fn error_at(&mut self, p: usize) -> Vec<O> {
+        let mut error_stack = self.scratch.take().unwrap();
         let mut trace = vec![];
+
+        let mut n = p;
         loop {
-            let state = mem::replace(&mut self.nodes[p].state, NodeState::Error);
-            match state {
-                NodeState::Pending { obligation } |
-                NodeState::Success { obligation, .. } => {
-                    trace.push(obligation);
-                }
-                NodeState::Error => {
-                    // we should not encounter an error, because if
-                    // there was an error in the ancestors, it should
-                    // have been propagated down and we should never
-                    // have tried to process this obligation
-                    panic!("encountered error in node {:?} when collecting stack trace",
-                           p);
-                }
-            }
+            self.nodes[n].state.set(NodeState::Error);
+            trace.push(self.nodes[n].obligation.clone());
+            error_stack.extend(self.nodes[n].dependents.iter().map(|x| x.get()));
 
             // loop to the parent
-            match self.nodes[p].parent {
-                Some(q) => {
-                    p = q.get();
-                }
-                None => {
-                    return trace;
-                }
+            match self.nodes[n].parent {
+                Some(q) => n = q.get(),
+                None => break
+            }
+        }
+
+        loop {
+            // non-standard `while let` to bypass #6393
+            let i = match error_stack.pop() {
+                Some(i) => i,
+                None => break
+            };
+
+            let node = &self.nodes[i];
+
+            match node.state.get() {
+                NodeState::Error => continue,
+                _ => node.state.set(NodeState::Error)
+            }
+
+            error_stack.extend(
+                node.dependents.iter().cloned().chain(node.parent).map(|x| x.get())
+            );
+        }
+
+        self.scratch = Some(error_stack);
+        trace
+    }
+
+    /// Marks all nodes that depend on a pending node as NodeState;:Waiting.
+    fn mark_as_waiting(&self) {
+        for node in &self.nodes {
+            if node.state.get() == NodeState::Waiting {
+                node.state.set(NodeState::Success);
+            }
+        }
+
+        for node in &self.nodes {
+            if node.state.get() == NodeState::Pending {
+                self.mark_as_waiting_from(node)
             }
         }
     }
 
+    fn mark_as_waiting_from(&self, node: &Node<O>) {
+        match node.state.get() {
+            NodeState::Pending | NodeState::Done => {},
+            NodeState::Waiting | NodeState::Error | NodeState::OnDfsStack => return,
+            NodeState::Success => {
+                node.state.set(NodeState::Waiting);
+            }
+        }
+
+        if let Some(parent) = node.parent {
+            self.mark_as_waiting_from(&self.nodes[parent.get()]);
+        }
+
+        for dependent in &node.dependents {
+            self.mark_as_waiting_from(&self.nodes[dependent.get()]);
+        }
+    }
+
     /// Compresses the vector, removing all popped nodes. This adjusts
     /// the indices and hence invalidates any outstanding
     /// indices. Cannot be used during a transaction.
+    ///
+    /// Beforehand, all nodes must be marked as `Done` and no cycles
+    /// on these nodes may be present. This is done by e.g. `process_cycles`.
+    #[inline(never)]
     fn compress(&mut self) -> Vec<O> {
         assert!(!self.in_snapshot()); // didn't write code to unroll this action
-        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
-        // only have to check immediate parents, rather than all
-        // ancestors, because all errors have already occurred that
-        // are going to occur.
         let nodes_len = self.nodes.len();
-        for i in 0..nodes_len {
-            if !self.nodes[i].is_popped() {
-                self.inherit_error(i);
-            }
-        }
+        let mut node_rewrites: Vec<_> = self.scratch.take().unwrap();
+        node_rewrites.extend(0..nodes_len);
+        let mut dead_nodes = 0;
 
-        // 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 move all popped nodes to the end. Try to keep the order.
+        //
+        // LOOP INVARIANT:
+        //     self.nodes[0..i - dead_nodes] are the first remaining nodes
+        //     self.nodes[i - dead_nodes..i] are all dead
+        //     self.nodes[i..] are unchanged
+        for i in 0..self.nodes.len() {
+            match self.nodes[i].state.get() {
+                NodeState::Done => {
+                    self.waiting_cache.remove(self.nodes[i].obligation.as_predicate());
+                    // FIXME(HashMap): why can't I get my key back?
+                    self.done_cache.insert(self.nodes[i].obligation.as_predicate().clone());
+                }
+                NodeState::Error => {
+                    // We *intentionally* remove the node from the cache at this point. Otherwise
+                    // tests must come up with a different type on every type error they
+                    // check against.
+                    self.waiting_cache.remove(self.nodes[i].obligation.as_predicate());
+                }
+                _ => {}
             }
-        }
 
-        // Now go through and move all nodes that are either
-        // successful or which have an error over into to the end of
-        // the list, preserving the relative order of the survivors
-        // (which is important for the `inherit_error` logic).
-        let mut dead_nodes = 0;
-        for i in 0..nodes_len {
             if self.nodes[i].is_popped() {
+                node_rewrites[i] = nodes_len;
                 dead_nodes += 1;
-            } else if dead_nodes > 0 {
-                self.nodes.swap(i, i - dead_nodes);
-                node_rewrites[i] -= dead_nodes;
+            } else {
+                if dead_nodes > 0 {
+                    self.nodes.swap(i, i - dead_nodes);
+                    node_rewrites[i] -= dead_nodes;
+                }
             }
         }
 
         // No compression needed.
-        if dead_nodes == 0 && dead_trees == 0 {
+        if dead_nodes == 0 {
+            node_rewrites.truncate(0);
+            self.scratch = Some(node_rewrites);
             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
         // stories.
         let successful = (0..dead_nodes)
                              .map(|_| self.nodes.pop().unwrap())
                              .flat_map(|node| {
-                                 match node.state {
+                                 match node.state.get() {
                                      NodeState::Error => None,
-                                     NodeState::Pending { .. } => unreachable!(),
-                                     NodeState::Success { obligation, num_incomplete_children } => {
-                                         assert_eq!(num_incomplete_children, 0);
-                                         Some(obligation)
-                                     }
+                                     NodeState::Done => Some(node.obligation),
+                                     _ => unreachable!()
                                  }
                              })
-                             .collect();
+            .collect();
+        self.apply_rewrites(&node_rewrites);
+
+        node_rewrites.truncate(0);
+        self.scratch = Some(node_rewrites);
+
+        successful
+    }
+
+    fn apply_rewrites(&mut self, node_rewrites: &[usize]) {
+        let nodes_len = node_rewrites.len();
 
-        // 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 {
-            if let Some(ref mut index) = node.parent {
+            if let Some(index) = node.parent {
                 let new_index = node_rewrites[index.get()];
-                debug_assert!(new_index < (nodes_len - dead_nodes));
-                *index = NodeIndex::new(new_index);
+                if new_index >= nodes_len {
+                    // parent dead due to error
+                    node.parent = None;
+                } else {
+                    node.parent = Some(NodeIndex::new(new_index));
+                }
             }
 
-            node.tree = TreeIndex::new(tree_rewrites[node.tree.get()]);
+            let mut i = 0;
+            while i < node.dependents.len() {
+                let new_index = node_rewrites[node.dependents[i].get()];
+                if new_index >= nodes_len {
+                    node.dependents.swap_remove(i);
+                } else {
+                    node.dependents[i] = NodeIndex::new(new_index);
+                    i += 1;
+                }
+            }
         }
 
-        successful
+        let mut kill_list = vec![];
+        for (predicate, index) in self.waiting_cache.iter_mut() {
+            let new_index = node_rewrites[index.get()];
+            if new_index >= nodes_len {
+                kill_list.push(predicate.clone());
+            } else {
+                *index = NodeIndex::new(new_index);
+            }
+        }
+
+        for predicate in kill_list { self.waiting_cache.remove(&predicate); }
     }
 }
 
 impl<O> Node<O> {
-    fn new(tree: TreeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
+    fn new(parent: Option<NodeIndex>, obligation: O) -> Node<O> {
         Node {
+            obligation: obligation,
             parent: parent,
-            state: NodeState::Pending { obligation: obligation },
-            tree: tree,
+            state: Cell::new(NodeState::Pending),
+            dependents: vec![],
         }
     }
 
     fn is_popped(&self) -> bool {
-        match self.state {
-            NodeState::Pending { .. } => false,
-            NodeState::Success { num_incomplete_children, .. } => num_incomplete_children == 0,
-            NodeState::Error => true,
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct Backtrace<'b, O: 'b> {
-    nodes: &'b [Node<O>],
-    pointer: Option<NodeIndex>,
-}
-
-impl<'b, O> Backtrace<'b, O> {
-    fn new(nodes: &'b [Node<O>], pointer: Option<NodeIndex>) -> Backtrace<'b, O> {
-        Backtrace {
-            nodes: nodes,
-            pointer: pointer,
-        }
-    }
-}
-
-impl<'b, O> Iterator for Backtrace<'b, O> {
-    type Item = &'b O;
-
-    fn next(&mut self) -> Option<&'b O> {
-        debug!("Backtrace: self.pointer = {:?}", self.pointer);
-        if let Some(p) = self.pointer {
-            self.pointer = self.nodes[p.get()].parent;
-            match self.nodes[p.get()].state {
-                NodeState::Pending { ref obligation } |
-                NodeState::Success { ref obligation, .. } => Some(obligation),
-                NodeState::Error => {
-                    panic!("Backtrace encountered an error.");
-                }
-            }
-        } else {
-            None
+        match self.state.get() {
+            NodeState::Pending | NodeState::Waiting => false,
+            NodeState::Error | NodeState::Done => true,
+            NodeState::OnDfsStack | NodeState::Success => unreachable!()
         }
     }
 }
diff --git a/src/librustc_data_structures/obligation_forest/test.rs b/src/librustc_data_structures/obligation_forest/test.rs
index a8c24270217..8eac8892a3e 100644
--- a/src/librustc_data_structures/obligation_forest/test.rs
+++ b/src/librustc_data_structures/obligation_forest/test.rs
@@ -8,30 +8,82 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use super::{ObligationForest, Outcome, Error};
+#![cfg(test)]
+
+use super::{ObligationForest, ObligationProcessor, Outcome, Error};
+
+use std::fmt;
+use std::marker::PhantomData;
+
+impl<'a> super::ForestObligation for &'a str {
+    type Predicate = &'a str;
+
+    fn as_predicate(&self) -> &Self::Predicate {
+        self
+    }
+}
+
+struct ClosureObligationProcessor<OF, BF, O, E> {
+    process_obligation: OF,
+    _process_backedge: BF,
+    marker: PhantomData<(O, E)>,
+}
+
+#[allow(non_snake_case)]
+fn C<OF, BF, O>(of: OF, bf: BF) -> ClosureObligationProcessor<OF, BF, O, &'static str>
+    where OF: FnMut(&mut O) -> Result<Option<Vec<O>>, &'static str>,
+          BF: FnMut(&[O])
+{
+    ClosureObligationProcessor {
+        process_obligation: of,
+        _process_backedge: bf,
+        marker: PhantomData
+    }
+}
+
+impl<OF, BF, O, E> ObligationProcessor for ClosureObligationProcessor<OF, BF, O, E>
+    where O: super::ForestObligation + fmt::Debug,
+          E: fmt::Debug,
+          OF: FnMut(&mut O) -> Result<Option<Vec<O>>, E>,
+          BF: FnMut(&[O])
+{
+    type Obligation = O;
+    type Error = E;
+
+    fn process_obligation(&mut self,
+                          obligation: &mut Self::Obligation)
+                          -> Result<Option<Vec<Self::Obligation>>, Self::Error>
+    {
+        (self.process_obligation)(obligation)
+    }
+
+    fn process_backedge<'c, I>(&mut self, _cycle: I,
+                               _marker: PhantomData<&'c Self::Obligation>)
+        where I: Clone + Iterator<Item=&'c Self::Obligation> {
+        }
+}
+
 
 #[test]
 fn push_pop() {
     let mut forest = ObligationForest::new();
-    forest.push_tree("A", "A");
-    forest.push_tree("B", "B");
-    forest.push_tree("C", "C");
+    forest.register_obligation("A");
+    forest.register_obligation("B");
+    forest.register_obligation("C");
 
     // first round, B errors out, A has subtasks, and C completes, creating this:
     //      A |-> A.1
     //        |-> A.2
     //        |-> A.3
-    let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation,
-                                                                                 tree,
-                                                                                 _| {
-        assert_eq!(obligation.chars().next(), tree.chars().next());
-        match *obligation {
-            "A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
-            "B" => Err("B is for broken"),
-            "C" => Ok(Some(vec![])),
-            _ => unreachable!(),
-        }
-    });
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
+                "B" => Err("B is for broken"),
+                "C" => Ok(Some(vec![])),
+                _ => unreachable!(),
+            }
+        }, |_| {}));
     assert_eq!(ok, vec!["C"]);
     assert_eq!(err,
                vec![Error {
@@ -45,10 +97,9 @@ fn push_pop() {
     //        |-> A.3 |-> A.3.i
     //      D |-> D.1
     //        |-> D.2
-    forest.push_tree("D", "D");
-    let Outcome { completed: ok, errors: err, .. }: Outcome<&'static str, ()> =
-        forest.process_obligations(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+    forest.register_obligation("D");
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A.1" => Ok(None),
                 "A.2" => Ok(None),
@@ -56,45 +107,43 @@ fn push_pop() {
                 "D" => Ok(Some(vec!["D.1", "D.2"])),
                 _ => unreachable!(),
             }
-        });
+        }, |_| {}));
     assert_eq!(ok, Vec::<&'static str>::new());
     assert_eq!(err, Vec::new());
 
 
     // third round: ok in A.1 but trigger an error in A.2. Check that it
-    // propagates to A.3.i, but not D.1 or D.2.
+    // propagates to A, but not D.1 or D.2.
     //      D |-> D.1 |-> D.1.i
     //        |-> D.2 |-> D.2.i
-    let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation,
-                                                                                 tree,
-                                                                                 _| {
-        assert_eq!(obligation.chars().next(), tree.chars().next());
-        match *obligation {
-            "A.1" => Ok(Some(vec![])),
-            "A.2" => Err("A is for apple"),
-            "D.1" => Ok(Some(vec!["D.1.i"])),
-            "D.2" => Ok(Some(vec!["D.2.i"])),
-            _ => unreachable!(),
-        }
-    });
-    assert_eq!(ok, vec!["A.1"]);
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A.1" => Ok(Some(vec![])),
+                "A.2" => Err("A is for apple"),
+                "A.3.i" => Ok(Some(vec![])),
+                "D.1" => Ok(Some(vec!["D.1.i"])),
+                "D.2" => Ok(Some(vec!["D.2.i"])),
+                _ => unreachable!(),
+            }
+        }, |_| {}));
+    assert_eq!(ok, vec!["A.3", "A.1", "A.3.i"]);
     assert_eq!(err,
                vec![Error {
                         error: "A is for apple",
                         backtrace: vec!["A.2", "A"],
                     }]);
 
-    // fourth round: error in D.1.i that should propagate to D.2.i
-    let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation,
-                                                                                 tree,
-                                                                                 _| {
-        assert_eq!(obligation.chars().next(), tree.chars().next());
-        match *obligation {
-            "D.1.i" => Err("D is for dumb"),
-            _ => panic!("unexpected obligation {:?}", obligation),
-        }
-    });
-    assert_eq!(ok, Vec::<&'static str>::new());
+    // fourth round: error in D.1.i
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "D.1.i" => Err("D is for dumb"),
+                "D.2.i" => Ok(Some(vec![])),
+                _ => panic!("unexpected obligation {:?}", obligation),
+            }
+        }, |_| {}));
+    assert_eq!(ok, vec!["D.2.i", "D.2"]);
     assert_eq!(err,
                vec![Error {
                         error: "D is for dumb",
@@ -113,60 +162,54 @@ fn push_pop() {
 #[test]
 fn success_in_grandchildren() {
     let mut forest = ObligationForest::new();
-    forest.push_tree("A", "A");
+    forest.register_obligation("A");
 
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
                 _ => unreachable!(),
             }
-        });
+        }, |_| {}));
     assert!(ok.is_empty());
     assert!(err.is_empty());
 
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A.1" => Ok(Some(vec![])),
                 "A.2" => Ok(Some(vec!["A.2.i", "A.2.ii"])),
                 "A.3" => Ok(Some(vec![])),
                 _ => unreachable!(),
             }
-        });
+        }, |_| {}));
     assert_eq!(ok, vec!["A.3", "A.1"]);
     assert!(err.is_empty());
 
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A.2.i" => Ok(Some(vec!["A.2.i.a"])),
                 "A.2.ii" => Ok(Some(vec![])),
                 _ => unreachable!(),
             }
-        });
+        }, |_| {}));
     assert_eq!(ok, vec!["A.2.ii"]);
     assert!(err.is_empty());
 
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A.2.i.a" => Ok(Some(vec![])),
                 _ => unreachable!(),
             }
-        });
+        }, |_| {}));
     assert_eq!(ok, vec!["A.2.i.a", "A.2.i", "A.2", "A"]);
     assert!(err.is_empty());
 
-    let Outcome { completed: ok, errors: err, .. } = forest.process_obligations::<(), _>(|_,
-                                                                                          _,
-                                                                                          _| {
-        unreachable!()
-    });
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|_| unreachable!(), |_| {}));
+
     assert!(ok.is_empty());
     assert!(err.is_empty());
 }
@@ -174,63 +217,204 @@ fn success_in_grandchildren() {
 #[test]
 fn to_errors_no_throw() {
     // check that converting multiple children with common parent (A)
-    // only yields one of them (and does not panic, in particular).
+    // yields to correct errors (and does not panic, in particular).
     let mut forest = ObligationForest::new();
-    forest.push_tree("A", "A");
+    forest.register_obligation("A");
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, _| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
                 "A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
                 _ => unreachable!(),
             }
-        });
+        }, |_|{}));
     assert_eq!(ok.len(), 0);
     assert_eq!(err.len(), 0);
     let errors = forest.to_errors(());
-    assert_eq!(errors.len(), 1);
+    assert_eq!(errors[0].backtrace, vec!["A.1", "A"]);
+    assert_eq!(errors[1].backtrace, vec!["A.2", "A"]);
+    assert_eq!(errors[2].backtrace, vec!["A.3", "A"]);
+    assert_eq!(errors.len(), 3);
 }
 
 #[test]
-fn backtrace() {
-    // check that converting multiple children with common parent (A)
-    // only yields one of them (and does not panic, in particular).
+fn diamond() {
+    // check that diamond dependencies are handled correctly
     let mut forest = ObligationForest::new();
-    forest.push_tree("A", "A");
+    forest.register_obligation("A");
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, mut backtrace| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
-            assert!(backtrace.next().is_none());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
-                "A" => Ok(Some(vec!["A.1"])),
+                "A" => Ok(Some(vec!["A.1", "A.2"])),
                 _ => unreachable!(),
             }
-        });
-    assert!(ok.is_empty());
-    assert!(err.is_empty());
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err.len(), 0);
+
     let Outcome { completed: ok, errors: err, .. } =
-        forest.process_obligations::<(), _>(|obligation, tree, mut backtrace| {
-            assert_eq!(obligation.chars().next(), tree.chars().next());
-            assert!(backtrace.next().unwrap() == &"A");
-            assert!(backtrace.next().is_none());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
-                "A.1" => Ok(Some(vec!["A.1.i"])),
+                "A.1" => Ok(Some(vec!["D"])),
+                "A.2" => Ok(Some(vec!["D"])),
                 _ => unreachable!(),
             }
-        });
-    assert!(ok.is_empty());
-    assert!(err.is_empty());
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err.len(), 0);
+
+    let mut d_count = 0;
     let Outcome { completed: ok, errors: err, .. } =
-        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");
-            assert!(backtrace.next().is_none());
+        forest.process_obligations(&mut C(|obligation| {
             match *obligation {
-                "A.1.i" => Ok(None),
+                "D" => { d_count += 1; Ok(Some(vec![])) },
                 _ => unreachable!(),
             }
-        });
+        }, |_|{}));
+    assert_eq!(d_count, 1);
+    assert_eq!(ok, vec!["D", "A.2", "A.1", "A"]);
+    assert_eq!(err.len(), 0);
+
+    let errors = forest.to_errors(());
+    assert_eq!(errors.len(), 0);
+
+    forest.register_obligation("A'");
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A'" => Ok(Some(vec!["A'.1", "A'.2"])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
     assert_eq!(ok.len(), 0);
-    assert!(err.is_empty());
+    assert_eq!(err.len(), 0);
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A'.1" => Ok(Some(vec!["D'", "A'"])),
+                "A'.2" => Ok(Some(vec!["D'"])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err.len(), 0);
+
+    let mut d_count = 0;
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "D'" => { d_count += 1; Err("operation failed") },
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(d_count, 1);
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err, vec![super::Error {
+        error: "operation failed",
+        backtrace: vec!["D'", "A'.1", "A'"]
+    }]);
+
+    let errors = forest.to_errors(());
+    assert_eq!(errors.len(), 0);
+}
+
+#[test]
+fn done_dependency() {
+    // check that the local cache works
+    let mut forest = ObligationForest::new();
+    forest.register_obligation("A: Sized");
+    forest.register_obligation("B: Sized");
+    forest.register_obligation("C: Sized");
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A: Sized" | "B: Sized" | "C: Sized" => Ok(Some(vec![])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok, vec!["C: Sized", "B: Sized", "A: Sized"]);
+    assert_eq!(err.len(), 0);
+
+    forest.register_obligation("(A,B,C): Sized");
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "(A,B,C): Sized" => Ok(Some(vec![
+                    "A: Sized",
+                    "B: Sized",
+                    "C: Sized"
+                        ])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok, vec!["(A,B,C): Sized"]);
+    assert_eq!(err.len(), 0);
+
+
+}
+
+
+#[test]
+fn orphan() {
+    // check that orphaned nodes are handled correctly
+    let mut forest = ObligationForest::new();
+    forest.register_obligation("A");
+    forest.register_obligation("B");
+    forest.register_obligation("C1");
+    forest.register_obligation("C2");
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "A" => Ok(Some(vec!["D", "E"])),
+                "B" => Ok(None),
+                "C1" => Ok(Some(vec![])),
+                "C2" => Ok(Some(vec![])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok, vec!["C2", "C1"]);
+    assert_eq!(err.len(), 0);
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "D" | "E" => Ok(None),
+                "B" => Ok(Some(vec!["D"])),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err.len(), 0);
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "D" => Ok(None),
+                "E" => Err("E is for error"),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err, vec![super::Error {
+        error: "E is for error",
+        backtrace: vec!["E", "A"]
+    }]);
+
+    let Outcome { completed: ok, errors: err, .. } =
+        forest.process_obligations(&mut C(|obligation| {
+            match *obligation {
+                "D" => Err("D is dead"),
+                _ => unreachable!(),
+            }
+        }, |_|{}));
+    assert_eq!(ok.len(), 0);
+    assert_eq!(err, vec![super::Error {
+        error: "D is dead",
+        backtrace: vec!["D"]
+    }]);
+
+    let errors = forest.to_errors(());
+    assert_eq!(errors.len(), 0);
 }
diff --git a/src/librustc_data_structures/obligation_forest/tree_index.rs b/src/librustc_data_structures/obligation_forest/tree_index.rs
deleted file mode 100644
index 499448634ac..00000000000
--- a/src/librustc_data_structures/obligation_forest/tree_index.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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
-    }
-}