Skip to content

Commit 1512ce5

Browse files
committed
Make cycle errors recoverable
In particular, this allows rustdoc to recover from cycle errors when normalizing associated types for documentation. In the past, `@jackh726` has said we need to be careful about overflow errors: > Off the top of my head, we definitely should be careful about treating overflow errors the same as "not implemented for some reason" errors. Otherwise, you could end up with behavior that is different depending on recursion depth. But, that might be context-dependent. But cycle errors should be safe to unconditionally report; they don't depend on the recursion depth, they will always be an error whenever they're encountered.
1 parent 749dec6 commit 1512ce5

File tree

8 files changed

+50
-16
lines changed

8 files changed

+50
-16
lines changed

compiler/rustc_data_structures/src/obligation_forest/mod.rs

+22-11
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ pub trait ObligationProcessor {
115115
/// In other words, if we had O1 which required O2 which required
116116
/// O3 which required O1, we would give an iterator yielding O1,
117117
/// O2, O3 (O1 is not yielded twice).
118-
fn process_backedge<'c, I>(&mut self, cycle: I, _marker: PhantomData<&'c Self::Obligation>)
118+
fn process_backedge<'c, I>(
119+
&mut self,
120+
cycle: I,
121+
_marker: PhantomData<&'c Self::Obligation>,
122+
) -> Result<(), Self::Error>
119123
where
120124
I: Clone + Iterator<Item = &'c Self::Obligation>;
121125
}
@@ -406,12 +410,11 @@ impl<O: ForestObligation> ObligationForest<O> {
406410

407411
/// Performs a fixpoint computation over the obligation list.
408412
#[inline(never)]
409-
pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT
413+
pub fn process_obligations<P>(&mut self, processor: &mut P) -> P::OUT
410414
where
411415
P: ObligationProcessor<Obligation = O>,
412-
OUT: OutcomeTrait<Obligation = O, Error = Error<O, P::Error>>,
413416
{
414-
let mut outcome = OUT::new();
417+
let mut outcome = P::OUT::new();
415418

416419
// Fixpoint computation: we repeat until the inner loop stalls.
417420
loop {
@@ -477,7 +480,7 @@ impl<O: ForestObligation> ObligationForest<O> {
477480
}
478481

479482
self.mark_successes();
480-
self.process_cycles(processor);
483+
self.process_cycles(processor, &mut outcome);
481484
self.compress(|obl| outcome.record_completed(obl));
482485
}
483486

@@ -562,7 +565,7 @@ impl<O: ForestObligation> ObligationForest<O> {
562565

563566
/// Report cycles between all `Success` nodes, and convert all `Success`
564567
/// nodes to `Done`. This must be called after `mark_successes`.
565-
fn process_cycles<P>(&mut self, processor: &mut P)
568+
fn process_cycles<P>(&mut self, processor: &mut P, outcome: &mut P::OUT)
566569
where
567570
P: ObligationProcessor<Obligation = O>,
568571
{
@@ -572,16 +575,21 @@ impl<O: ForestObligation> ObligationForest<O> {
572575
// to handle the no-op cases immediately to avoid the cost of the
573576
// function call.
574577
if node.state.get() == NodeState::Success {
575-
self.find_cycles_from_node(&mut stack, processor, index);
578+
self.find_cycles_from_node(&mut stack, processor, index, outcome);
576579
}
577580
}
578581

579582
debug_assert!(stack.is_empty());
580583
self.reused_node_vec = stack;
581584
}
582585

583-
fn find_cycles_from_node<P>(&self, stack: &mut Vec<usize>, processor: &mut P, index: usize)
584-
where
586+
fn find_cycles_from_node<P>(
587+
&self,
588+
stack: &mut Vec<usize>,
589+
processor: &mut P,
590+
index: usize,
591+
outcome: &mut P::OUT,
592+
) where
585593
P: ObligationProcessor<Obligation = O>,
586594
{
587595
let node = &self.nodes[index];
@@ -590,17 +598,20 @@ impl<O: ForestObligation> ObligationForest<O> {
590598
None => {
591599
stack.push(index);
592600
for &dep_index in node.dependents.iter() {
593-
self.find_cycles_from_node(stack, processor, dep_index);
601+
self.find_cycles_from_node(stack, processor, dep_index, outcome);
594602
}
595603
stack.pop();
596604
node.state.set(NodeState::Done);
597605
}
598606
Some(rpos) => {
599607
// Cycle detected.
600-
processor.process_backedge(
608+
let result = processor.process_backedge(
601609
stack[rpos..].iter().map(|&i| &self.nodes[i].obligation),
602610
PhantomData,
603611
);
612+
if let Err(err) = result {
613+
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
614+
}
604615
}
605616
}
606617
}

compiler/rustc_data_structures/src/obligation_forest/tests.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,15 @@ where
7777
(self.process_obligation)(obligation)
7878
}
7979

80-
fn process_backedge<'c, I>(&mut self, _cycle: I, _marker: PhantomData<&'c Self::Obligation>)
80+
fn process_backedge<'c, I>(
81+
&mut self,
82+
_cycle: I,
83+
_marker: PhantomData<&'c Self::Obligation>,
84+
) -> Result<(), Self::Error>
8185
where
8286
I: Clone + Iterator<Item = &'c Self::Obligation>,
8387
{
88+
Ok(())
8489
}
8590
}
8691

