Rollup merge of #102037 - jyn514:normalize-docs, r=lcnr

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: https://github.com/rust-lang/rust/pull/91430#issuecomment-983997013

> 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.

Helps with https://github.com/rust-lang/rust/issues/81091.

r? ```@lcnr``` cc ```@matthewjasper```
This commit is contained in:
Dylan DPC 2022-09-22 18:25:53 +05:30 committed by GitHub
commit d5ae6737bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 58 additions and 30 deletions

View File

@ -95,6 +95,10 @@ pub trait ForestObligation: Clone + Debug {
pub trait ObligationProcessor { pub trait ObligationProcessor {
type Obligation: ForestObligation; type Obligation: ForestObligation;
type Error: Debug; type Error: Debug;
type OUT: OutcomeTrait<
Obligation = Self::Obligation,
Error = Error<Self::Obligation, Self::Error>,
>;
fn needs_process_obligation(&self, obligation: &Self::Obligation) -> bool; fn needs_process_obligation(&self, obligation: &Self::Obligation) -> bool;
@ -111,7 +115,11 @@ pub trait ObligationProcessor {
/// In other words, if we had O1 which required O2 which required /// In other words, if we had O1 which required O2 which required
/// O3 which required O1, we would give an iterator yielding O1, /// O3 which required O1, we would give an iterator yielding O1,
/// O2, O3 (O1 is not yielded twice). /// O2, O3 (O1 is not yielded twice).
fn process_backedge<'c, I>(&mut self, cycle: I, _marker: PhantomData<&'c Self::Obligation>) fn process_backedge<'c, I>(
&mut self,
cycle: I,
_marker: PhantomData<&'c Self::Obligation>,
) -> Result<(), Self::Error>
where where
I: Clone + Iterator<Item = &'c Self::Obligation>; I: Clone + Iterator<Item = &'c Self::Obligation>;
} }
@ -402,12 +410,11 @@ impl<O: ForestObligation> ObligationForest<O> {
/// Performs a fixpoint computation over the obligation list. /// Performs a fixpoint computation over the obligation list.
#[inline(never)] #[inline(never)]
pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT pub fn process_obligations<P>(&mut self, processor: &mut P) -> P::OUT
where where
P: ObligationProcessor<Obligation = O>, P: ObligationProcessor<Obligation = O>,
OUT: OutcomeTrait<Obligation = O, Error = Error<O, P::Error>>,
{ {
let mut outcome = OUT::new(); let mut outcome = P::OUT::new();
// Fixpoint computation: we repeat until the inner loop stalls. // Fixpoint computation: we repeat until the inner loop stalls.
loop { loop {
@ -473,7 +480,7 @@ impl<O: ForestObligation> ObligationForest<O> {
} }
self.mark_successes(); self.mark_successes();
self.process_cycles(processor); self.process_cycles(processor, &mut outcome);
self.compress(|obl| outcome.record_completed(obl)); self.compress(|obl| outcome.record_completed(obl));
} }
@ -558,7 +565,7 @@ impl<O: ForestObligation> ObligationForest<O> {
/// Report cycles between all `Success` nodes, and convert all `Success` /// Report cycles between all `Success` nodes, and convert all `Success`
/// nodes to `Done`. This must be called after `mark_successes`. /// nodes to `Done`. This must be called after `mark_successes`.
fn process_cycles<P>(&mut self, processor: &mut P) fn process_cycles<P>(&mut self, processor: &mut P, outcome: &mut P::OUT)
where where
P: ObligationProcessor<Obligation = O>, P: ObligationProcessor<Obligation = O>,
{ {
@ -568,7 +575,7 @@ impl<O: ForestObligation> ObligationForest<O> {
// to handle the no-op cases immediately to avoid the cost of the // to handle the no-op cases immediately to avoid the cost of the
// function call. // function call.
if node.state.get() == NodeState::Success { if node.state.get() == NodeState::Success {
self.find_cycles_from_node(&mut stack, processor, index); self.find_cycles_from_node(&mut stack, processor, index, outcome);
} }
} }
@ -576,8 +583,13 @@ impl<O: ForestObligation> ObligationForest<O> {
self.reused_node_vec = stack; self.reused_node_vec = stack;
} }
fn find_cycles_from_node<P>(&self, stack: &mut Vec<usize>, processor: &mut P, index: usize) fn find_cycles_from_node<P>(
where &self,
stack: &mut Vec<usize>,
processor: &mut P,
index: usize,
outcome: &mut P::OUT,
) where
P: ObligationProcessor<Obligation = O>, P: ObligationProcessor<Obligation = O>,
{ {
let node = &self.nodes[index]; let node = &self.nodes[index];
@ -586,17 +598,20 @@ impl<O: ForestObligation> ObligationForest<O> {
None => { None => {
stack.push(index); stack.push(index);
for &dep_index in node.dependents.iter() { for &dep_index in node.dependents.iter() {
self.find_cycles_from_node(stack, processor, dep_index); self.find_cycles_from_node(stack, processor, dep_index, outcome);
} }
stack.pop(); stack.pop();
node.state.set(NodeState::Done); node.state.set(NodeState::Done);
} }
Some(rpos) => { Some(rpos) => {
// Cycle detected. // Cycle detected.
processor.process_backedge( let result = processor.process_backedge(
stack[rpos..].iter().map(|&i| &self.nodes[i].obligation), stack[rpos..].iter().map(|&i| &self.nodes[i].obligation),
PhantomData, PhantomData,
); );
if let Err(err) = result {
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
}
} }
} }
} }

View File

@ -64,6 +64,7 @@ where
{ {
type Obligation = O; type Obligation = O;
type Error = E; type Error = E;
type OUT = TestOutcome<O, E>;
fn needs_process_obligation(&self, _obligation: &Self::Obligation) -> bool { fn needs_process_obligation(&self, _obligation: &Self::Obligation) -> bool {
true true
@ -76,10 +77,15 @@ where
(self.process_obligation)(obligation) (self.process_obligation)(obligation)
} }
fn process_backedge<'c, I>(&mut self, _cycle: I, _marker: PhantomData<&'c Self::Obligation>) fn process_backedge<'c, I>(
&mut self,
_cycle: I,
_marker: PhantomData<&'c Self::Obligation>,
) -> Result<(), Self::Error>
where where
I: Clone + Iterator<Item = &'c Self::Obligation>, I: Clone + Iterator<Item = &'c Self::Obligation>,
{ {
Ok(())
} }
} }

