implement the obligation forest data structure and add some unit tests
This commit is contained in:
parent
82c43432e0
commit
0defb158aa
5 changed files with 627 additions and 2 deletions
|
@ -24,17 +24,21 @@
|
||||||
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
|
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
|
||||||
html_root_url = "https://doc.rust-lang.org/nightly/")]
|
html_root_url = "https://doc.rust-lang.org/nightly/")]
|
||||||
|
|
||||||
#![feature(rustc_private, staged_api)]
|
|
||||||
#![feature(hashmap_hasher)]
|
#![feature(hashmap_hasher)]
|
||||||
|
#![feature(nonzero)]
|
||||||
|
#![feature(rustc_private)]
|
||||||
|
#![feature(staged_api)]
|
||||||
|
|
||||||
#![cfg_attr(test, feature(test))]
|
#![cfg_attr(test, feature(test))]
|
||||||
|
|
||||||
|
extern crate core;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
extern crate serialize as rustc_serialize; // used by deriving
|
extern crate serialize as rustc_serialize; // used by deriving
|
||||||
|
|
||||||
pub mod bitvec;
|
pub mod bitvec;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
pub mod ivar;
|
pub mod ivar;
|
||||||
|
pub mod obligation_forest;
|
||||||
pub mod snapshot_vec;
|
pub mod snapshot_vec;
|
||||||
pub mod transitive_relation;
|
pub mod transitive_relation;
|
||||||
pub mod unify;
|
pub mod unify;
|
||||||
|
|
412
src/librustc_data_structures/obligation_forest/mod.rs
Normal file
412
src/librustc_data_structures/obligation_forest/mod.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/librustc_data_structures/obligation_forest/node_index.rs
Normal file
21
src/librustc_data_structures/obligation_forest/node_index.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
188
src/librustc_data_structures/obligation_forest/test.rs
Normal file
188
src/librustc_data_structures/obligation_forest/test.rs
Normal file
|
@ -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());
|
||||||
|
}
|
|
@ -869,7 +869,7 @@ impl LateLintPass for UnconditionalRecursion {
|
||||||
let node_id = tcx.map.as_local_node_id(method.def_id).unwrap();
|
let node_id = tcx.map.as_local_node_id(method.def_id).unwrap();
|
||||||
|
|
||||||
let param_env = ty::ParameterEnvironment::for_item(tcx, node_id);
|
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);
|
let mut selcx = traits::SelectionContext::new(&infcx);
|
||||||
match selcx.select(&obligation) {
|
match selcx.select(&obligation) {
|
||||||
// The method comes from a `T: Trait` bound.
|
// The method comes from a `T: Trait` bound.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue