Skip to content

Commit 60c302f

Browse files
committed
Document ObligationForest better.
1 parent 83f2c8c commit 60c302f

File tree

5 files changed

+180
-50
lines changed

5 files changed

+180
-50
lines changed

src/librustc/middle/traits/fulfill.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ impl<'tcx> FulfillmentContext<'tcx> {
281281
debug!("select_where_possible: outcome={:?}", outcome);
282282

283283
// these are obligations that were proven to be true.
284-
for pending_obligation in outcome.successful {
284+
for pending_obligation in outcome.completed {
285285
let predicate = &pending_obligation.obligation.predicate;
286286
if predicate.is_global() {
287287
selcx.tcx().fulfilled_predicates.borrow_mut()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
The `ObligationForest` is a utility data structure used in trait
2+
matching to track the set of outstanding obligations (those not yet
3+
resolved to success or error). It also tracks the "backtrace" of each
4+
pending obligation (why we are trying to figure this out in the first
5+
place).
6+
7+
### External view
8+
9+
`ObligationForest` supports two main public operations (there are a
10+
few others not discussed here):
11+
12+
1. Add a new root obligation (`push_root`).
13+
2. Process the pending obligations (`process_obligations`).
14+
15+
When a new obligation `N` is added, it becomes the root of an
16+
obligation tree. This tree is a singleton to start, so `N` is both the
17+
root and the only leaf. Each time the `process_obligations` method is
18+
called, it will invoke its callback with every pending obligation (so
19+
that will include `N`, the first time). The callback shoud process the
20+
obligation `O` that it is given and return one of three results:
21+
22+
- `Ok(None)` -> ambiguous result. Obligation was neither a success
23+
nor a failure. It is assumed that further attempts to process the
24+
obligation will yield the same result unless something in the
25+
surrounding environment changes.
26+
- `Ok(Some(C))` - the obligation was *shallowly successful*. The
27+
vector `C` is a list of subobligations. The meaning of this is that
28+
`O` was successful on the assumption that all the obligations in `C`
29+
are also successful. Therefore, `O` is only considered a "true"
30+
success if `C` is empty. Otherwise, `O` is put into a suspended
31+
state and the obligations in `C` become the new pending
32+
obligations. They will be processed the next time you call
33+
`process_obligations`.
34+
- `Err(E)` -> obligation failed with error `E`. We will collect this
35+
error and return it from `process_obligations`, along with the
36+
"backtrace" of obligations (that is, the list of obligations up to
37+
and including the root of the failed obligation). No further
38+
obligations from that same tree will be processed, since the tree is
39+
now considered to be in error.
40+
41+
When the call to `process_obligations` completes, you get back an `Outcome`,
42+
which includes three bits of information:
43+
44+
- `completed`: a list of obligations where processing was fully
45+
completed without error (meaning that all transitive subobligations
46+
have also been completed). So, for example, if the callback from
47+
`process_obligations` returns `Ok(Some(C))` for some obligation `O`,
48+
then `O` will be considered completed right away if `C` is the
49+
empty vector. Otherwise it will only be considered completed once
50+
all the obligations in `C` have been found completed.
51+
- `errors`: a list of errors that occurred and associated backtraces
52+
at the time of error, which can be used to give context to the user.
53+
- `stalled`: if true, then none of the existing obligations were
54+
*shallowly successful* (that is, no callback returned `Ok(Some(_))`).
55+
This implies that all obligations were either errors or returned an
56+
ambiguous result, which means that any further calls to
57+
`process_obligations` would simply yield back further ambiguous
58+
results. This is used by the `FulfillmentContext` to decide when it
59+
has reached a steady state.
60+
61+
#### Snapshots
62+
63+
The `ObligationForest` supports a limited form of snapshots; see
64+
`start_snapshot`; `commit_snapshot`; and `rollback_snapshot`. In
65+
particular, you can use a snapshot to roll back new root
66+
obligations. However, it is an error to attempt to
67+
`process_obligations` during a snapshot.
68+
69+
### Implementation details
70+
71+
For the most part, comments specific to the implementation are in the
72+
code. This file only contains a very high-level overview. Basically,
73+
the forest is stored in a vector. Each element of the vector is a node
74+
in some tree. Each node in the vector has the index of an (optional)
75+
parent and (for convenience) its root (which may be itself). It also
76+
has a current state, described by `NodeState`. After each
77+
processing step, we compress the vector to remove completed and error
78+
nodes, which aren't needed anymore.
79+
80+

src/librustc_data_structures/obligation_forest/mod.rs

+85-36
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
//! The `ObligationForest` is a utility data structure used in trait
12+
//! matching to track the set of outstanding obligations (those not
13+
//! yet resolved to success or error). It also tracks the "backtrace"
14+
//! of each pending obligation (why we are trying to figure this out
15+
//! in the first place). See README.md for a general overview of how
16+
//! to use this class.
17+
1118
use std::fmt::Debug;
1219
use std::mem;
1320

@@ -17,6 +24,18 @@ mod node_index;
1724
mod test;
1825

1926
pub struct ObligationForest<O> {
27+
/// The list of obligations. In between calls to
28+
/// `process_obligations`, this list only contains nodes in the
29+
/// `Pending` or `Success` state (with a non-zero number of
30+
/// incomplete children). During processing, some of those nodes
31+
/// may be changed to the error state, or we may find that they
32+
/// are completed (That is, `num_incomplete_children` drops to 0).
33+
/// At the end of processing, those nodes will be removed by a
34+
/// call to `compress`.
35+
///
36+
/// At all times we maintain the invariant that every node appears
37+
/// at a higher index than its parent. This is needed by the
38+
/// backtrace iterator (which uses `split_at`).
2039
nodes: Vec<Node<O>>,
2140
snapshots: Vec<usize>
2241
}
@@ -33,28 +52,44 @@ struct Node<O> {
3352
root: NodeIndex, // points to the root, which may be the current node
3453
}
3554

55+
/// The state of one node in some tree within the forest. This
56+
/// represents the current state of processing for the obligation (of
57+
/// type `O`) associated with this node.
3658
#[derive(Debug)]
3759
enum NodeState<O> {
38-
Leaf { obligation: O },
39-
Success { obligation: O, num_children: usize },
60+
/// Obligation not yet resolved to success or error.
61+
Pending { obligation: O },
62+
63+
/// Obligation resolved to success; `num_incomplete_children`
64+
/// indicates the number of children still in an "incomplete"
65+
/// state. Incomplete means that either the child is still
66+
/// pending, or it has children which are incomplete. (Basically,
67+
/// there is pending work somewhere in the subtree of the child.)
68+
///
69+
/// Once all children have completed, success nodes are removed
70+
/// from the vector by the compression step.
71+
Success { obligation: O, num_incomplete_children: usize },
72+
73+
/// This obligation was resolved to an error. Error nodes are
74+
/// removed from the vector by the compression step.
4075
Error,
4176
}
4277

4378
#[derive(Debug)]
4479
pub struct Outcome<O,E> {
4580
/// Obligations that were completely evaluated, including all
4681
/// (transitive) subobligations.
47-
pub successful: Vec<O>,
82+
pub completed: Vec<O>,
4883

4984
/// Backtrace of obligations that were found to be in error.
5085
pub errors: Vec<Error<O,E>>,
5186

5287
/// If true, then we saw no successful obligations, which means
5388
/// there is no point in further iteration. This is based on the
54-
/// assumption that `Err` and `Ok(None)` results do not affect
55-
/// environmental inference state. (Note that if we invoke
56-
/// `process_obligations` with no pending obligations, stalled
57-
/// will be true.)
89+
/// assumption that when trait matching returns `Err` or
90+
/// `Ok(None)`, those results do not affect environmental
91+
/// inference state. (Note that if we invoke `process_obligations`
92+
/// with no pending obligations, stalled will be true.)
5893
pub stalled: bool,
5994
}
6095

@@ -90,13 +125,15 @@ impl<O: Debug> ObligationForest<O> {
90125
}
91126

92127
pub fn rollback_snapshot(&mut self, snapshot: Snapshot) {
93-
// check that we are obeying stack discipline
128+
// Check that we are obeying stack discipline.
94129
assert_eq!(snapshot.len, self.snapshots.len());
95130
let nodes_len = self.snapshots.pop().unwrap();
96131

97-
// the only action permitted while in a snapshot is to push new roots
132+
// The only action permitted while in a snapshot is to push
133+
// new root obligations. Because no processing will have been
134+
// done, those roots should still be in the pending state.
98135
debug_assert!(self.nodes[nodes_len..].iter().all(|n| match n.state {
99-
NodeState::Leaf { .. } => true,
136+
NodeState::Pending { .. } => true,
100137
_ => false,
101138
}));
102139

@@ -116,12 +153,15 @@ impl<O: Debug> ObligationForest<O> {
116153
}
117154

118155
/// Convert all remaining obligations to the given error.
156+
///
157+
/// This cannot be done during a snapshot.
119158
pub fn to_errors<E:Clone>(&mut self, error: E) -> Vec<Error<O,E>> {
159+
assert!(!self.in_snapshot());
120160
let mut errors = vec![];
121161
for index in 0..self.nodes.len() {
122162
debug_assert!(!self.nodes[index].is_popped());
123163
self.inherit_error(index);
124-
if let NodeState::Leaf { .. } = self.nodes[index].state {
164+
if let NodeState::Pending { .. } = self.nodes[index].state {
125165
let backtrace = self.backtrace(index);
126166
errors.push(Error { error: error.clone(), backtrace: backtrace });
127167
}
@@ -131,11 +171,11 @@ impl<O: Debug> ObligationForest<O> {
131171
errors
132172
}
133173

134-
/// Convert all remaining obligations to the given error.
174+
/// Returns the set of obligations that are in a pending state.
135175
pub fn pending_obligations(&self) -> Vec<O> where O: Clone {
136176
self.nodes.iter()
137177
.filter_map(|n| match n.state {
138-
NodeState::Leaf { ref obligation } => Some(obligation),
178+
NodeState::Pending { ref obligation } => Some(obligation),
139179
_ => None,
140180
})
141181
.cloned()
@@ -174,9 +214,11 @@ impl<O: Debug> ObligationForest<O> {
174214
let (prefix, suffix) = self.nodes.split_at_mut(index);
175215
let backtrace = Backtrace::new(prefix, parent);
176216
match suffix[0].state {
177-
NodeState::Error => continue,
178-
NodeState::Success { .. } => continue,
179-
NodeState::Leaf { ref mut obligation } => action(obligation, backtrace),
217+
NodeState::Error |
218+
NodeState::Success { .. } =>
219+
continue,
220+
NodeState::Pending { ref mut obligation } =>
221+
action(obligation, backtrace),
180222
}
181223
};
182224

@@ -204,7 +246,7 @@ impl<O: Debug> ObligationForest<O> {
204246
debug!("process_obligations: complete");
205247

206248
Outcome {
207-
successful: successful_obligations,
249+
completed: successful_obligations,
208250
errors: errors,
209251
stalled: stalled,
210252
}
@@ -219,9 +261,9 @@ impl<O: Debug> ObligationForest<O> {
219261
fn success(&mut self, index: usize, children: Vec<O>) {
220262
debug!("success(index={}, children={:?})", index, children);
221263

222-
let num_children = children.len();
264+
let num_incomplete_children = children.len();
223265

224-
if num_children == 0 {
266+
if num_incomplete_children == 0 {
225267
// if there is no work left to be done, decrement parent's ref count
226268
self.update_parent(index);
227269
} else {
@@ -233,13 +275,14 @@ impl<O: Debug> ObligationForest<O> {
233275
.map(|o| Node::new(root_index, Some(node_index), o)));
234276
}
235277

236-
// change state from `Leaf` to `Success`, temporarily swapping in `Error`
278+
// change state from `Pending` to `Success`, temporarily swapping in `Error`
237279
let state = mem::replace(&mut self.nodes[index].state, NodeState::Error);
238280
self.nodes[index].state = match state {
239-
NodeState::Leaf { obligation } =>
281+
NodeState::Pending { obligation } =>
240282
NodeState::Success { obligation: obligation,
241-
num_children: num_children },
242-
NodeState::Success { .. } | NodeState::Error =>
283+
num_incomplete_children: num_incomplete_children },
284+
NodeState::Success { .. } |
285+
NodeState::Error =>
243286
unreachable!()
244287
};
245288
}
@@ -251,9 +294,9 @@ impl<O: Debug> ObligationForest<O> {
251294
if let Some(parent) = self.nodes[child].parent {
252295
let parent = parent.get();
253296
match self.nodes[parent].state {
254-
NodeState::Success { ref mut num_children, .. } => {
255-
*num_children -= 1;
256-
if *num_children > 0 {
297+
NodeState::Success { ref mut num_incomplete_children, .. } => {
298+
*num_incomplete_children -= 1;
299+
if *num_incomplete_children > 0 {
257300
return;
258301
}
259302
}
@@ -263,8 +306,10 @@ impl<O: Debug> ObligationForest<O> {
263306
}
264307
}
265308

266-
/// If the root of `child` is in an error error, places `child`
267-
/// into an error state.
309+
/// If the root of `child` is in an error state, places `child`
310+
/// into an error state. This is used during processing so that we
311+
/// skip the remaining obligations from a tree once some other
312+
/// node in the tree is found to be in error.
268313
fn inherit_error(&mut self, child: usize) {
269314
let root = self.nodes[child].root.get();
270315
if let NodeState::Error = self.nodes[root].state {
@@ -274,12 +319,15 @@ impl<O: Debug> ObligationForest<O> {
274319

275320
/// Returns a vector of obligations for `p` and all of its
276321
/// ancestors, putting them into the error state in the process.
322+
/// The fact that the root is now marked as an error is used by
323+
/// `inherit_error` above to propagate the error state to the
324+
/// remainder of the tree.
277325
fn backtrace(&mut self, mut p: usize) -> Vec<O> {
278326
let mut trace = vec![];
279327
loop {
280328
let state = mem::replace(&mut self.nodes[p].state, NodeState::Error);
281329
match state {
282-
NodeState::Leaf { obligation } |
330+
NodeState::Pending { obligation } |
283331
NodeState::Success { obligation, .. } => {
284332
trace.push(obligation);
285333
}
@@ -338,9 +386,9 @@ impl<O: Debug> ObligationForest<O> {
338386
(0 .. dead).map(|_| self.nodes.pop().unwrap())
339387
.flat_map(|node| match node.state {
340388
NodeState::Error => None,
341-
NodeState::Leaf { .. } => unreachable!(),
342-
NodeState::Success { obligation, num_children } => {
343-
assert_eq!(num_children, 0);
389+
NodeState::Pending { .. } => unreachable!(),
390+
NodeState::Success { obligation, num_incomplete_children } => {
391+
assert_eq!(num_incomplete_children, 0);
344392
Some(obligation)
345393
}
346394
})
@@ -365,15 +413,15 @@ impl<O> Node<O> {
365413
fn new(root: NodeIndex, parent: Option<NodeIndex>, obligation: O) -> Node<O> {
366414
Node {
367415
parent: parent,
368-
state: NodeState::Leaf { obligation: obligation },
416+
state: NodeState::Pending { obligation: obligation },
369417
root: root
370418
}
371419
}
372420

373421
fn is_popped(&self) -> bool {
374422
match self.state {
375-
NodeState::Leaf { .. } => false,
376-
NodeState::Success { num_children, .. } => num_children == 0,
423+
NodeState::Pending { .. } => false,
424+
NodeState::Success { num_incomplete_children, .. } => num_incomplete_children == 0,
377425
NodeState::Error => true,
378426
}
379427
}
@@ -399,7 +447,8 @@ impl<'b, O> Iterator for Backtrace<'b, O> {
399447
if let Some(p) = self.pointer {
400448
self.pointer = self.nodes[p.get()].parent;
401449
match self.nodes[p.get()].state {
402-
NodeState::Leaf { ref obligation } | NodeState::Success { ref obligation, .. } => {
450+
NodeState::Pending { ref obligation } |
451+
NodeState::Success { ref obligation, .. } => {
403452
Some(obligation)
404453
}
405454
NodeState::Error => {

0 commit comments

Comments
 (0)