Skip to content

Commit e783d2b

Browse files
committed
Auto merge of #54199 - nikomatsakis:predicate_may_hold-failure, r=eddyb
overlook overflows in rustdoc trait solving Context: The new rustdoc "auto trait" feature walks across impls and tries to run trait solving on them with a lot of unconstrained variables. This is prone to overflows. These overflows used to cause an ICE because of a caching bug (fixed in this PR). But even once that is fixed, it means that rustdoc causes an overflow rather than generating docs. This PR therefore adds a new helper that propagates the overflow error out. This requires rustdoc to then decide what to do when it encounters such an overflow: technically, an overflow represents neither "yes" nor "no", but rather a failure to make a decision. I've decided to opt on the side of treating this as "yes, implemented", since rustdoc already takes an optimistic view. This may prove to include too many items, but I *suspect* not. We could probably reduce the rate of overflows by unifying more of the parameters from the impl -- right now we only seem to consider the self type. Moreover, in the future, as we transition to Chalk, overflow errors are expected to just "go away" (in some cases, though, queries might return an ambiguous result). Fixes #52873 cc @QuietMisdreavus -- this is the stuff we were talking about earlier cc @GuillaumeGomez -- this supersedes #53687
2 parents c3a1a0d + a3997f7 commit e783d2b

File tree

5 files changed

+214
-15
lines changed

5 files changed

+214
-15
lines changed

src/librustc/traits/query/evaluate_obligation.rs

+24-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
2020
&self,
2121
obligation: &PredicateObligation<'tcx>,
2222
) -> bool {
23-
self.evaluate_obligation(obligation).may_apply()
23+
self.evaluate_obligation_no_overflow(obligation).may_apply()
2424
}
2525

2626
/// Evaluates whether the predicate can be satisfied in the given
@@ -30,28 +30,44 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
3030
&self,
3131
obligation: &PredicateObligation<'tcx>,
3232
) -> bool {
33-
self.evaluate_obligation(obligation) == EvaluationResult::EvaluatedToOk
33+
self.evaluate_obligation_no_overflow(obligation) == EvaluationResult::EvaluatedToOk
3434
}
3535

