]> git.lizzy.rs Git - rust.git/commitdiff
implement the obligation forest data structure and add some unit tests
authorNiko Matsakis <niko@alum.mit.edu>
Wed, 18 Nov 2015 19:44:24 +0000 (14:44 -0500)
committerNiko Matsakis <niko@alum.mit.edu>
Thu, 14 Jan 2016 17:59:50 +0000 (12:59 -0500)
src/librustc_data_structures/lib.rs
src/librustc_data_structures/obligation_forest/mod.rs [new file with mode: 0644]
src/librustc_data_structures/obligation_forest/node_index.rs [new file with mode: 0644]
src/librustc_data_structures/obligation_forest/test.rs [new file with mode: 0644]
src/librustc_lint/builtin.rs

index ef64d7dde091c0b99f28295e059d0b232a9071df..1fbbdf17455b27872b1297bc3b8982348e92ff01 100644 (file)
       html_favicon_url = "https://www.rust-lang.org/favicon.ico",
       html_root_url = "https://doc.rust-lang.org/nightly/")]
 
-#![feature(rustc_private, staged_api)]
 #![feature(hashmap_hasher)]
+#![feature(nonzero)]
+#![feature(rustc_private)]
+#![feature(staged_api)]
 
 #![cfg_attr(test, feature(test))]
 
+extern crate core;
 #[macro_use] extern crate log;
 extern crate serialize as rustc_serialize; // used by deriving
 
 pub mod bitvec;
 pub mod graph;
 pub mod ivar;
+pub mod obligation_forest;
 pub mod snapshot_vec;
 pub mod transitive_relation;
 pub mod unify;
