]> git.lizzy.rs Git - rust.git/commitdiff
Document `ObligationForest` better.
authorNiko Matsakis <niko@alum.mit.edu>
Sat, 16 Jan 2016 10:21:01 +0000 (05:21 -0500)
committerNiko Matsakis <niko@alum.mit.edu>
Sat, 16 Jan 2016 10:22:42 +0000 (05:22 -0500)
src/librustc/middle/traits/fulfill.rs
src/librustc_data_structures/obligation_forest/README.md [new file with mode: 0644]
src/librustc_data_structures/obligation_forest/mod.rs
src/librustc_data_structures/obligation_forest/test.rs

index 7d1af8ca83aa02285f6153863f5c277504f3bc7b..6ef8404cf079a7ce9ef61f499c9bf8dd8ef4f5ee 100644 (file)
@@ -281,7 +281,7 @@ fn select<'a>(&mut self,
             debug!("select_where_possible: outcome={:?}", outcome);
 
             // these are obligations that were proven to be true.
-            for pending_obligation in outcome.successful {
+            for pending_obligation in outcome.completed {
                 let predicate = &pending_obligation.obligation.predicate;
                 if predicate.is_global() {
                     selcx.tcx().fulfilled_predicates.borrow_mut()
diff --git a/src/librustc_data_structures/obligation_forest/README.md b/src/librustc_data_structures/obligation_forest/README.md
new file mode 100644 (file)
index 0000000..1ffe07b
--- /dev/null
@@ -0,0 +1,80 @@
+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.
+
+  
index d23b42790d762f18a9bd3bbe7a831713eb37a8da..0d92a2b158f82496ac10e5e16edde3ccacf23ae0 100644 (file)
@@ -8,6 +8,13 @@
 // 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>
 }
@@ -33,10 +52,26 @@ struct Node<O> {
     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,
 }
 
@@ -44,17 +79,17 @@ enum NodeState<O> {
 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,
 }
 
@@ -90,13 +125,15 @@ pub fn commit_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());
         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,
         }));
 
@@ -116,12 +153,15 @@ pub fn push_root(&mut self, obligation: O) {
     }
 
     /// 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 });
             }
@@ -131,11 +171,11 @@ pub fn to_errors<E:Clone>(&mut self, error: E) -> Vec<Error<O,E>> {
         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()
@@ -174,9 +214,11 @@ pub fn process_obligations<E,F>(&mut self, mut action: F) -> Outcome<O,E>
                 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),
                 }
             };
 
@@ -204,7 +246,7 @@ pub fn process_obligations<E,F>(&mut self, mut action: F) -> Outcome<O,E>
         debug!("process_obligations: complete");
 
         Outcome {
-            successful: successful_obligations,
+            completed: successful_obligations,
             errors: errors,
             stalled: stalled,
         }
@@ -219,9 +261,9 @@ pub fn process_obligations<E,F>(&mut self, mut action: F) -> Outcome<O,E>
     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 {
@@ -233,13 +275,14 @@ fn success(&mut self, index: usize, children: Vec<O>) {
                         .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!()
         };
     }
@@ -251,9 +294,9 @@ fn update_parent(&mut self, child: usize) {
         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;
                     }
                 }
@@ -263,8 +306,10 @@ fn update_parent(&mut self, child: usize) {
         }
     }
 
-    /// 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 {
@@ -274,12 +319,15 @@ fn inherit_error(&mut self, child: usize) {
 
     /// 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);
                 }
@@ -338,9 +386,9 @@ fn compress(&mut self) -> Vec<O> {
             (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)
                            }
                        })
@@ -365,15 +413,15 @@ impl<O> Node<O> {
     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,
         }
     }
@@ -399,7 +447,8 @@ fn next(&mut self) -> Option<&'b O> {
         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 => {
index 628d3f94bdb90f6d2a2e7cf99c4e74a837985aab..519b282a6a8c7a0f523a1ea81a9886e50572bb6d 100644 (file)
@@ -21,7 +21,7 @@ fn push_pop() {
     //      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"),
@@ -40,7 +40,7 @@ fn push_pop() {
     //      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),
@@ -58,7 +58,7 @@ fn push_pop() {
     // 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"),
@@ -72,7 +72,7 @@ fn push_pop() {
                                  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),
@@ -96,7 +96,7 @@ fn success_in_grandchildren() {
     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"])),
@@ -106,7 +106,7 @@ fn success_in_grandchildren() {
     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![])),
@@ -118,7 +118,7 @@ fn success_in_grandchildren() {
     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"])),
@@ -129,7 +129,7 @@ fn success_in_grandchildren() {
     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![])),
@@ -139,7 +139,7 @@ fn success_in_grandchildren() {
     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());
@@ -151,7 +151,7 @@ fn to_errors_no_throw() {
     // 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"])),
@@ -170,7 +170,7 @@ fn backtrace() {
     // 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 {
@@ -180,7 +180,7 @@ fn backtrace() {
         });
     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());
@@ -191,7 +191,7 @@ fn backtrace() {
         });
     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");