compiler/rustc_infer/src/traits/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub struct FulfillmentError<'tcx> {
105105

106106
#[derive(Clone)]
107107
pub enum FulfillmentErrorCode<'tcx> {
108+
/// Inherently impossible to fulfill; this trait is implemented if and only if it is already implemented.
109+
CodeCycle(Vec<Obligation<'tcx, ty::Predicate<'tcx>>>),
108110
CodeSelectionError(SelectionError<'tcx>),
109111
CodeProjectionError(MismatchedProjectionTypes<'tcx>),
110112
CodeSubtypeError(ExpectedFound<Ty<'tcx>>, TypeError<'tcx>), // always comes from a SubtypePredicate

compiler/rustc_infer/src/traits/structural_impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ impl<'tcx> fmt::Debug for traits::FulfillmentErrorCode<'tcx> {
4747
write!(f, "CodeConstEquateError({:?}, {:?})", a, b)
4848
}
4949
super::CodeAmbiguity => write!(f, "Ambiguity"),
50+
super::CodeCycle(ref cycle) => write!(f, "Cycle({:?})", cycle),
5051
}
5152
}
5253
}

compiler/rustc_trait_selection/src/traits/codegen.rs

+10
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
// general routines.
55

66
use crate::infer::{DefiningAnchor, TyCtxtInferExt};
7+
use crate::traits::error_reporting::InferCtxtExt;
78
use crate::traits::{
89
ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt,
910
Unimplemented,
1011
};
12+
use rustc_infer::traits::FulfillmentErrorCode;
1113
use rustc_middle::traits::CodegenObligationError;
1214
use rustc_middle::ty::{self, TyCtxt};
1315

@@ -62,6 +64,14 @@ pub fn codegen_select_candidate<'tcx>(
6264
// optimization to stop iterating early.
6365
let errors = fulfill_cx.select_all_or_error(&infcx);
6466
if !errors.is_empty() {
67+
// `rustc_monomorphize::collector` assumes there are no type errors.
68+
// Cycle errors are the only post-monomorphization errors possible; emit them now so
69+
// `rustc_ty_utils::resolve_associated_item` doesn't return `None` post-monomorphization.
70+
for err in errors {
71+
if let FulfillmentErrorCode::CodeCycle(cycle) = err.code {
72+
infcx.report_overflow_error_cycle(&cycle);
73+
}
74+
}
6575
return Err(CodegenObligationError::FulfillmentError);
6676
}
6777

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,9 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
15401540
}
15411541
diag.emit();
15421542
}
1543+
FulfillmentErrorCode::CodeCycle(ref cycle) => {
1544+
self.report_overflow_error_cycle(cycle);
1545+
}
15431546
}
15441547
}
15451548

compiler/rustc_trait_selection/src/traits/fulfill.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ use super::Unimplemented;
2525
use super::{FulfillmentError, FulfillmentErrorCode};
2626
use super::{ObligationCause, PredicateObligation};
2727

28-
use crate::traits::error_reporting::InferCtxtExt as _;
2928
use crate::traits::project::PolyProjectionObligation;
3029
use crate::traits::project::ProjectionCacheKeyExt as _;
31-
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
30+
use crate::traits::query::evaluate_obligation::InferCtxtExt;
3231

3332
impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> {
3433
/// Note that we include both the `ParamEnv` and the `Predicate`,
@@ -603,14 +602,16 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
603602
&mut self,
604603
cycle: I,
605604
_marker: PhantomData<&'c PendingPredicateObligation<'tcx>>,
606-
) where
605+
) -> Result<(), FulfillmentErrorCode<'tcx>>
606+
where
607607
I: Clone + Iterator<Item = &'c PendingPredicateObligation<'tcx>>,
608608
{
609609
if self.selcx.coinductive_match(cycle.clone().map(|s| s.obligation.predicate)) {
610610
debug!("process_child_obligations: coinductive match");
611+
Ok(())
611612
} else {
612613
let cycle: Vec<_> = cycle.map(|c| c.obligation.clone()).collect();
613-
self.selcx.infcx().report_overflow_error_cycle(&cycle);
614+
Err(FulfillmentErrorCode::CodeCycle(cycle))
614615
}
615616
}
616617
}

src/test/rustdoc-ui/normalize-cycle.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// check-pass
2+
// compile-flags: -Znormalize-docs
23
// Regression test for <https://github.com/rust-lang/rust/issues/79459>.
34
pub trait Query {}
45

0 commit comments

Comments
 (0)