From 4477b07e8efddd08bbe30b2e4209acbd895b067a Mon Sep 17 00:00:00 2001 From: Masood Malekghassemi Date: Mon, 8 Feb 2016 00:36:18 -0800 Subject: [PATCH 1/2] Add snapshotting to ObligationForest --- src/librustc/middle/traits/fulfill.rs | 50 ++- src/librustc_data_structures/lib.rs | 1 + .../obligation_forest/mod.rs | 336 ++++++++++++++---- .../obligation_forest/test.rs | 232 +++++++++++- src/librustc_data_structures/undoable.rs | 35 ++ 5 files changed, 587 insertions(+), 67 deletions(-) create mode 100644 src/librustc_data_structures/undoable.rs diff --git a/src/librustc/middle/traits/fulfill.rs b/src/librustc/middle/traits/fulfill.rs index bb411e76e929b..71be0dc68580f 100644 --- a/src/librustc/middle/traits/fulfill.rs +++ b/src/librustc/middle/traits/fulfill.rs @@ -11,8 +11,11 @@ use dep_graph::DepGraph; use middle::infer::InferCtxt; use middle::ty::{self, Ty, TypeFoldable}; -use rustc_data_structures::obligation_forest::{Backtrace, ObligationForest, Error}; +use rustc_data_structures::obligation_forest::{Backtrace, ObligationForest, UndoLog, Error}; +use rustc_data_structures::undoable::{Undoable, UndoableTracker, Undoer}; +use std::cell::RefCell; use std::iter; +use std::rc::Rc; use syntax::ast; use util::common::ErrorReported; use util::nodemap::{FnvHashMap, FnvHashSet, NodeMap}; @@ -31,14 +34,18 @@ use super::select::SelectionContext; use super::Unimplemented; use super::util::predicate_for_builtin_bound; +pub type ObligationForestUndoLog<'tcx> = UndoLog, + LocalFulfilledPredicates<'tcx>>; + pub struct GlobalFulfilledPredicates<'tcx> { set: FnvHashSet>, dep_graph: DepGraph, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LocalFulfilledPredicates<'tcx> { - set: FnvHashSet> + set: FnvHashSet>, + log: Option<(Rc>>, usize)>, } /// The fulfillment context is used to drive trait resolution. It @@ -110,6 +117,12 @@ pub struct PendingPredicateObligation<'tcx> { pub stalled_on: Vec>, } +#[derive(Debug)] +pub enum LocalFulfilledPredicatesAction<'tcx> { + Add { key: ty::Predicate<'tcx> } +} + + impl<'tcx> FulfillmentContext<'tcx> { /// Creates a new fulfillment context. pub fn new() -> FulfillmentContext<'tcx> { @@ -670,11 +683,19 @@ fn register_region_obligation<'tcx>(t_a: Ty<'tcx>, impl<'tcx> LocalFulfilledPredicates<'tcx> { pub fn new() -> LocalFulfilledPredicates<'tcx> { LocalFulfilledPredicates { - set: FnvHashSet() + set: FnvHashSet(), + log: None, } } fn is_duplicate_or_add(&mut self, key: &ty::Predicate<'tcx>) -> bool { + let insert_result = self.set.insert(key.clone()); + if insert_result { + if let Some((ref log, id)) = self.log { + (*log).borrow_mut().push_action( + id, LocalFulfilledPredicatesAction::Add { key: key.clone() }); + } + } // For a `LocalFulfilledPredicates`, if we find a match, we // don't need to add a read edge to the dep-graph. This is // because it means that the predicate has already been @@ -682,7 +703,26 @@ impl<'tcx> LocalFulfilledPredicates<'tcx> { // containing task will already have an edge. (Here we are // assuming each `FulfillmentContext` only gets used from one // task; but to do otherwise makes no sense) - !self.set.insert(key.clone()) + !insert_result + } +} + +impl<'tcx> Undoer for LocalFulfilledPredicatesAction<'tcx> { + type Undoable = LocalFulfilledPredicates<'tcx>; + fn undo(self, predicates: &mut LocalFulfilledPredicates<'tcx>) { + match self { + LocalFulfilledPredicatesAction::Add { key } => { + assert!(predicates.set.remove(&key)); + } + } + } +} + +impl<'tcx> Undoable for LocalFulfilledPredicates<'tcx> { + type Undoer = LocalFulfilledPredicatesAction<'tcx>; + type Tracker = ObligationForestUndoLog<'tcx>; + fn register_tracker(&mut self, log: Rc>, id: usize) { + self.log = Some((log, id)); } } diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index e4b13ff548a45..f4698ba85bb60 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -44,6 +44,7 @@ pub mod transitive_relation; pub mod unify; pub mod fnv; pub mod tuple_slice; +pub mod undoable; pub mod veccell; // See comments in src/librustc/lib.rs diff --git a/src/librustc_data_structures/obligation_forest/mod.rs b/src/librustc_data_structures/obligation_forest/mod.rs index 25a77adba2820..f895ffedf1574 100644 --- a/src/librustc_data_structures/obligation_forest/mod.rs +++ b/src/librustc_data_structures/obligation_forest/mod.rs @@ -15,8 +15,14 @@ //! in the first place). See README.md for a general overview of how //! to use this class. +#![allow(dead_code)] + +use std::cell::RefCell; use std::fmt::Debug; use std::mem; +use std::rc::Rc; + +use undoable::{Undoable, UndoableTracker, Undoer}; mod node_index; use self::node_index::NodeIndex; @@ -28,7 +34,8 @@ use self::tree_index::TreeIndex; #[cfg(test)] mod test; -pub struct ObligationForest { + +pub struct ObligationForest { /// 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,19 +49,28 @@ pub struct ObligationForest { /// at a higher index than its parent. This is needed by the /// backtrace iterator (which uses `split_at`). nodes: Vec>, + + /// The list of per-tree data. trees: Vec>, - snapshots: Vec + + log: Rc>>, } +#[derive(Debug)] +pub struct UndoLog(Vec>); + +#[derive(Debug)] pub struct Snapshot { - len: usize, + log_len: usize, } +#[derive(Debug)] struct Tree { root: NodeIndex, state: T, } +#[derive(Debug)] struct Node { state: NodeState, parent: Option, @@ -84,6 +100,43 @@ enum NodeState { Error, } +#[derive(Debug)] +enum LogItem { + /// Any push of a new node, whether it's a root or not. If it's a + /// root, then we can assume that at this point in the log a new + /// tree state had been pushed as well. + PushPending, + PendingIntoError { + at: NodeIndex, + old_obligation: O, + }, + PendingIntoSuccess { + at: NodeIndex + }, + SuccessIntoError { + at: NodeIndex, + old_obligation: O, + old_num_incomplete_children: usize, + }, + /// Because each tree has interesting state, each tree needs some + /// record of its state at a snapshot. Instead of combining all + /// that state into one snapshot item (which'd end up being a + /// list of some kind) we just shove it into the same log as all + /// other ObligationForest modifications. We know where an actual + /// OF snapshot begins via the Snapshot structure (passed back to + /// user code) keeping track of our location in the log. + EnterSnapshot, + TreeModify { + tree: TreeIndex, + undoer: T::Undoer, + }, + /// A placeholder in the log for operations that, for some + /// reason, effectively became no-ops. For the OF, it is what + /// replaces a EnterSnapshot when that snapshot is committed and + /// the OF is still in at least one snapshot. + NoOp, +} + #[derive(Debug)] pub struct Outcome { /// Obligations that were completely evaluated, including all @@ -108,12 +161,12 @@ pub struct Error { pub backtrace: Vec, } -impl ObligationForest { +impl>> ObligationForest { pub fn new() -> ObligationForest { ObligationForest { trees: vec![], nodes: vec![], - snapshots: vec![] + log: Rc::new(RefCell::new(UndoLog(vec![]))), } } @@ -124,53 +177,90 @@ impl ObligationForest { } pub fn start_snapshot(&mut self) -> Snapshot { - self.snapshots.push(self.trees.len()); - Snapshot { len: self.snapshots.len() } + let log = &mut self.log.borrow_mut().0; + let result = Snapshot { log_len: log.len() }; + log.push(LogItem::EnterSnapshot); + result } 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); + self.expect_top_snapshot_at(snapshot.log_len); + let log = &mut self.log.borrow_mut().0; + if snapshot.log_len == 0 { + log.clear(); + } else { + for i in snapshot.log_len..log.len() { + let into_noop = match &log[i] { + &LogItem::EnterSnapshot => true, + _ => false, + }; + if into_noop { + mem::replace(&mut log[i], LogItem::NoOp); + } + } + } } 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(); - - // If nothing happened in snapshot, done. - if self.trees.len() == trees_len { - return; + self.expect_top_snapshot_at(snapshot.log_len); + let log = &mut self.log.borrow_mut().0; + while log.len() > snapshot.log_len { + match log.pop().unwrap() { + LogItem::PushPending => { + let node = self.nodes.pop().unwrap(); + if self.trees[node.tree.get()].root.get() == self.nodes.len() { + // We just popped a root; we should also pop the associated tree + self.trees.pop().unwrap(); + } + }, + LogItem::PendingIntoError { at, old_obligation } => { + self.nodes[at.get()].state.error_into_pending(old_obligation); + }, + LogItem::PendingIntoSuccess { at } => { + self.nodes[at.get()].state.success_into_pending(); + let mut parent = self.nodes[at.get()].parent; + // now walk the parents of the node, incrementing num_incomplete_children if + // need be. + while let Some(index) = parent { + match &mut self.nodes[index.get()].state { + &mut NodeState::Success { + num_incomplete_children: ref mut num @ 0, + .. + } => { *num += 1; }, + &mut NodeState::Success { .. } => break, + &mut NodeState::Pending { .. } | + &mut NodeState::Error => panic!(), + } + parent = self.nodes[index.get()].parent; + } + }, + LogItem::SuccessIntoError { at, old_obligation, old_num_incomplete_children } => { + self.nodes[at.get()].state.error_into_success( + old_obligation, old_num_incomplete_children); + }, + LogItem::EnterSnapshot => { assert!(log.len() == snapshot.log_len); }, + LogItem::TreeModify { tree, undoer } => { + undoer.undo(&mut self.trees[tree.get()].state); + } + LogItem::NoOp => {}, + } } - - // 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); } pub fn in_snapshot(&self) -> bool { - !self.snapshots.is_empty() + self.log.borrow().in_snapshot() } /// Adds a new tree to the forest. /// /// This CAN be done during a snapshot. - pub fn push_tree(&mut self, obligation: O, tree_state: T) { + pub fn push_tree(&mut self, obligation: O, mut tree_state: T) { let index = NodeIndex::new(self.nodes.len()); let tree = TreeIndex::new(self.trees.len()); + tree_state.register_tracker(self.log.clone(), self.trees.len()); self.trees.push(Tree { root: index, state: tree_state }); self.nodes.push(Node::new(tree, None, obligation)); + self.log_maybe(|| Some(LogItem::PushPending)); } /// Convert all remaining obligations to the given error. @@ -193,7 +283,7 @@ impl ObligationForest { } /// Returns the set of obligations that are in a pending state. - pub fn pending_obligations(&self) -> Vec where O: Clone { + pub fn pending_obligations(&self) -> Vec { self.nodes.iter() .filter_map(|n| match n.state { NodeState::Pending { ref obligation } => Some(obligation), @@ -204,15 +294,13 @@ impl ObligationForest { } /// 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, &mut T, Backtrace) -> Result>, E> { debug!("process_obligations(len={})", self.nodes.len()); - assert!(!self.in_snapshot()); // cannot unroll this action let mut errors = vec![]; + let mut successes = vec![]; let mut stalled = true; // We maintain the invariant that the list is in pre-order, so @@ -224,7 +312,13 @@ impl ObligationForest { // encountered an error. for index in 0..self.nodes.len() { - debug_assert!(!self.nodes[index].is_popped()); + if self.in_snapshot() { + if self.nodes[index].is_popped() { + continue; + } + } else { + assert!(!self.nodes[index].is_popped()); + } self.inherit_error(index); debug!("process_obligations: node {} == {:?}", @@ -252,7 +346,7 @@ impl ObligationForest { Ok(Some(children)) => { // if we saw a Some(_) result, we are not (yet) stalled stalled = false; - self.success(index, children); + self.success(index, children, &mut successes); } Err(err) => { let backtrace = self.backtrace(index); @@ -262,12 +356,14 @@ impl ObligationForest { } // Now we have to compress the result - let successful_obligations = self.compress(); + if !self.in_snapshot() { + self.compress(); + } debug!("process_obligations: complete"); Outcome { - completed: successful_obligations, + completed: successes, errors: errors, stalled: stalled, } @@ -279,14 +375,14 @@ impl ObligationForest { /// `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) { + fn success(&mut self, index: usize, children: Vec, successes: &mut Vec) { 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); + self.update_parent(index, successes); } else { // create child work let tree_index = self.nodes[index].tree; @@ -294,24 +390,25 @@ impl ObligationForest { self.nodes.extend( children.into_iter() .map(|o| Node::new(tree_index, Some(node_index), o))); + self.log_maybe(|| (0..num_incomplete_children).map(|_| LogItem::PushPending)); } - // 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!() - }; + // change state from `Pending` to `Success` + self.nodes[index].state.pending_into_success(num_incomplete_children); + self.log_maybe(|| Some(LogItem::PendingIntoSuccess { at: NodeIndex::new(index) })); } /// 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) { + fn update_parent(&mut self, child: usize, successes: &mut Vec) { debug!("update_parent(child={})", child); + match &self.nodes[child].state { + &NodeState::Pending { ref obligation } | + &NodeState::Success { ref obligation, .. } => { + successes.push(obligation.clone()); + }, + &NodeState::Error => unreachable!(), + } if let Some(parent) = self.nodes[child].parent { let parent = parent.get(); match self.nodes[parent].state { @@ -323,7 +420,7 @@ impl ObligationForest { } _ => unreachable!(), } - self.update_parent(parent); + self.update_parent(parent, successes); } } @@ -335,7 +432,22 @@ impl ObligationForest { 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; + match mem::replace(&mut self.nodes[child].state, NodeState::Error) { + NodeState::Pending { obligation } => { + self.log_maybe(|| Some(LogItem::PendingIntoError { + at: NodeIndex::new(child), + old_obligation: obligation + })); + }, + NodeState::Success { obligation, num_incomplete_children } => { + self.log_maybe(|| Some(LogItem::SuccessIntoError { + at: NodeIndex::new(child), + old_obligation: obligation, + old_num_incomplete_children: num_incomplete_children + })); + }, + NodeState::Error => {}, + } } } @@ -349,10 +461,21 @@ impl ObligationForest { loop { let state = mem::replace(&mut self.nodes[p].state, NodeState::Error); match state { - NodeState::Pending { obligation } | - NodeState::Success { obligation, .. } => { + NodeState::Pending { obligation } => { + self.log_maybe(|| Some(LogItem::PendingIntoError { + at: NodeIndex::new(p), + old_obligation: obligation.clone(), + })); trace.push(obligation); - } + }, + NodeState::Success { obligation, num_incomplete_children } => { + self.log_maybe(|| Some(LogItem::SuccessIntoError { + at: NodeIndex::new(p), + old_obligation: obligation.clone(), + old_num_incomplete_children: num_incomplete_children, + })); + trace.push(obligation); + }, NodeState::Error => { // we should not encounter an error, because if // there was an error in the ancestors, it should @@ -374,7 +497,7 @@ impl ObligationForest { /// 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 + assert!(!self.in_snapshot()); let mut node_rewrites: Vec<_> = (0..self.nodes.len()).collect(); let mut tree_rewrites: Vec<_> = (0..self.trees.len()).collect(); @@ -456,6 +579,26 @@ impl ObligationForest { successful } + + fn expect_top_snapshot_at(&self, at: usize) { + let log = &self.log.borrow().0; + match &log[at] { + &LogItem::EnterSnapshot => {}, + _ => panic!("missing snapshot at index {}", at), + } + for i in (at + 1)..log.len() { + match &log[i] { + &LogItem::EnterSnapshot => panic!("snapshot at {} is not topmost", at), + _ => {} + } + } + } + + fn log_maybe I, I: IntoIterator>>(&mut self, mk_log: F) { + if self.in_snapshot() { + self.log.borrow_mut().0.extend(mk_log()); + } + } } impl Node { @@ -476,6 +619,61 @@ impl Node { } } +impl NodeState { + // The following are helper methods to help enforce constraints on transitions. + + fn pending_into_success(&mut self, num_incomplete_children: usize) { + let result = match mem::replace(self, NodeState::Error) { + NodeState::Pending { obligation } => NodeState::Success { + obligation: obligation, + num_incomplete_children: num_incomplete_children, + }, + _ => unreachable!() + }; + mem::replace(self, result); + } + fn pending_into_error(&mut self) { + match self { + &mut NodeState::Pending { .. } => {}, + _ => unreachable!(), + }; + mem::replace(self, NodeState::Error); + } + fn success_into_error(&mut self) { + match self { + &mut NodeState::Success { .. } => {}, + _ => unreachable!(), + }; + mem::replace(self, NodeState::Error); + } + fn success_into_pending(&mut self) { + let result = match mem::replace(self, NodeState::Error) { + NodeState::Success { obligation, .. } => NodeState::Pending { + obligation: obligation, + }, + _ => unreachable!() + }; + mem::replace(self, result); + } + fn error_into_pending(&mut self, obligation: O) { + match self { + &mut NodeState::Error => {}, + _ => unreachable!() + } + mem::replace(self, NodeState::Pending { obligation: obligation }); + } + fn error_into_success(&mut self, obligation: O, num_incomplete_children: usize) { + match self { + &mut NodeState::Error => {}, + _ => unreachable!() + } + mem::replace(self, NodeState::Success { + obligation: obligation, + num_incomplete_children: num_incomplete_children + }); + } +} + #[derive(Clone)] pub struct Backtrace<'b, O: 'b> { nodes: &'b [Node], @@ -509,3 +707,21 @@ impl<'b, O> Iterator for Backtrace<'b, O> { } } } + +impl UndoLog { + fn in_snapshot(&self) -> bool { + !self.0.is_empty() + } +} + +impl UndoableTracker for UndoLog { + type Undoable = T; + fn push_action(&mut self, id: usize, undoer: T::Undoer) { + if self.in_snapshot() { + self.0.push(LogItem::TreeModify { + tree: TreeIndex::new(id), + undoer: undoer, + }); + } + } +} diff --git a/src/librustc_data_structures/obligation_forest/test.rs b/src/librustc_data_structures/obligation_forest/test.rs index 9a0a4218d4593..4bbbc65f3a6fc 100644 --- a/src/librustc_data_structures/obligation_forest/test.rs +++ b/src/librustc_data_structures/obligation_forest/test.rs @@ -8,7 +8,235 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::{ObligationForest, Outcome, Error}; +use std::cell::RefCell; +use std::collections::BTreeSet; +use std::iter::FromIterator; +use std::rc::Rc; + +use super::{ObligationForest, UndoLog, Outcome, Error}; +use super::super::undoable::{Undoable, UndoableTracker, Undoer}; + + +#[derive(Debug)] +pub struct TestTreeStateUndoer { old_value: usize } +impl Undoer for TestTreeStateUndoer { + type Undoable = TestTreeState; + fn undo(self, state: &mut TestTreeState) { + state.value = self.old_value; + } +} + +#[derive(Debug)] +pub struct TestTreeState { + id: Option, + tracker: Option>>>, + value: usize, +} +impl Undoable for TestTreeState { + type Undoer = TestTreeStateUndoer; + type Tracker = UndoLog<&'static str, TestTreeState>; + fn register_tracker( + &mut self, tracker: Rc>>, id: usize) + { + self.id = Some(id); + self.tracker = Some(tracker); + } +} +impl TestTreeState { + fn new(value: usize) -> TestTreeState { + TestTreeState { id: None, tracker: None, value: value } + } + fn set(&mut self, value: usize) { + let mut tracker = self.tracker.as_ref().unwrap().borrow_mut(); + tracker.push_action(self.id.unwrap(), TestTreeStateUndoer { old_value: self.value }); + self.value = value; + } +} + +#[test] +fn push_snap_push_snap_modify_commit_rollback() { + let mut forest = ObligationForest::new(); + forest.push_tree("A", TestTreeState::new(0)); + forest.push_tree("B", TestTreeState::new(1)); + forest.push_tree("C", TestTreeState::new(2)); + + let snap0 = forest.start_snapshot(); + forest.push_tree("D", TestTreeState::new(3)); + + let snap1 = forest.start_snapshot(); + let Outcome { completed: ok, errors: err, .. } = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A" => { + assert_eq!(tree.value, 0); + tree.set(4); + Ok(Some(vec!["A.1", "A.2", "A.3"])) + }, + "B" => { + assert_eq!(tree.value, 1); + tree.set(5); + Err("B is for broken") + }, + "C" => { + assert_eq!(tree.value, 2); + tree.set(6); + Ok(Some(vec![])) + }, + "D" => { + assert_eq!(tree.value, 3); + Ok(Some(vec!["D.1"])) + } + _ => unreachable!(), + } + }); + assert_eq!(ok, vec!["C"]); + assert_eq!(err, vec![Error { error: "B is for broken", + backtrace: vec!["B"] }]); + + forest.commit_snapshot(snap1); + + let Outcome { completed: ok, errors: err, .. } = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A.1" => { + assert_eq!(tree.value, 4); + Ok(Some(vec![])) + }, + "A.2" => { + assert_eq!(tree.value, 4); + Err("A.2 broke too") + }, + "A.3" => { + assert_eq!(tree.value, 4); + Ok(Some(vec!["A.3.1"])) + }, + "D.1" => { + assert_eq!(tree.value, 3); + Ok(Some(vec![])) + } + _ => unreachable!(), + } + }); + assert_eq!(BTreeSet::from_iter(ok), BTreeSet::from_iter(vec!["A.1", "D.1", "D"])); + assert_eq!(err, vec![Error { error: "A.2 broke too", + backtrace: vec!["A.2", "A"] }]); + + forest.rollback_snapshot(snap0); + + let Outcome { completed: ok, errors: err, .. } = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A" => { + assert_eq!(tree.value, 0); + Ok(Some(vec!["A.1", "A.2", "A.3"])) + }, + "B" => { + assert_eq!(tree.value, 1); + Err("B is for broken") + }, + "C" => { + assert_eq!(tree.value, 2); + Ok(Some(vec![])) + }, + _ => unreachable!(), + } + }); + assert_eq!(ok, vec!["C"]); + assert_eq!(err, vec![Error { error: "B is for broken", + backtrace: vec!["B"] }]); +} + +#[test] +fn push_snap_modify_rollback() { + let mut forest = ObligationForest::new(); + forest.push_tree("A", TestTreeState::new(0)); + forest.push_tree("B", TestTreeState::new(1)); + forest.push_tree("C", TestTreeState::new(2)); + + let snap0 = forest.start_snapshot(); + let Outcome { completed: ok, errors: err, .. } = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A" => { + assert_eq!(tree.value, 0); + tree.set(3); + Ok(Some(vec!["A.1", "A.2", "A.3"])) + }, + "B" => { + assert_eq!(tree.value, 1); + tree.set(4); + Err("B is for broken") + }, + "C" => { + assert_eq!(tree.value, 2); + tree.set(5); + Ok(Some(vec![])) + }, + _ => unreachable!(), + } + }); + assert_eq!(ok, vec!["C"]); + assert_eq!(err, vec![Error { error: "B is for broken", + backtrace: vec!["B"] }]); + + let _ = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A.1" => { + assert_eq!(tree.value, 3); + Ok(None) + }, + "A.2" => { + assert_eq!(tree.value, 3); + Err("A.2 broke too") + }, + "A.3" => { + assert_eq!(tree.value, 3); + Ok(Some(vec!["A.3.1"])) + }, + _ => unreachable!(), + } + }); + + forest.rollback_snapshot(snap0); + + let Outcome { completed: ok, errors: err, .. } = + forest.process_obligations(|obligation, tree, _| { + match *obligation { + "A" => { + assert_eq!(tree.value, 0); + Ok(Some(vec!["A.1", "A.2", "A.3"])) + }, + "B" => { + assert_eq!(tree.value, 1); + Err("B is for broken") + }, + "C" => { + assert_eq!(tree.value, 2); + Ok(Some(vec![])) + }, + _ => unreachable!(), + } + }); + assert_eq!(ok, vec!["C"]); + assert_eq!(err, vec![Error { error: "B is for broken", + backtrace: vec!["B"] }]); +} + +#[derive(Debug)] +pub struct StrUndoer; +impl Undoer for StrUndoer { + type Undoable = &'static str; + fn undo(self, _: &mut &'static str) {} +} + +impl Undoable for &'static str { + type Undoer = StrUndoer; + type Tracker = UndoLog<&'static str, &'static str>; + fn register_tracker(&mut self, _: Rc>, _: usize) { + // noop + } +} #[test] fn push_pop() { @@ -124,7 +352,7 @@ fn success_in_grandchildren() { _ => unreachable!(), } }); - assert_eq!(ok, vec!["A.3", "A.1"]); + assert_eq!(BTreeSet::from_iter(ok), BTreeSet::from_iter(vec!["A.3", "A.1"])); assert!(err.is_empty()); let Outcome { completed: ok, errors: err, .. } = diff --git a/src/librustc_data_structures/undoable.rs b/src/librustc_data_structures/undoable.rs new file mode 100644 index 0000000000000..31156edc10f71 --- /dev/null +++ b/src/librustc_data_structures/undoable.rs @@ -0,0 +1,35 @@ +// 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::cell::RefCell; +use std::fmt::Debug; +use std::rc::Rc; + +/// Tracks undoable actions. +pub trait UndoableTracker { + type Undoable: ?Sized + Undoable; + fn push_action(&mut self, id: usize, undoer: ::Undoer); +} + +/// The 'undo' part of an undoable action that may be tracked by an UndoableTracker. +pub trait Undoer { + type Undoable: ?Sized + Undoable; + fn undo(self, item: &mut Self::Undoable); +} + +/// Type that is contractually obligated to push undoable actions onto any (single) registered +/// tracker. +pub trait Undoable { + type Undoer: Undoer + Debug; + type Tracker: UndoableTracker; + // We use an Rc here due to it being difficult to represent a reference-to-owner safely in the + // type system. + fn register_tracker(&mut self, tracker: Rc>, id: usize); +} From d08e1ac4fd91912552dff818309818a576f4cfe2 Mon Sep 17 00:00:00 2001 From: Masood Malekghassemi Date: Wed, 10 Feb 2016 20:38:50 -0800 Subject: [PATCH 2/2] Remove spurious derive(Clone) --- src/librustc/middle/traits/fulfill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc/middle/traits/fulfill.rs b/src/librustc/middle/traits/fulfill.rs index 71be0dc68580f..c6a14e3a466aa 100644 --- a/src/librustc/middle/traits/fulfill.rs +++ b/src/librustc/middle/traits/fulfill.rs @@ -42,7 +42,7 @@ pub struct GlobalFulfilledPredicates<'tcx> { dep_graph: DepGraph, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct LocalFulfilledPredicates<'tcx> { set: FnvHashSet>, log: Option<(Rc>>, usize)>,