diff --git a/src/librustc_data_structures/obligation_forest/mod.rs b/src/librustc_data_structures/obligation_forest/mod.rs
new file mode 100644 (file)
index 0000000..21fa150
--- /dev/null
@@ -0,0 +1,412 @@
+// 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::fmt::Debug;
+use std::mem;
+
+mod node_index;
+
+#[cfg(test)]
+mod test;
+
+pub struct ObligationForest<O> {
+    nodes: Vec<Node<O>>,
+    snapshots: Vec<usize>
+}
+
+pub struct Snapshot {
+    len: usize,
+}
+
+pub use self::node_index::NodeIndex;
+
+struct Node<O> {
+    state: NodeState<O>,
+    parent: Option<NodeIndex>,
+    root: NodeIndex, // points to the root, which may be the current node
+}
+
+#[derive(Debug)]
+enum NodeState<O> {
+    Leaf { obligation: O },
+    Success { obligation: O, num_children: usize },
+    Error,
+}
+
+#[derive(Debug)]
+pub struct Outcome<O,E> {
+    /// Obligations that were completely evaluated, including all
+    /// (transitive) subobligations.
+    pub successful: 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.)
+    pub stalled: bool,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Error<O,E> {
+    pub error: E,
+    pub backtrace: Vec<O>,
+}
+
+impl<O: Debug> ObligationForest<O> {
+    pub fn new() -> ObligationForest<O> {
+        ObligationForest {
+            nodes: vec![],
+            snapshots: vec![]
+        }
+    }
+
+    /// Return the total number of nodes in the forest that have not
+    /// yet been fully resolved.
+    pub fn len(&self) -> usize {
+        self.nodes.len()
+    }
+
+    pub fn start_snapshot(&mut self) -> Snapshot {
+        self.snapshots.push(self.nodes.len());
+        Snapshot { len: self.snapshots.len() }
+    }
+
+    pub fn commit_snapshot(&mut self, snapshot: Snapshot) {
+        assert_eq!(snapshot.len, self.snapshots.len());
+        let nodes_len = self.snapshots.pop().unwrap();
+        assert!(self.nodes.len() >= nodes_len);
+    }
+
+    pub fn rollback_snapshot(&mut self, snapshot: Snapshot) {
+        // 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
+        debug_assert!(self.nodes[nodes_len..].iter().all(|n| match n.state {
+            NodeState::Leaf { .. } => true,
+            _ => false,
+        }));
+
+        self.nodes.truncate(nodes_len);
+    }
+
+    pub fn in_snapshot(&self) -> bool {
+        !self.snapshots.is_empty()
+    }
+
+    /// Adds a new tree to the forest.
+    ///
+    /// This CAN be done during a snapshot.
+    pub fn push_root(&mut self, obligation: O) {
+        let index = NodeIndex::new(self.nodes.len());
+        self.nodes.push(Node::new(index, None, obligation));
+    }
+
+    /// Convert all remaining obligations to the given error.
+    pub fn to_errors<E:Clone>(&mut self, error: E) -> Vec<Error<O,E>> {
+        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 {
+                let backtrace = self.backtrace(index);
+                errors.push(Error { error: error.clone(), backtrace: backtrace });
+            }
+        }
+        let successful_obligations = self.compress();
+        assert!(successful_obligations.is_empty());
+        errors
+    }
+
+    /// Convert all remaining obligations to the given error.
+    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),
+                      _ => None,
+                  })
+                  .cloned()
+                  .collect()
+    }
+
+    /// Process the obligations.
+    ///
+    /// 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, Backtrace<O>) -> Result<Option<Vec<O>>, E>
+    {
+        debug!("process_obligations(len={})", self.nodes.len());
+        assert!(!self.in_snapshot()); // cannot unroll this action
+
+        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 parent = self.nodes[index].parent;
+                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),
+                }
+            };
+
+            debug!("process_obligations: node {} got result {:?}", index, result);
+
+            match result {
+                Ok(None) => {
+                    // no change in state
+                }
+                Ok(Some(children)) => {
+                    // if we saw a Some(_) result, we are not (yet) stalled
+                    stalled = false;
+                    self.success(index, children);
+                }
+                Err(err) => {
+                    let backtrace = self.backtrace(index);
+                    errors.push(Error { error: err, backtrace: backtrace });
+                }
+            }
+        }
+
+        // Now we have to compress the result
+        let successful_obligations = self.compress();
+
+        debug!("process_obligations: complete");
+
+        Outcome {
+            successful: successful_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_children = children.len();
+
+        if num_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 root_index = self.nodes[index].root;
+            let node_index = NodeIndex::new(index);
+            self.nodes.extend(
+                children.into_iter()
+                        .map(|o| Node::new(root_index, Some(node_index), o)));
+        }
+
+        // change state from `Leaf` 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::Success { obligation: obligation,
+                                     num_children: num_children },
+            NodeState::Success { .. } | NodeState::Error =>
+                unreachable!()
+        };
+    }
+
+    /// 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_children, .. } => {
+                    *num_children -= 1;
+                    if *num_children > 0 {
+                        return;
+                    }
+                }
+                _ => unreachable!(),
+            }
+            self.update_parent(parent);
+        }
+    }
+
+    /// If the root of `child` is in an error error, places `child`
+    /// into an error state.
+    fn inherit_error(&mut self, child: usize) {
+        let root = self.nodes[child].root.get();
+        if let NodeState::Error = self.nodes[root].state {
+            self.nodes[child].state = NodeState::Error;
+        }
+    }
+
+    /// Returns a vector of obligations for `p` and all of its
+    /// ancestors, putting them into the error state in the process.
+    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::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);
+                }
+            }
+
+            // loop to the parent
+            match self.nodes[p].parent {
+                Some(q) => { p = q.get(); }
+                None => { return trace; }
+            }
+        }
+    }
+
+    /// Compresses the vector, removing all popped nodes. This adjusts
+    /// the indices and hence invalidates any outstanding
+    /// indices. Cannot be used during a transaction.
+    fn compress(&mut self) -> Vec<O> {
+        assert!(!self.in_snapshot()); // didn't write code to unroll this action
+        let mut rewrites: Vec<_> = (0..self.nodes.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);
+            }
+        }
+
+        // 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 = 0;
+        for i in 0..nodes_len {
+            if self.nodes[i].is_popped() {
+                dead += 1;
+            } else if dead > 0 {
+                self.nodes.swap(i, i - dead);
+                rewrites[i] -= dead;
+            }
+        }
+
+        // Pop off all the nodes we killed and extract the success
+        // stories.
+        let successful =
+            (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);
+                               Some(obligation)
+                           }
+                       })
+                       .collect();
+
+        // Adjust the parent indices, since we compressed things.
+        for node in &mut self.nodes {
+            if let Some(ref mut index) = node.parent {
+                let new_index = rewrites[index.get()];
+                debug_assert!(new_index < (nodes_len - dead));
+                *index = NodeIndex::new(new_index);
+            }
+
+            node.root = NodeIndex::new(rewrites[node.root.get()]);
+        }
+
+        successful
+    }
+}
+
+impl<O> Node<O> {
+    fn new(root: NodeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
+        Node {
+            parent: parent,
+            state: NodeState::Leaf { obligation: obligation },
+            root: root
+        }
+    }
+
+    fn is_popped(&self) -> bool {
+        match self.state {
+            NodeState::Leaf { .. } => false,
+            NodeState::Success { num_children, .. } => num_children == 0,
+            NodeState::Error => true,
+        }
+    }
+}
+
+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::Leaf { ref obligation } | NodeState::Success { ref obligation, .. } => {
+                    Some(obligation)
+                }
+                NodeState::Error => {
+                    panic!("Backtrace encountered an error.");
+                }
+            }
+        } else {
+            None
+        }
+    }
+}
diff --git a/src/librustc_data_structures/obligation_forest/node_index.rs b/src/librustc_data_structures/obligation_forest/node_index.rs
new file mode 100644 (file)
index 0000000..ecfecd4
--- /dev/null
@@ -0,0 +1,21 @@
+use core::nonzero::NonZero;
+use std::u32;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct NodeIndex {
+    index: NonZero<u32>
+}
+
+impl NodeIndex {
+    pub fn new(value: usize) -> NodeIndex {
+        assert!(value < (u32::MAX as usize));
+        unsafe {
+            NodeIndex { index: NonZero::new((value as u32) + 1) }
+        }
+    }
+
+    pub fn get(self) -> usize {
+        (*self.index - 1) as usize
+    }
+}
+
diff --git a/src/librustc_data_structures/obligation_forest/test.rs b/src/librustc_data_structures/obligation_forest/test.rs
new file mode 100644 (file)
index 0000000..039b683
--- /dev/null
@@ -0,0 +1,188 @@
+use super::{ObligationForest, Outcome, Error};
+
+#[test]
+fn push_pop() {
+    let mut forest = ObligationForest::new();
+    forest.push_root("A");
+    forest.push_root("B");
+    forest.push_root("C");
+
+    // first round, B errors out, A has subtasks, and C completes, creating this:
+    //      A |-> A.1
+    //        |-> A.2
+    //        |-> A.3
+    let Outcome { successful: 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"),
+            "C" => Ok(Some(vec![])),
+            _ => unreachable!(),
+        }
+    });
+    assert_eq!(ok, vec!["C"]);
+    assert_eq!(err, vec![Error {error: "B is for broken",
+                                backtrace: vec!["B"]}]);
+
+    // second round: two delays, one success, creating an uneven set of subtasks:
+    //      A |-> A.1
+    //        |-> A.2
+    //        |-> A.3 |-> A.3.i
+    //      D |-> D.1
+    //        |-> D.2
+    forest.push_root("D");
+    let Outcome { successful: ok, errors: err, .. }: Outcome<&'static str, ()> =
+        forest.process_obligations(|obligation, _| {
+            match *obligation {
+                "A.1" => Ok(None),
+                "A.2" => Ok(None),
+                "A.3" => Ok(Some(vec!["A.3.i"])),
+                "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.
+    //      D |-> D.1 |-> D.1.i
+    //        |-> D.2 |-> D.2.i
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
+        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"]);
+    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 { successful: ok, errors: err, .. } = forest.process_obligations(|obligation, _| {
+        match *obligation {
+            "D.1.i" => Err("D is for dumb"),
+            _ => panic!("unexpected obligation {:?}", obligation),
+        }
+    });
+    assert_eq!(ok, Vec::<&'static str>::new());
+    assert_eq!(err, vec![Error { error: "D is for dumb",
+                                 backtrace: vec!["D.1.i", "D.1", "D"] }]);
+}
+
+// Test that if a tree with grandchildren succeeds, everything is
+// reported as expected:
+// A
+//   A.1
+//   A.2
+//      A.2.i
+//      A.2.ii
+//   A.3
+#[test]
+fn success_in_grandchildren() {
+    let mut forest = ObligationForest::new();
+    forest.push_root("A");
+
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|obligation, _| {
+        match *obligation {
+            "A" => Ok(Some(vec!["A.1", "A.2", "A.3"])),
+            _ => unreachable!(),
+        }
+    });
+    assert!(ok.is_empty());
+    assert!(err.is_empty());
+
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|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 { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|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 { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|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 { successful: ok, errors: err, .. } =
+        forest.process_obligations::<(),_>(|_, _| unreachable!());
+    assert!(ok.is_empty());
+    assert!(err.is_empty());
+}
+
+#[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).
+    let mut forest = ObligationForest::new();
+    forest.push_root("A");
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|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);
+}
+
+#[test]
+fn backtrace() {
+    // check that converting multiple children with common parent (A)
+    // 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, .. } = forest.process_obligations::<(),_>(|obligation, mut backtrace| {
+        assert!(backtrace.next().is_none());
+        match *obligation {
+            "A" => Ok(Some(vec!["A.1"])),
+            _ => unreachable!(),
+        }
+    });
+    assert!(ok.is_empty());
+    assert!(err.is_empty());
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|obligation, mut backtrace| {
+        assert!(backtrace.next().unwrap() == &"A");
+        assert!(backtrace.next().is_none());
+        match *obligation {
+            "A.1" => Ok(Some(vec!["A.1.i"])),
+            _ => unreachable!(),
+        }
+    });
+    assert!(ok.is_empty());
+    assert!(err.is_empty());
+    let Outcome { successful: ok, errors: err, .. } = forest.process_obligations::<(),_>(|obligation, mut backtrace| {
+        assert!(backtrace.next().unwrap() == &"A.1");
+        assert!(backtrace.next().unwrap() == &"A");
+        assert!(backtrace.next().is_none());
+        match *obligation {
+            "A.1.i" => Ok(None),
+            _ => unreachable!(),
+        }
+    });
+    assert_eq!(ok.len(), 0);
+    assert!(err.is_empty());
+}
index 09e6f454fb4f51c5ffaccba9fdccff42e83da4b7..8985b1e56bc0097a2679137adc5c81b7c74046d4 100644 (file)
@@ -869,7 +869,7 @@ fn method_call_refers_to_method<'tcx>(tcx: &ty::ctxt<'tcx>,
                     let node_id = tcx.map.as_local_node_id(method.def_id).unwrap();
 
                     let param_env = ty::ParameterEnvironment::for_item(tcx, node_id);
-                    let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, Some(param_env), false);
+                    let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, Some(param_env));
                     let mut selcx = traits::SelectionContext::new(&infcx);
                     match selcx.select(&obligation) {
                         // The method comes from a `T: Trait` bound.