View File

@ -113,6 +113,8 @@ pub struct FulfillmentError<'tcx> {
#[derive(Clone)] #[derive(Clone)]
pub enum FulfillmentErrorCode<'tcx> { pub enum FulfillmentErrorCode<'tcx> {
/// Inherently impossible to fulfill; this trait is implemented if and only if it is already implemented.
CodeCycle(Vec<Obligation<'tcx, ty::Predicate<'tcx>>>),
CodeSelectionError(SelectionError<'tcx>), CodeSelectionError(SelectionError<'tcx>),
CodeProjectionError(MismatchedProjectionTypes<'tcx>), CodeProjectionError(MismatchedProjectionTypes<'tcx>),
CodeSubtypeError(ExpectedFound<Ty<'tcx>>, TypeError<'tcx>), // always comes from a SubtypePredicate CodeSubtypeError(ExpectedFound<Ty<'tcx>>, TypeError<'tcx>), // always comes from a SubtypePredicate

View File

@ -47,6 +47,7 @@ impl<'tcx> fmt::Debug for traits::FulfillmentErrorCode<'tcx> {
write!(f, "CodeConstEquateError({:?}, {:?})", a, b) write!(f, "CodeConstEquateError({:?}, {:?})", a, b)
} }
super::CodeAmbiguity => write!(f, "Ambiguity"), super::CodeAmbiguity => write!(f, "Ambiguity"),
super::CodeCycle(ref cycle) => write!(f, "Cycle({:?})", cycle),
} }
} }
} }

View File

@ -4,10 +4,12 @@
// general routines. // general routines.
use crate::infer::{DefiningAnchor, TyCtxtInferExt}; use crate::infer::{DefiningAnchor, TyCtxtInferExt};
use crate::traits::error_reporting::InferCtxtExt;
use crate::traits::{ use crate::traits::{
ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt, ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt,
Unimplemented, Unimplemented,
}; };
use rustc_infer::traits::FulfillmentErrorCode;
use rustc_middle::traits::CodegenObligationError; use rustc_middle::traits::CodegenObligationError;
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, TyCtxt};
@ -62,6 +64,14 @@ pub fn codegen_select_candidate<'tcx>(
// optimization to stop iterating early. // optimization to stop iterating early.
let errors = fulfill_cx.select_all_or_error(&infcx); let errors = fulfill_cx.select_all_or_error(&infcx);
if !errors.is_empty() { if !errors.is_empty() {
// `rustc_monomorphize::collector` assumes there are no type errors.
// Cycle errors are the only post-monomorphization errors possible; emit them now so
// `rustc_ty_utils::resolve_associated_item` doesn't return `None` post-monomorphization.
for err in errors {
if let FulfillmentErrorCode::CodeCycle(cycle) = err.code {
infcx.report_overflow_error_cycle(&cycle);
}
}
return Err(CodegenObligationError::FulfillmentError); return Err(CodegenObligationError::FulfillmentError);
} }

