Skip to content

Commit 8d5e845

Browse files
committed
Auto merge of #32582 - nikomatsakis:issue-32326, r=aturon
process cycles as soon as they are detected We used to wait for the recursion limit, but that might well be too long! Fixes #32326 r? @aturon
2 parents 6a3b558 + e733b86 commit 8d5e845

File tree

4 files changed

+236
-153
lines changed

4 files changed

+236
-153
lines changed

src/librustc/traits/fulfill.rs

+179-148
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+
let backtrace = backtrace.clone();
411+
let cycle: Vec<_> =
412+
iter::once(&obligation)
413+
.chain(Some(pending_obligation))
414+
.chain(backtrace.take(index + 1).map(|p| &p.obligation))
415+
.cloned()
416+
.collect();
417+
if coinductive_match(selcx, &cycle) {
418+
debug!("process_child_obligations: coinductive match");
419+
None
420+
} else {
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)) => {
@@ -609,63 +663,40 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
609663

610664
/// For defaulted traits, we use a co-inductive strategy to solve, so
611665
/// that recursion is ok. This routine returns true if the top of the
612-
/// stack (`top_obligation` and `top_data`):
666+
/// stack (`cycle[0]`):
613667
/// - is a defaulted trait, and
614668
/// - it also appears in the backtrace at some position `X`; and,
615669
/// - all the predicates at positions `X..` between `X` an the top are
616670
/// also defaulted traits.
617671
fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
618-
top_obligation: &PredicateObligation<'tcx>,
619-
top_data: &ty::PolyTraitPredicate<'tcx>,
620-
backtrace: &Backtrace<PendingPredicateObligation<'tcx>>)
672+
cycle: &[PredicateObligation<'tcx>])
621673
-> bool
622674
{
623-
if selcx.tcx().trait_has_default_impl(top_data.def_id()) {
624-
debug!("coinductive_match: top_data={:?}", top_data);
625-
for bt_obligation in backtrace.clone() {
626-
debug!("coinductive_match: bt_obligation={:?}", bt_obligation);
627-
628-
// *Everything* in the backtrace must be a defaulted trait.
629-
match bt_obligation.obligation.predicate {
630-
ty::Predicate::Trait(ref data) => {
631-
if !selcx.tcx().trait_has_default_impl(data.def_id()) {
632-
debug!("coinductive_match: trait does not have default impl");
633-
break;
634-
}
635-
}
636-
_ => { break; }
637-
}
638-
639-
// And we must find a recursive match.
640-
if bt_obligation.obligation.predicate == top_obligation.predicate {
641-
debug!("coinductive_match: found a match in the backtrace");
642-
return true;
643-
}
644-
}
645-
}
646-
647-
false
675+
let len = cycle.len();
676+
677+
assert_eq!(cycle[0].predicate, cycle[len - 1].predicate);
678+
679+
cycle[0..len-1]
680+
.iter()
681+
.all(|bt_obligation| {
682+
let result = coinductive_obligation(selcx, bt_obligation);
683+
debug!("coinductive_match: bt_obligation={:?} coinductive={}",
684+
bt_obligation, result);
685+
result
686+
})
648687
}
649688

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);
689+
fn coinductive_obligation<'a, 'tcx>(selcx: &SelectionContext<'a, 'tcx>,
690+
obligation: &PredicateObligation<'tcx>)
691+
-> bool {
692+
match obligation.predicate {
693+
ty::Predicate::Trait(ref data) => {
694+
selcx.tcx().trait_has_default_impl(data.def_id())
695+
}
696+
_ => {
697+
false
666698
}
667699
}
668-
None
669700
}
670701

671702
fn register_region_obligation<'tcx>(t_a: Ty<'tcx>,

0 commit comments

Comments
 (0)