--- /dev/null
+The `ObligationForest` is a utility data structure used in trait
+matching to track the set of outstanding obligations (those not yet
+resolved to success or error). It also tracks the "backtrace" of each
+pending obligation (why we are trying to figure this out in the first
+place).
+
+### External view
+
+`ObligationForest` supports two main public operations (there are a
+few others not discussed here):
+
+1. Add a new root obligation (`push_root`).
+2. Process the pending obligations (`process_obligations`).
+
+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
+root and the only leaf. Each time the `process_obligations` method is
+called, it will invoke its callback with every pending obligation (so
+that will include `N`, the first time). The callback shoud process the
+obligation `O` that it is given and return one of three results:
+
+- `Ok(None)` -> ambiguous result. Obligation was neither a success
+ nor a failure. It is assumed that further attempts to process the
+ obligation will yield the same result unless something in the
+ surrounding environment changes.
+- `Ok(Some(C))` - the obligation was *shallowly successful*. The
+ vector `C` is a list of subobligations. The meaning of this is that
+ `O` was successful on the assumption that all the obligations in `C`
+ are also successful. Therefore, `O` is only considered a "true"
+ success if `C` is empty. Otherwise, `O` is put into a suspended
+ state and the obligations in `C` become the new pending
+ obligations. They will be processed the next time you call
+ `process_obligations`.
+- `Err(E)` -> obligation failed with error `E`. We will collect this
+ error and return it from `process_obligations`, along with the
+ "backtrace" of obligations (that is, the list of obligations up to
+ and including the root of the failed obligation). No further
+ obligations from that same tree will be processed, since the tree is
+ now considered to be in error.
+
+When the call to `process_obligations` completes, you get back an `Outcome`,
+which includes three bits of information:
+
+- `completed`: a list of obligations where processing was fully
+ completed without error (meaning that all transitive subobligations
+ have also been completed). So, for example, if the callback from
+ `process_obligations` returns `Ok(Some(C))` for some obligation `O`,
+ then `O` will be considered completed right away if `C` is the
+ empty vector. Otherwise it will only be considered completed once
+ all the obligations in `C` have been found completed.
+- `errors`: a list of errors that occurred and associated backtraces
+ at the time of error, which can be used to give context to the user.
+- `stalled`: if true, then none of the existing obligations were
+ *shallowly successful* (that is, no callback returned `Ok(Some(_))`).
+ This implies that all obligations were either errors or returned an
+ ambiguous result, which means that any further calls to
+ `process_obligations` would simply yield back further ambiguous
+ results. This is used by the `FulfillmentContext` to decide when it
+ has reached a steady state.
+
+#### Snapshots
+
+The `ObligationForest` supports a limited form of snapshots; see
+`start_snapshot`; `commit_snapshot`; and `rollback_snapshot`. In
+particular, you can use a snapshot to roll back new root
+obligations. However, it is an error to attempt to
+`process_obligations` during a snapshot.
+
+### Implementation details
+
+For the most part, comments specific to the implementation are in the
+code. This file only contains a very high-level overview. Basically,
+the forest is stored in a vector. Each element of the vector is a node
+in some tree. Each node in the vector has the index of an (optional)
+parent and (for convenience) its root (which may be itself). It also
+has a current state, described by `NodeState`. After each
+processing step, we compress the vector to remove completed and error
+nodes, which aren't needed anymore.
+
+
// option. This file may not be copied, modified, or distributed
// except according to those terms.
+//! The `ObligationForest` is a utility data structure used in trait
+//! matching to track the set of outstanding obligations (those not
+//! yet resolved to success or error). It also tracks the "backtrace"
+//! of each pending obligation (why we are trying to figure this out
+//! in the first place). See README.md for a general overview of how
+//! to use this class.
+
use std::fmt::Debug;
use std::mem;
mod test;
pub struct ObligationForest<O> {
+ /// 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
+ /// incomplete children). During processing, some of those nodes
+ /// may be changed to the error state, or we may find that they
+ /// are completed (That is, `num_incomplete_children` drops to 0).
+ /// At the end of processing, those nodes will be removed by a
+ /// call to `compress`.
+ ///
+ /// At all times we maintain the invariant that every node appears
+ /// at a higher index than its parent. This is needed by the
+ /// backtrace iterator (which uses `split_at`).
nodes: Vec<Node<O>>,
snapshots: Vec<usize>
}
root: NodeIndex, // points to the root, which may be the current node
}
+/// 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> {
- Leaf { obligation: O },
- Success { obligation: O, num_children: usize },
+ /// 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 },
+
+ /// This obligation was resolved to an error. Error nodes are
+ /// removed from the vector by the compression step.
Error,
}
pub struct Outcome<O,E> {
/// Obligations that were completely evaluated, including all
/// (transitive) subobligations.
- pub successful: Vec<O>,
+ pub completed: Vec<O>,
/// Backtrace of obligations that were found to be in error.
pub errors: Vec<Error<O,E>>,
/// If true, then we saw no successful obligations, which means
/// there is no point in further iteration. This is based on the
- /// assumption that `Err` and `Ok(None)` results do not affect
- /// environmental inference state. (Note that if we invoke
- /// `process_obligations` with no pending obligations, stalled
- /// will be true.)
+ /// assumption that when trait matching returns `Err` or
+ /// `Ok(None)`, those results do not affect environmental
+ /// inference state. (Note that if we invoke `process_obligations`
+ /// with no pending obligations, stalled will be true.)
pub stalled: bool,
}
}
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());
let nodes_len = self.snapshots.pop().unwrap();
- // the only action permitted while in a snapshot is to push new roots
+ // The only action permitted while in a snapshot is to push
+ // new root obligations. Because no processing will have been
+ // done, those roots should still be in the pending state.
debug_assert!(self.nodes[nodes_len..].iter().all(|n| match n.state {
- NodeState::Leaf { .. } => true,
+ NodeState::Pending { .. } => true,
_ => false,
}));
}
/// Convert all remaining obligations to the given error.
+ ///
+ /// This cannot be done during a snapshot.
pub fn to_errors<E:Clone>(&mut self, error: E) -> Vec<Error<O,E>> {
+ 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::Leaf { .. } = self.nodes[index].state {
+ if let NodeState::Pending { .. } = self.nodes[index].state {
let backtrace = self.backtrace(index);
errors.push(Error { error: error.clone(), backtrace: backtrace });
}
errors
}
- /// Convert all remaining obligations to the given error.
+ /// Returns the set of obligations that are in a pending state.
pub fn pending_obligations(&self) -> Vec<O> where O: Clone {
self.nodes.iter()
.filter_map(|n| match n.state {
- NodeState::Leaf { ref obligation } => Some(obligation),
+ NodeState::Pending { ref obligation } => Some(obligation),
_ => None,
})
.cloned()
let (prefix, suffix) = self.nodes.split_at_mut(index);
let backtrace = Backtrace::new(prefix, parent);
match suffix[0].state {
- NodeState::Error => continue,
- NodeState::Success { .. } => continue,
- NodeState::Leaf { ref mut obligation } => action(obligation, backtrace),
+ NodeState::Error |
+ NodeState::Success { .. } =>
+ continue,
+ NodeState::Pending { ref mut obligation } =>
+ action(obligation, backtrace),
}
};
debug!("process_obligations: complete");
Outcome {
- successful: successful_obligations,
+ completed: successful_obligations,
errors: errors,
stalled: stalled,
}
fn success(&mut self, index: usize, children: Vec<O>) {
debug!("success(index={}, children={:?})", index, children);
- let num_children = children.len();
+ let num_incomplete_children = children.len();
- if num_children == 0 {
+ if num_incomplete_children == 0 {
// if there is no work left to be done, decrement parent's ref count
self.update_parent(index);
} else {
.map(|o| Node::new(root_index, Some(node_index), o)));
}
- // change state from `Leaf` to `Success`, temporarily swapping in `Error`
+ // 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::Leaf { obligation } =>
+ NodeState::Pending { obligation } =>
NodeState::Success { obligation: obligation,
- num_children: num_children },
- NodeState::Success { .. } | NodeState::Error =>
+ num_incomplete_children: num_incomplete_children },
+ NodeState::Success { .. } |
+ NodeState::Error =>
unreachable!()
};
}
if let Some(parent) = self.nodes[child].parent {
let parent = parent.get();
match self.nodes[parent].state {
- NodeState::Success { ref mut num_children, .. } => {
- *num_children -= 1;
- if *num_children > 0 {
+ NodeState::Success { ref mut num_incomplete_children, .. } => {
+ *num_incomplete_children -= 1;
+ if *num_incomplete_children > 0 {
return;
}
}
}
}
- /// If the root of `child` is in an error error, places `child`
- /// into an error state.
+ /// 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 root = self.nodes[child].root.get();
if let NodeState::Error = self.nodes[root].state {
/// 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> {
let mut trace = vec![];
loop {
let state = mem::replace(&mut self.nodes[p].state, NodeState::Error);
match state {
- NodeState::Leaf { obligation } |
+ NodeState::Pending { obligation } |
NodeState::Success { obligation, .. } => {
trace.push(obligation);
}
(0 .. dead).map(|_| self.nodes.pop().unwrap())
.flat_map(|node| match node.state {
NodeState::Error => None,
- NodeState::Leaf { .. } => unreachable!(),
- NodeState::Success { obligation, num_children } => {
- assert_eq!(num_children, 0);
+ NodeState::Pending { .. } => unreachable!(),
+ NodeState::Success { obligation, num_incomplete_children } => {
+ assert_eq!(num_incomplete_children, 0);
Some(obligation)
}
})
fn new(root: NodeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
Node {
parent: parent,
- state: NodeState::Leaf { obligation: obligation },
+ state: NodeState::Pending { obligation: obligation },
root: root
}
}
fn is_popped(&self) -> bool {
match self.state {
- NodeState::Leaf { .. } => false,
- NodeState::Success { num_children, .. } => num_children == 0,
+ NodeState::Pending { .. } => false,
+ NodeState::Success { num_incomplete_children, .. } => num_incomplete_children == 0,
NodeState::Error => true,
}
}
if let Some(p) = self.pointer {
self.pointer = self.nodes[p.get()].parent;
match self.nodes[p.get()].state {
- NodeState::Leaf { ref obligation } | NodeState::Success { ref obligation, .. } => {
+ NodeState::Pending { ref obligation } |
+ NodeState::Success { ref obligation, .. } => {
Some(obligation)
}
NodeState::Error => {
// A |-> A.1
// |-> A.2
// |-> A.3
- let Outcome { successful: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
+ let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
match *obligation {
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
"B" => Err("B is for broken"),
// D |-> D.1
// |-> D.2
forest.push_root("D");
- let Outcome { successful: ok, errors: err, .. }: Outcome<&'static str, ()> =
+ let Outcome { completed: ok, errors: err, .. }: Outcome<&'static str, ()> =
forest.process_obligations(|obligation, _| {
match *obligation {
"A.1" => Ok(None),
// propagates to A.3.i, but not D.1 or D.2.
// D |-> D.1 |-> D.1.i
// |-> D.2 |-> D.2.i
- let Outcome { successful: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
+ let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
match *obligation {
"A.1" => Ok(Some(vec![])),
"A.2" => Err("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 { successful: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
+ let Outcome { completed: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
match *obligation {
"D.1.i" => Err("D is for dumb"),
_ => panic!("unexpected obligation {:?}", obligation),
let mut forest = ObligationForest::new();
forest.push_root("A");
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, _| {
match *obligation {
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
assert!(ok.is_empty());
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, _| {
match *obligation {
"A.1" => Ok(Some(vec![])),
assert_eq!(ok, vec!["A.3", "A.1"]);
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, _| {
match *obligation {
"A.2.i" => Ok(Some(vec!["A.2.i.a"])),
assert_eq!(ok, vec!["A.2.ii"]);
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, _| {
match *obligation {
"A.2.i.a" => Ok(Some(vec![])),
assert_eq!(ok, vec!["A.2.i.a", "A.2.i", "A.2", "A"]);
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|_, _| unreachable!());
assert!(ok.is_empty());
assert!(err.is_empty());
// only yields one of them (and does not panic, in particular).
let mut forest = ObligationForest::new();
forest.push_root("A");
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, _| {
match *obligation {
"A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
// only yields one of them (and does not panic, in particular).
let mut forest: ObligationForest<&'static str> = ObligationForest::new();
forest.push_root("A");
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
assert!(backtrace.next().is_none());
match *obligation {
});
assert!(ok.is_empty());
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
assert!(backtrace.next().unwrap() == &"A");
assert!(backtrace.next().is_none());
});
assert!(ok.is_empty());
assert!(err.is_empty());
- let Outcome { successful: ok, errors: err, .. } =
+ let Outcome { completed: ok, errors: err, .. } =
forest.process_obligations::<(),_>(|obligation, mut backtrace| {
assert!(backtrace.next().unwrap() == &"A.1");
assert!(backtrace.next().unwrap() == &"A");