42
42
//! now considered to be in error.
43
43
//!
44
44
//! When the call to `process_obligations` completes, you get back an `Outcome`,
45
- //! which includes three bits of information:
45
+ //! which includes two bits of information:
46
46
//!
47
47
//! - `completed`: a list of obligations where processing was fully
48
48
//! completed without error (meaning that all transitive subobligations
53
53
//! all the obligations in `C` have been found completed.
54
54
//! - `errors`: a list of errors that occurred and associated backtraces
55
55
//! at the time of error, which can be used to give context to the user.
56
- //! - `stalled`: if true, then none of the existing obligations were
57
- //! *shallowly successful* (that is, no callback returned `Changed(_)`).
58
- //! This implies that all obligations were either errors or returned an
59
- //! ambiguous result, which means that any further calls to
60
- //! `process_obligations` would simply yield back further ambiguous
61
- //! results. This is used by the `FulfillmentContext` to decide when it
62
- //! has reached a steady state.
56
+ //!
57
+ //! Upon completion, none of the existing obligations were *shallowly
58
+ //! successful* (that is, no callback returned `Changed(_)`). This implies that
59
+ //! all obligations were either errors or returned an ambiguous result.
63
60
//!
64
61
//! ### Implementation details
65
62
//!
@@ -99,6 +96,8 @@ pub trait ObligationProcessor {
99
96
type Obligation : ForestObligation ;
100
97
type Error : Debug ;
101
98
99
+ fn needs_process_obligation ( & self , obligation : & Self :: Obligation ) -> bool ;
100
+
102
101
fn process_obligation (
103
102
& mut self ,
104
103
obligation : & mut Self :: Obligation ,
@@ -146,7 +145,7 @@ pub struct ObligationForest<O: ForestObligation> {
146
145
147
146
/// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately,
148
147
/// its contents are not guaranteed to match those of `nodes`. See the
149
- /// comments in [ `Self::process_obligation` for details.
148
+ /// comments in `Self::process_obligation` for details.
150
149
active_cache : FxHashMap < O :: CacheKey , usize > ,
151
150
152
151
/// A vector reused in [Self::compress()] and [Self::find_cycles_from_node()],
@@ -260,8 +259,6 @@ pub trait OutcomeTrait {
260
259
type Obligation ;
261
260
262
261
fn new ( ) -> Self ;
263
- fn mark_not_stalled ( & mut self ) ;
264
- fn is_stalled ( & self ) -> bool ;
265
262
fn record_completed ( & mut self , outcome : & Self :: Obligation ) ;
266
263
fn record_error ( & mut self , error : Self :: Error ) ;
267
264
}
@@ -270,30 +267,14 @@ pub trait OutcomeTrait {
270
267
pub struct Outcome < O , E > {
271
268
/// Backtrace of obligations that were found to be in error.
272
269
pub errors : Vec < Error < O , E > > ,
273
-
274
- /// If true, then we saw no successful obligations, which means
275
- /// there is no point in further iteration. This is based on the
276
- /// assumption that when trait matching returns `Error` or
277
- /// `Unchanged`, those results do not affect environmental
278
- /// inference state. (Note that if we invoke `process_obligations`
279
- /// with no pending obligations, stalled will be true.)
280
- pub stalled : bool ,
281
270
}
282
271
283
272
impl < O , E > OutcomeTrait for Outcome < O , E > {
284
273
type Error = Error < O , E > ;
285
274
type Obligation = O ;
286
275
287
276
fn new ( ) -> Self {
288
- Self { stalled : true , errors : vec ! [ ] }
289
- }
290
-
291
- fn mark_not_stalled ( & mut self ) {
292
- self . stalled = false ;
293
- }
294
-
295
- fn is_stalled ( & self ) -> bool {
296
- self . stalled
277
+ Self { errors : vec ! [ ] }
297
278
}
298
279
299
280
fn record_completed ( & mut self , _outcome : & Self :: Obligation ) {
@@ -415,10 +396,7 @@ impl<O: ForestObligation> ObligationForest<O> {
415
396
. insert ( node. obligation . as_cache_key ( ) ) ;
416
397
}
417
398
418
- /// Performs a pass through the obligation list. This must
419
- /// be called in a loop until `outcome.stalled` is false.
420
- ///
421
- /// This _cannot_ be unrolled (presently, at least).
399
+ /// Performs a fixpoint computation over the obligation list.
422
400
#[ inline( never) ]
423
401
pub fn process_obligations < P , OUT > ( & mut self , processor : & mut P ) -> OUT
424
402
where
@@ -427,55 +405,69 @@ impl<O: ForestObligation> ObligationForest<O> {
427
405
{
428
406
let mut outcome = OUT :: new ( ) ;
429
407
430
- // Note that the loop body can append new nodes, and those new nodes
431
- // will then be processed by subsequent iterations of the loop.
432
- //
433
- // We can't use an iterator for the loop because `self.nodes` is
434
- // appended to and the borrow checker would complain. We also can't use
435
- // `for index in 0..self.nodes.len() { ... }` because the range would
436
- // be computed with the initial length, and we would miss the appended
437
- // nodes. Therefore we use a `while` loop.
438
- let mut index = 0 ;
439
- while let Some ( node) = self . nodes . get_mut ( index) {
440
- // `processor.process_obligation` can modify the predicate within
441
- // `node.obligation`, and that predicate is the key used for
442
- // `self.active_cache`. This means that `self.active_cache` can get
443
- // out of sync with `nodes`. It's not very common, but it does
444
- // happen, and code in `compress` has to allow for it.
445
- if node. state . get ( ) != NodeState :: Pending {
446
- index += 1 ;
447
- continue ;
448
- }
449
-
450
- match processor. process_obligation ( & mut node. obligation ) {
451
- ProcessResult :: Unchanged => {
452
- // No change in state.
408
+ // Fixpoint computation: we repeat until the inner loop stalls.
409
+ loop {
410
+ let mut has_changed = false ;
411
+
412
+ // Note that the loop body can append new nodes, and those new nodes
413
+ // will then be processed by subsequent iterations of the loop.
414
+ //
415
+ // We can't use an iterator for the loop because `self.nodes` is
416
+ // appended to and the borrow checker would complain. We also can't use
417
+ // `for index in 0..self.nodes.len() { ... }` because the range would
418
+ // be computed with the initial length, and we would miss the appended
419
+ // nodes. Therefore we use a `while` loop.
420
+ let mut index = 0 ;
421
+ while let Some ( node) = self . nodes . get_mut ( index) {
422
+ if node. state . get ( ) != NodeState :: Pending
423
+ || !processor. needs_process_obligation ( & node. obligation )
424
+ {
425
+ index += 1 ;
426
+ continue ;
453
427
}
454
- ProcessResult :: Changed ( children) => {
455
- // We are not (yet) stalled.
456
- outcome. mark_not_stalled ( ) ;
457
- node. state . set ( NodeState :: Success ) ;
458
-
459
- for child in children {
460
- let st = self . register_obligation_at ( child, Some ( index) ) ;
461
- if let Err ( ( ) ) = st {
462
- // Error already reported - propagate it
463
- // to our node.
464
- self . error_at ( index) ;
428
+
429
+ // `processor.process_obligation` can modify the predicate within
430
+ // `node.obligation`, and that predicate is the key used for
431
+ // `self.active_cache`. This means that `self.active_cache` can get
432
+ // out of sync with `nodes`. It's not very common, but it does
433
+ // happen, and code in `compress` has to allow for it.
434
+
435
+ match processor. process_obligation ( & mut node. obligation ) {
436
+ ProcessResult :: Unchanged => {
437
+ // No change in state.
438
+ }
439
+ ProcessResult :: Changed ( children) => {
440
+ // We are not (yet) stalled.
441
+ has_changed = true ;
442
+ node. state . set ( NodeState :: Success ) ;
443
+
444
+ for child in children {
445
+ let st = self . register_obligation_at ( child, Some ( index) ) ;
446
+ if let Err ( ( ) ) = st {
447
+ // Error already reported - propagate it
448
+ // to our node.
449
+ self . error_at ( index) ;
450
+ }
465
451
}
466
452
}
453
+ ProcessResult :: Error ( err) => {
454
+ has_changed = true ;
455
+ outcome. record_error ( Error { error : err, backtrace : self . error_at ( index) } ) ;
456
+ }
467
457
}
468
- ProcessResult :: Error ( err) => {
469
- outcome. mark_not_stalled ( ) ;
470
- outcome. record_error ( Error { error : err, backtrace : self . error_at ( index) } ) ;
471
- }
458
+ index += 1 ;
459
+ }
460
+
461
+ // If unchanged, then we saw no successful obligations, which means
462
+ // there is no point in further iteration. This is based on the
463
+ // assumption that when trait matching returns `Error` or
464
+ // `Unchanged`, those results do not affect environmental inference
465
+ // state. (Note that this will occur if we invoke
466
+ // `process_obligations` with no pending obligations.)
467
+ if !has_changed {
468
+ break ;
472
469
}
473
- index += 1 ;
474
- }
475
470
476
- // There's no need to perform marking, cycle processing and compression when nothing
477
- // changed.
478
- if !outcome. is_stalled ( ) {
479
471
self . mark_successes ( ) ;
480
472
self . process_cycles ( processor) ;
481
473
self . compress ( |obl| outcome. record_completed ( obl) ) ;
@@ -634,17 +626,14 @@ impl<O: ForestObligation> ObligationForest<O> {
634
626
}
635
627
}
636
628
NodeState :: Done => {
637
- // This lookup can fail because the contents of
629
+ // The removal lookup might fail because the contents of
638
630
// `self.active_cache` are not guaranteed to match those of
639
631
// `self.nodes`. See the comment in `process_obligation`
640
632
// for more details.
641
- if let Some ( ( predicate, _) ) =
642
- self . active_cache . remove_entry ( & node. obligation . as_cache_key ( ) )
643
- {
644
- self . done_cache . insert ( predicate) ;
645
- } else {
646
- self . done_cache . insert ( node. obligation . as_cache_key ( ) . clone ( ) ) ;
647
- }
633
+ let cache_key = node. obligation . as_cache_key ( ) ;
634
+ self . active_cache . remove ( & cache_key) ;
635
+ self . done_cache . insert ( cache_key) ;
636
+
648
637
// Extract the success stories.
649
638
outcome_cb ( & node. obligation ) ;
650
639
node_rewrites[ index] = orig_nodes_len;
0 commit comments