diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index ef64d7dde09..1fbbdf17455 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -24,17 +24,21 @@ 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 index 00000000000..21fa150b012 --- /dev/null +++ b/src/librustc_data_structures/obligation_forest/mod.rs @@ -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 or the MIT license +// , 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 { + nodes: Vec>, + snapshots: Vec +} + +pub struct Snapshot { + len: usize, +} + +pub use self::node_index::NodeIndex; + +struct Node { + state: NodeState, + parent: Option, + root: NodeIndex, // points to the root, which may be the current node +} + +#[derive(Debug)] +enum NodeState { + Leaf { obligation: O }, + Success { obligation: O, num_children: usize }, + Error, +} + +#[derive(Debug)] +pub struct Outcome { + /// Obligations that were completely evaluated, including all + /// (transitive) subobligations. + pub successful: Vec, + + /// Backtrace of obligations that were found to be in error. + pub errors: Vec>, + + /// 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 { + pub error: E, + pub backtrace: Vec, +} + +impl ObligationForest { + pub fn new() -> ObligationForest { + 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(&mut self, error: E) -> Vec> { + 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 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(&mut self, mut action: F) -> Outcome + where E: Debug, F: FnMut(&mut O, Backtrace) -> Result>, 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) { + 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 { + 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 { + 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 Node { + fn new(root: NodeIndex, parent: Option, obligation: O) -> Node { + 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], + pointer: Option, +} + +impl<'b, O> Backtrace<'b, O> { + fn new(nodes: &'b [Node], pointer: Option) -> 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 index 00000000000..ecfecd4e628 --- /dev/null +++ b/src/librustc_data_structures/obligation_forest/node_index.rs @@ -0,0 +1,21 @@ +use core::nonzero::NonZero; +use std::u32; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct NodeIndex { + index: NonZero +} + +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 index 00000000000..039b683717f --- /dev/null +++ b/src/librustc_data_structures/obligation_forest/test.rs @@ -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()); +} diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 09e6f454fb4..8985b1e56bc 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -869,7 +869,7 @@ impl LateLintPass for UnconditionalRecursion { 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.