Skip to content

Commit 944723b

Browse files
committed
process cycles as soon as they are detected
We used to wait for the recursion limit, but that might well be too long!
1 parent f92ce2e commit 944723b

File tree

3 files changed

+187
-129
lines changed

3 files changed

+187
-129
lines changed

src/librustc/traits/fulfill.rs

Lines changed: 162 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -320,103 +320,172 @@ impl<'tcx> FulfillmentContext<'tcx> {
320320
fn process_predicate<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
321321
tree_cache: &mut LocalFulfilledPredicates<'tcx>,
322322
pending_obligation: &mut PendingPredicateObligation<'tcx>,
323-
mut backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
323+
backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
324324
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>)
325325
-> Result<Option<Vec<PendingPredicateObligation<'tcx>>>,
326326
FulfillmentErrorCode<'tcx>>
327327
{
328-
match process_predicate1(selcx, pending_obligation, backtrace.clone(), region_obligations) {
329-
Ok(Some(v)) => {
330-
// FIXME(#30977) The code below is designed to detect (and
331-
// permit) DAGs, while still ensuring that the reasoning
332-
// is acyclic. However, it does a few things
333-
// suboptimally. For example, it refreshes type variables
334-
// a lot, probably more than needed, but also less than
335-
// you might want.
336-
//
337-
// - more than needed: I want to be very sure we don't
338-
// accidentally treat a cycle as a DAG, so I am
339-
// refreshing type variables as we walk the ancestors;
340-
// but we are going to repeat this a lot, which is
341-
// sort of silly, and it would be nicer to refresh
342-
// them *in place* so that later predicate processing
343-
// can benefit from the same work;
344-
// - less than you might want: we only add items in the cache here,
345-
// but maybe we learn more about type variables and could add them into
346-
// the cache later on.
347-
348-
let tcx = selcx.tcx();
349-
350-
// Compute a little FnvHashSet for the ancestors. We only
351-
// do this the first time that we care.
352-
let mut cache = None;
353-
let mut is_ancestor = |predicate: &ty::Predicate<'tcx>| {
354-
if cache.is_none() {
355-
let mut c = FnvHashSet();
356-
for ancestor in backtrace.by_ref() {
357-
// Ugh. This just feels ridiculously
358-
// inefficient. But we need to compare
359-
// predicates without being concerned about
360-
// the vagaries of type inference, so for now
361-
// just ensure that they are always
362-
// up-to-date. (I suppose we could just use a
363-
// snapshot and check if they are unifiable?)
364-
let resolved_predicate =
365-
selcx.infcx().resolve_type_vars_if_possible(
366-
&ancestor.obligation.predicate);
367-
c.insert(resolved_predicate);
368-
}
369-
cache = Some(c);
328+
match process_predicate1(selcx, pending_obligation, region_obligations) {
329+
Ok(Some(v)) => process_child_obligations(selcx,
330+
tree_cache,
331+
&pending_obligation.obligation,
332+
backtrace,
333+
v),
334+
Ok(None) => Ok(None),
335+
Err(e) => Err(e)
336+
}
337+
}
338+
339+
fn process_child_obligations<'a,'tcx>(
340+
selcx: &mut SelectionContext<'a,'tcx>,
341+
tree_cache: &mut LocalFulfilledPredicates<'tcx>,
342+
pending_obligation: &PredicateObligation<'tcx>,
343+
backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
344+
child_obligations: Vec<PredicateObligation<'tcx>>)
345+
-> Result<Option<Vec<PendingPredicateObligation<'tcx>>>,
346+
FulfillmentErrorCode<'tcx>>
347+
{
348+
// FIXME(#30977) The code below is designed to detect (and
349+
// permit) DAGs, while still ensuring that the reasoning
350+
// is acyclic. However, it does a few things
351+
// suboptimally. For example, it refreshes type variables
352+
// a lot, probably more than needed, but also less than
353+
// you might want.
354+
//
355+
// - more than needed: I want to be very sure we don't
356+
// accidentally treat a cycle as a DAG, so I am
357+
// refreshing type variables as we walk the ancestors;
358+
// but we are going to repeat this a lot, which is
359+
// sort of silly, and it would be nicer to refresh
360+
// them *in place* so that later predicate processing
361+
// can benefit from the same work;
362+
// - less than you might want: we only add items in the cache here,
363+
// but maybe we learn more about type variables and could add them into
364+
// the cache later on.
365+
366+
let tcx = selcx.tcx();
367+
368+
let mut ancestor_set = AncestorSet::new(&backtrace);
369+
370+
let pending_predicate_obligations: Vec<_> =
371+
child_obligations
372+
.into_iter()
373+
.filter_map(|obligation| {
374+
// Probably silly, but remove any inference
375+
// variables. This is actually crucial to the ancestor
376+
// check marked (*) below, but it's not clear that it
377+
// makes sense to ALWAYS do it.
378+
let obligation = selcx.infcx().resolve_type_vars_if_possible(&obligation);
379+
380+
// Screen out obligations that we know globally
381+
// are true.
382+
if tcx.fulfilled_predicates.borrow().check_duplicate(&obligation.predicate) {
383+
return None;
384+
}
385+
386+
// Check whether this obligation appears
387+
// somewhere else in the tree. If not, we have to
388+
// process it for sure.
389+
if !tree_cache.is_duplicate_or_add(&obligation.predicate) {
390+
return Some(PendingPredicateObligation {
391+
obligation: obligation,
392+
stalled_on: vec![]
393+
});
394+
}
395+
396+
debug!("process_child_obligations: duplicate={:?}",
397+
obligation.predicate);
398+
399+
// OK, the obligation appears elsewhere in the tree.
400+
// This is either a fatal error or else something we can
401+
// ignore. If the obligation appears in our *ancestors*
402+
// (rather than some more distant relative), that
403+
// indicates a cycle. Cycles are either considered
404+
// resolved (if this is a coinductive case) or a fatal
405+
// error.
406+
if let Some(index) = ancestor_set.has(selcx.infcx(), &obligation.predicate) {
407+
// ~~~ (*) see above
408+
debug!("process_child_obligations: cycle index = {}", index);
409+
410+
if coinductive_match(selcx, &obligation, &backtrace) {
411+
debug!("process_child_obligations: coinductive match");
412+
None
413+
} else {
414+
let backtrace = backtrace.clone();
415+
let cycle: Vec<_> =
416+
iter::once(&obligation)
417+
.chain(Some(pending_obligation))
418+
.chain(backtrace.take(index + 1).map(|p| &p.obligation))
419+
.cloned()
420+
.collect();
421+
report_overflow_error_cycle(selcx.infcx(), &cycle);
370422
}
423+
} else {
424+
// Not a cycle. Just ignore this obligation then,
425+
// we're already in the process of proving it.
426+
debug!("process_child_obligations: not a cycle");
427+
None
428+
}
429+
})
430+
.collect();
371431

372-
cache.as_ref().unwrap().contains(predicate)
373-
};
432+
Ok(Some(pending_predicate_obligations))
433+
}
434+
435+
struct AncestorSet<'b, 'tcx: 'b> {
436+
populated: bool,
437+
cache: FnvHashMap<ty::Predicate<'tcx>, usize>,
438+
backtrace: Backtrace<'b, PendingPredicateObligation<'tcx>>,
439+
}
374440

375-
let pending_predicate_obligations: Vec<_> =
376-
v.into_iter()
377-
.filter_map(|obligation| {
378-
// Probably silly, but remove any inference
379-
// variables. This is actually crucial to the
380-
// ancestor check below, but it's not clear that
381-
// it makes sense to ALWAYS do it.
382-
let obligation = selcx.infcx().resolve_type_vars_if_possible(&obligation);
383-
384-
// Screen out obligations that we know globally
385-
// are true. This should really be the DAG check
386-
// mentioned above.
387-
if tcx.fulfilled_predicates.borrow().check_duplicate(&obligation.predicate) {
388-
return None;
389-
}
390-
391-
// Check whether this obligation appears somewhere else in the tree.
392-
if tree_cache.is_duplicate_or_add(&obligation.predicate) {
393-
// If the obligation appears as a parent,
394-
// allow it, because that is a cycle.
395-
// Otherwise though we can just ignore
396-
// it. Note that we have to be careful around
397-
// inference variables here -- for the
398-
// purposes of the ancestor check, we retain
399-
// the invariant that all type variables are
400-
// fully refreshed.
401-
if !is_ancestor(&obligation.predicate) {
402-
return None;
403-
}
404-
}
405-
406-
Some(PendingPredicateObligation {
407-
obligation: obligation,
408-
stalled_on: vec![]
409-
})
410-
})
411-
.collect();
412-
413-
Ok(Some(pending_predicate_obligations))
441+
impl<'b, 'tcx> AncestorSet<'b, 'tcx> {
442+
fn new(backtrace: &Backtrace<'b, PendingPredicateObligation<'tcx>>) -> Self {
443+
AncestorSet {
444+
populated: false,
445+
cache: FnvHashMap(),
446+
backtrace: backtrace.clone(),
414447
}
415-
Ok(None) => Ok(None),
416-
Err(e) => Err(e)
417448
}
418-
}
419449

450+
/// Checks whether any of the ancestors in the backtrace are equal
451+
/// to `predicate` (`predicate` is assumed to be fully
452+
/// type-resolved). Returns `None` if not; otherwise, returns
453+
/// `Some` with the index within the backtrace.
454+
fn has<'a>(&mut self,
455+
infcx: &InferCtxt<'a, 'tcx>,
456+
predicate: &ty::Predicate<'tcx>)
457+
-> Option<usize> {
458+
// the first time, we have to populate the cache
459+
if !self.populated {
460+
let backtrace = self.backtrace.clone();
461+
for (index, ancestor) in backtrace.enumerate() {
462+
// Ugh. This just feels ridiculously
463+
// inefficient. But we need to compare
464+
// predicates without being concerned about
465+
// the vagaries of type inference, so for now
466+
// just ensure that they are always
467+
// up-to-date. (I suppose we could just use a
468+
// snapshot and check if they are unifiable?)
469+
let resolved_predicate =
470+
infcx.resolve_type_vars_if_possible(
471+
&ancestor.obligation.predicate);
472+
473+
// Though we try to avoid it, it can happen that a
474+
// cycle already exists in the predecessors. This
475+
// happens if the type variables were not fully known
476+
// at the time that the ancestors were pushed. We'll
477+
// just ignore such cycles for now, on the premise
478+
// that they will repeat themselves and we'll deal
479+
// with them properly then.
480+
self.cache.entry(resolved_predicate)
481+
.or_insert(index);
482+
}
483+
self.populated = true;
484+
}
485+
486+
self.cache.get(predicate).cloned()
487+
}
488+
}
420489

421490
/// Return the set of type variables contained in a trait ref
422491
fn trait_ref_type_vars<'a, 'tcx>(selcx: &mut SelectionContext<'a, 'tcx>,
@@ -438,7 +507,6 @@ fn trait_ref_type_vars<'a, 'tcx>(selcx: &mut SelectionContext<'a, 'tcx>,
438507
/// - `Err` if the predicate does not hold
439508
fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
440509
pending_obligation: &mut PendingPredicateObligation<'tcx>,
441-
backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
442510
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>)
443511
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
444512
FulfillmentErrorCode<'tcx>>
@@ -461,16 +529,6 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
461529

462530
let obligation = &mut pending_obligation.obligation;
463531

464-
// If we exceed the recursion limit, take a moment to look for a
465-
// cycle so we can give a better error report from here, where we
466-
// have more context.
467-
let recursion_limit = selcx.tcx().sess.recursion_limit.get();
468-
if obligation.recursion_depth >= recursion_limit {
469-
if let Some(cycle) = scan_for_cycle(obligation, &backtrace) {
470-
report_overflow_error_cycle(selcx.infcx(), &cycle);
471-
}
472-
}
473-
474532
if obligation.predicate.has_infer_types() {
475533
obligation.predicate = selcx.infcx().resolve_type_vars_if_possible(&obligation.predicate);
476534
}
@@ -481,10 +539,6 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
481539
return Ok(Some(vec![]));
482540
}
483541

484-
if coinductive_match(selcx, obligation, data, &backtrace) {
485-
return Ok(Some(vec![]));
486-
}
487-
488542
let trait_obligation = obligation.with(data.clone());
489543
match selcx.select(&trait_obligation) {
490544
Ok(Some(vtable)) => {
@@ -616,10 +670,15 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
616670
/// also defaulted traits.
617671
fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
618672
top_obligation: &PredicateObligation<'tcx>,
619-
top_data: &ty::PolyTraitPredicate<'tcx>,
620673
backtrace: &Backtrace<PendingPredicateObligation<'tcx>>)
621674
-> bool
622675
{
676+
// only trait predicates can be coinductive matches
677+
let top_data = match top_obligation.predicate {
678+
ty::Predicate::Trait(ref data) => data,
679+
_ => return false
680+
};
681+
623682
if selcx.tcx().trait_has_default_impl(top_data.def_id()) {
624683
debug!("coinductive_match: top_data={:?}", top_data);
625684
for bt_obligation in backtrace.clone() {
@@ -647,27 +706,6 @@ fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
647706
false
648707
}
649708

650-
fn scan_for_cycle<'a,'tcx>(top_obligation: &PredicateObligation<'tcx>,
651-
backtrace: &Backtrace<PendingPredicateObligation<'tcx>>)
652-
-> Option<Vec<PredicateObligation<'tcx>>>
653-
{
654-
let mut map = FnvHashMap();
655-
let all_obligations =
656-
|| iter::once(top_obligation)
657-
.chain(backtrace.clone()
658-
.map(|p| &p.obligation));
659-
for (index, bt_obligation) in all_obligations().enumerate() {
660-
if let Some(&start) = map.get(&bt_obligation.predicate) {
661-
// Found a cycle starting at position `start` and running
662-
// until the current position (`index`).
663-
return Some(all_obligations().skip(start).take(index - start + 1).cloned().collect());
664-
} else {
665-
map.insert(bt_obligation.predicate.clone(), index);
666-
}
667-
}
668-
None
669-
}
670-
671709
fn register_region_obligation<'tcx>(t_a: Ty<'tcx>,
672710
r_b: ty::Region,
673711
cause: ObligationCause<'tcx>,

src/test/compile-fail/issue-32326.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Regression test for #32326. We ran out of memory because we
12+
// attempted to expand this case up to the recursion limit, and 2^N is
13+
// too big.
14+
15+
enum Expr { //~ ERROR E0072
16+
Plus(Expr, Expr),
17+
Literal(i64),
18+
}
19+
20+
fn main() { }

src/test/compile-fail/sized-cycle-note.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ struct Baz { q: Option<Foo> }
2020

2121
struct Foo { q: Option<Baz> }
2222
//~^ ERROR recursive type `Foo` has infinite size
23-
//~| type `Foo` is embedded within `std::option::Option<Foo>`...
24-
//~| ...which in turn is embedded within `std::option::Option<Foo>`...
25-
//~| ...which in turn is embedded within `Baz`...
26-
//~| ...which in turn is embedded within `std::option::Option<Baz>`...
27-
//~| ...which in turn is embedded within `Foo`, completing the cycle.
23+
//~| NOTE type `Foo` is embedded within `std::option::Option<Foo>`...
24+
//~| NOTE ...which in turn is embedded within `std::option::Option<Foo>`...
25+
//~| NOTE ...which in turn is embedded within `Baz`...
26+
//~| NOTE ...which in turn is embedded within `std::option::Option<Baz>`...
27+
//~| NOTE ...which in turn is embedded within `Foo`, completing the cycle.
2828

2929
impl Foo { fn bar(&self) {} }
3030

0 commit comments

Comments
 (0)