36-
// Helper function that canonicalizes and runs the query, as well as handles
37-
// overflow.
38-
fn evaluate_obligation(
36+
/// Evaluate a given predicate, capturing overflow and propagating it back.
37+
pub fn evaluate_obligation(
3938
&self,
4039
obligation: &PredicateObligation<'tcx>,
41-
) -> EvaluationResult {
40+
) -> Result<EvaluationResult, OverflowError> {
4241
let mut _orig_values = SmallVec::new();
4342
let c_pred = self.canonicalize_query(&obligation.param_env.and(obligation.predicate),
4443
&mut _orig_values);
4544
// Run canonical query. If overflow occurs, rerun from scratch but this time
4645
// in standard trait query mode so that overflow is handled appropriately
4746
// within `SelectionContext`.
48-
match self.tcx.global_tcx().evaluate_obligation(c_pred) {
47+
self.tcx.global_tcx().evaluate_obligation(c_pred)
48+
}
49+
50+
// Helper function that canonicalizes and runs the query. If an
51+
// overflow results, we re-run it in the local context so we can
52+
// report a nice error.
53+
fn evaluate_obligation_no_overflow(
54+
&self,
55+
obligation: &PredicateObligation<'tcx>,
56+
) -> EvaluationResult {
57+
match self.evaluate_obligation(obligation) {
4958
Ok(result) => result,
5059
Err(OverflowError) => {
5160
let mut selcx =
5261
SelectionContext::with_query_mode(&self, TraitQueryMode::Standard);
5362
selcx.evaluate_obligation_recursively(obligation)
54-
.expect("Overflow should be caught earlier in standard query mode")
63+
.unwrap_or_else(|r| {
64+
span_bug!(
65+
obligation.cause.span,
66+
"Overflow should be caught earlier in standard query mode: {:?}, {:?}",
67+
obligation,
68+
r,
69+
)
70+
})
5571
}
5672
}
5773
}

src/librustc/traits/select.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1374,7 +1374,10 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
13741374
let tcx = self.tcx();
13751375
let trait_ref = cache_fresh_trait_pred.skip_binder().trait_ref;
13761376
if self.can_use_global_caches(param_env) {
1377-
if let Some(trait_ref) = tcx.lift_to_global(&trait_ref) {
1377+
if let Err(Overflow) = candidate {
1378+
// Don't cache overflow globally; we only produce this
1379+
// in certain modes.
1380+
} else if let Some(trait_ref) = tcx.lift_to_global(&trait_ref) {
13781381
if let Some(candidate) = tcx.lift_to_global(&candidate) {
13791382
debug!(
13801383
"insert_candidate_cache(trait_ref={:?}, candidate={:?}) global",

src/librustc/traits/structural_impls.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::SelectionError<'a> {
175175
super::ConstEvalFailure(ref err) => tcx.lift(&**err).map(|err| super::ConstEvalFailure(
176176
err.into(),
177177
)),
178-
super::Overflow => bug!(), // FIXME: ape ConstEvalFailure?
178+
super::Overflow => Some(super::Overflow),
179179
}
180180
}
181181
}

src/librustdoc/clean/blanket_impl.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,20 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
103103
// FIXME(eddyb) ignoring `obligations` might cause false positives.
104104
drop(obligations);
105105

106-
let may_apply = infcx.predicate_may_hold(&traits::Obligation::new(
107-
cause.clone(),
108-
param_env,
109-
trait_ref.to_predicate(),
110-
));
106+
debug!(
107+
"invoking predicate_may_hold: {:?}",
108+
trait_ref,
109+
);
110+
let may_apply = match infcx.evaluate_obligation(
111+
&traits::Obligation::new(
112+
cause.clone(),
113+
param_env,
114+
trait_ref.to_predicate(),
115+
),
116+
) {
117+
Ok(eval_result) => eval_result.may_apply(),
118+
Err(traits::OverflowError) => true, // overflow doesn't mean yes *or* no
119+
};
111120
if !may_apply {
112121
return
113122
}

src/test/rustdoc/issue-52873.rs

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Regression test for #52873. We used to ICE due to unexpected
2+
// overflows when checking for "blanket impl inclusion".
3+
4+
use std::marker::PhantomData;
5+
use std::cmp::Ordering;
6+
use std::ops::{Add, Mul};
7+
8+
pub type True = B1;
9+
pub type False = B0;
10+
pub type U0 = UTerm;
11+
pub type U1 = UInt<UTerm, B1>;
12+
13+
pub trait NonZero {}
14+
15+
pub trait Bit {
16+
}
17+
18+
pub trait Unsigned {
19+
}
20+
21+
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Default)]
22+
pub struct B0;
23+
24+
impl B0 {
25+
#[inline]
26+
pub fn new() -> B0 {
27+
B0
28+
}
29+
}
30+
31+
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Default)]
32+
pub struct B1;
33+
34+
impl B1 {
35+
#[inline]
36+
pub fn new() -> B1 {
37+
B1
38+
}
39+
}
40+
41+
impl Bit for B0 {
42+
}
43+
44+
impl Bit for B1 {
45+
}
46+
47+
impl NonZero for B1 {}
48+
49+
pub trait PrivatePow<Y, N> {
50+
type Output;
51+
}
52+
pub type PrivatePowOut<A, Y, N> = <A as PrivatePow<Y, N>>::Output;
53+
54+
pub type Add1<A> = <A as Add<::B1>>::Output;
55+
pub type Prod<A, B> = <A as Mul<B>>::Output;
56+
pub type Square<A> = <A as Mul>::Output;
57+
pub type Sum<A, B> = <A as Add<B>>::Output;
58+
59+
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Default)]
60+
pub struct UTerm;
61+
62+
impl UTerm {
63+
#[inline]
64+
pub fn new() -> UTerm {
65+
UTerm
66+
}
67+
}
68+
69+
impl Unsigned for UTerm {
70+
}
71+
72+
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Default)]
73+
pub struct UInt<U, B> {
74+
_marker: PhantomData<(U, B)>,
75+
}
76+
77+
impl<U: Unsigned, B: Bit> UInt<U, B> {
78+
#[inline]
79+
pub fn new() -> UInt<U, B> {
80+
UInt {
81+
_marker: PhantomData,
82+
}
83+
}
84+
}
85+
86+
impl<U: Unsigned, B: Bit> Unsigned for UInt<U, B> {
87+
}
88+
89+
impl<U: Unsigned, B: Bit> NonZero for UInt<U, B> {}
90+
91+
impl Add<B0> for UTerm {
92+
type Output = UTerm;
93+
fn add(self, _: B0) -> Self::Output {
94+
UTerm
95+
}
96+
}
97+
98+
impl<U: Unsigned, B: Bit> Add<B0> for UInt<U, B> {
99+
type Output = UInt<U, B>;
100+
fn add(self, _: B0) -> Self::Output {
101+
UInt::new()
102+
}
103+
}
104+
105+
impl<U: Unsigned> Add<U> for UTerm {
106+
type Output = U;
107+
fn add(self, _: U) -> Self::Output {
108+
unsafe { ::std::mem::uninitialized() }
109+
}
110+
}
111+
112+
impl<U: Unsigned, B: Bit> Mul<B0> for UInt<U, B> {
113+
type Output = UTerm;
114+
fn mul(self, _: B0) -> Self::Output {
115+
UTerm
116+
}
117+
}
118+
119+
impl<U: Unsigned, B: Bit> Mul<B1> for UInt<U, B> {
120+
type Output = UInt<U, B>;
121+
fn mul(self, _: B1) -> Self::Output {
122+
UInt::new()
123+
}
124+
}
125+
126+
impl<U: Unsigned> Mul<U> for UTerm {
127+
type Output = UTerm;
128+
fn mul(self, _: U) -> Self::Output {
129+
UTerm
130+
}
131+
}
132+
133+
impl<Ul: Unsigned, B: Bit, Ur: Unsigned> Mul<UInt<Ur, B>> for UInt<Ul, B0>
134+
where
135+
Ul: Mul<UInt<Ur, B>>,
136+
{
137+
type Output = UInt<Prod<Ul, UInt<Ur, B>>, B0>;
138+
fn mul(self, _: UInt<Ur, B>) -> Self::Output {
139+
unsafe { ::std::mem::uninitialized() }
140+
}
141+
}
142+
143+
pub trait Pow<Exp> {
144+
type Output;
145+
}
146+
147+
impl<X: Unsigned, N: Unsigned> Pow<N> for X
148+
where
149+
X: PrivatePow<U1, N>,
150+
{
151+
type Output = PrivatePowOut<X, U1, N>;
152+
}
153+
154+
impl<Y: Unsigned, X: Unsigned> PrivatePow<Y, U0> for X {
155+
type Output = Y;
156+
}
157+
158+
impl<Y: Unsigned, X: Unsigned> PrivatePow<Y, U1> for X
159+
where
160+
X: Mul<Y>,
161+
{
162+
type Output = Prod<X, Y>;
163+
}
164+
165+
impl<Y: Unsigned, U: Unsigned, B: Bit, X: Unsigned> PrivatePow<Y, UInt<UInt<U, B>, B0>> for X
166+
where
167+
X: Mul,
168+
Square<X>: PrivatePow<Y, UInt<U, B>>,
169+
{
170+
type Output = PrivatePowOut<Square<X>, Y, UInt<U, B>>;
171+
}

0 commit comments

Comments
 (0)