View File

@ -1540,6 +1540,9 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
} }
diag.emit(); diag.emit();
} }
FulfillmentErrorCode::CodeCycle(ref cycle) => {
self.report_overflow_error_cycle(cycle);
}
} }
} }

View File

@ -25,10 +25,9 @@ use super::Unimplemented;
use super::{FulfillmentError, FulfillmentErrorCode}; use super::{FulfillmentError, FulfillmentErrorCode};
use super::{ObligationCause, PredicateObligation}; use super::{ObligationCause, PredicateObligation};
use crate::traits::error_reporting::InferCtxtExt as _;
use crate::traits::project::PolyProjectionObligation; use crate::traits::project::PolyProjectionObligation;
use crate::traits::project::ProjectionCacheKeyExt as _; use crate::traits::project::ProjectionCacheKeyExt as _;
use crate::traits::query::evaluate_obligation::InferCtxtExt as _; use crate::traits::query::evaluate_obligation::InferCtxtExt;
impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> { impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> {
/// Note that we include both the `ParamEnv` and the `Predicate`, /// Note that we include both the `ParamEnv` and the `Predicate`,
@ -224,6 +223,7 @@ fn mk_pending(os: Vec<PredicateObligation<'_>>) -> Vec<PendingPredicateObligatio
impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> { impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
type Obligation = PendingPredicateObligation<'tcx>; type Obligation = PendingPredicateObligation<'tcx>;
type Error = FulfillmentErrorCode<'tcx>; type Error = FulfillmentErrorCode<'tcx>;
type OUT = Outcome<Self::Obligation, Self::Error>;
/// Identifies whether a predicate obligation needs processing. /// Identifies whether a predicate obligation needs processing.
/// ///
@ -594,14 +594,16 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
&mut self, &mut self,
cycle: I, cycle: I,
_marker: PhantomData<&'c PendingPredicateObligation<'tcx>>, _marker: PhantomData<&'c PendingPredicateObligation<'tcx>>,
) where ) -> Result<(), FulfillmentErrorCode<'tcx>>
where
I: Clone + Iterator<Item = &'c PendingPredicateObligation<'tcx>>, I: Clone + Iterator<Item = &'c PendingPredicateObligation<'tcx>>,
{ {
if self.selcx.coinductive_match(cycle.clone().map(|s| s.obligation.predicate)) { if self.selcx.coinductive_match(cycle.clone().map(|s| s.obligation.predicate)) {
debug!("process_child_obligations: coinductive match"); debug!("process_child_obligations: coinductive match");
Ok(())
} else { } else {
let cycle: Vec<_> = cycle.map(|c| c.obligation.clone()).collect(); let cycle: Vec<_> = cycle.map(|c| c.obligation.clone()).collect();
self.selcx.infcx().report_overflow_error_cycle(&cycle); Err(FulfillmentErrorCode::CodeCycle(cycle))
} }
} }
} }

View File

@ -226,13 +226,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
} }
pub fn intercrate(infcx: &'cx InferCtxt<'cx, 'tcx>) -> SelectionContext<'cx, 'tcx> { pub fn intercrate(infcx: &'cx InferCtxt<'cx, 'tcx>) -> SelectionContext<'cx, 'tcx> {
SelectionContext { SelectionContext { intercrate: true, ..SelectionContext::new(infcx) }
infcx,
freshener: infcx.freshener_keep_static(),
intercrate: true,
intercrate_ambiguity_causes: None,
query_mode: TraitQueryMode::Standard,
}
} }
pub fn with_query_mode( pub fn with_query_mode(
@ -240,13 +234,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
query_mode: TraitQueryMode, query_mode: TraitQueryMode,
) -> SelectionContext<'cx, 'tcx> { ) -> SelectionContext<'cx, 'tcx> {
debug!(?query_mode, "with_query_mode"); debug!(?query_mode, "with_query_mode");
SelectionContext { SelectionContext { query_mode, ..SelectionContext::new(infcx) }
infcx,
freshener: infcx.freshener_keep_static(),
intercrate: false,
intercrate_ambiguity_causes: None,
query_mode,
}
} }
/// Enables tracking of intercrate ambiguity causes. See /// Enables tracking of intercrate ambiguity causes. See

View File

@ -1,4 +1,5 @@
// check-pass // check-pass
// compile-flags: -Znormalize-docs
// Regression test for <https://github.com/rust-lang/rust/issues/79459>. // Regression test for <https://github.com/rust-lang/rust/issues/79459>.
pub trait Query {} pub trait Query {}