Skip to content

Commit a7362db

Browse files
authored
Rollup merge of rust-lang#55258 - Aaron1011:fix/rustdoc-blanket, r=GuillaumeGomez
Fix Rustdoc ICE when checking blanket impls Fixes rust-lang#55001, rust-lang#54744 Previously, SelectionContext would unconditionally cache the selection result for an obligation. This worked fine for most users of SelectionContext, but it caused an issue when used by Rustdoc's blanket impl finder. The issue occured when SelectionContext chose a ParamCandidate which contained inference variables. Since inference variables can change between calls to select(), it's not safe to cache the selection result - the chosen candidate might not be applicable for future results, leading to an ICE when we try to run confirmation. This commit prevents SelectionContext from caching any ParamCandidate that contains inference variables. This should always be completely safe, as trait selection should never depend on a particular result being cached. I've also added some extra debug!() statements, which I found helpful in tracking down this bug.
2 parents 9dcdb63 + 4f2624c commit a7362db

File tree

5 files changed

+87
-3
lines changed

5 files changed

+87
-3
lines changed

src/librustc/infer/combine.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
251251
dir: RelationDir)
252252
-> RelateResult<'tcx, Generalization<'tcx>>
253253
{
254+
debug!("generalize(ty={:?}, for_vid={:?}, dir={:?}", ty, for_vid, dir);
254255
// Determine the ambient variance within which `ty` appears.
255256
// The surrounding equation is:
256257
//
@@ -273,8 +274,15 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
273274
root_ty: ty,
274275
};
275276

276-
let ty = generalize.relate(&ty, &ty)?;
277+
let ty = match generalize.relate(&ty, &ty) {
278+
Ok(ty) => ty,
279+
Err(e) => {
280+
debug!("generalize: failure {:?}", e);
281+
return Err(e);
282+
}
283+
};
277284
let needs_wf = generalize.needs_wf;
285+
debug!("generalize: success {{ {:?}, {:?} }}", ty, needs_wf);
278286
Ok(Generalization { ty, needs_wf })
279287
}
280288
}

src/librustc/infer/equate.rs

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ impl<'combine, 'infcx, 'gcx, 'tcx> TypeRelation<'infcx, 'gcx, 'tcx>
7474
let infcx = self.fields.infcx;
7575
let a = infcx.type_variables.borrow_mut().replace_if_possible(a);
7676
let b = infcx.type_variables.borrow_mut().replace_if_possible(b);
77+
78+
debug!("{}.tys: replacements ({:?}, {:?})", self.tag(), a, b);
79+
7780
match (&a.sty, &b.sty) {
7881
(&ty::Infer(TyVar(a_id)), &ty::Infer(TyVar(b_id))) => {
7982
infcx.type_variables.borrow_mut().equate(a_id, b_id);

src/librustc/traits/select.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,33 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
15221522
.map(|v| v.get(tcx))
15231523
}
15241524

1525+
/// Determines whether can we safely cache the result
1526+
/// of selecting an obligation. This is almost always 'true',
1527+
/// except when dealing with certain ParamCandidates.
1528+
///
1529+
/// Ordinarily, a ParamCandidate will contain no inference variables,
1530+
/// since it was usually produced directly from a DefId. However,
1531+
/// certain cases (currently only librustdoc's blanket impl finder),
1532+
/// a ParamEnv may be explicitly constructed with inference types.
1533+
/// When this is the case, we do *not* want to cache the resulting selection
1534+
/// candidate. This is due to the fact that it might not always be possible
1535+
/// to equate the obligation's trait ref and the candidate's trait ref,
1536+
/// if more constraints end up getting added to an inference variable.
1537+
///
1538+
/// Because of this, we always want to re-run the full selection
1539+
/// process for our obligation the next time we see it, since
1540+
/// we might end up picking a different SelectionCandidate (or none at all)
1541+
fn can_cache_candidate(&self,
1542+
result: &SelectionResult<'tcx, SelectionCandidate<'tcx>>
1543+
) -> bool {
1544+
match result {
1545+
Ok(Some(SelectionCandidate::ParamCandidate(trait_ref))) => {
1546+
!trait_ref.skip_binder().input_types().any(|t| t.walk().any(|t_| t_.is_ty_infer()))
1547+
},
1548+
_ => true
1549+
}
1550+
}
1551+
15251552
fn insert_candidate_cache(
15261553
&mut self,
15271554
param_env: ty::ParamEnv<'tcx>,
@@ -1531,6 +1558,14 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
15311558
) {
15321559
let tcx = self.tcx();
15331560
let trait_ref = cache_fresh_trait_pred.skip_binder().trait_ref;
1561+
1562+
if !self.can_cache_candidate(&candidate) {
1563+
debug!("insert_candidate_cache(trait_ref={:?}, candidate={:?} -\
1564+
candidate is not cacheable", trait_ref, candidate);
1565+
return;
1566+
1567+
}
1568+
15341569
if self.can_use_global_caches(param_env) {
15351570
if let Err(Overflow) = candidate {
15361571
// Don't cache overflow globally; we only produce this

src/librustdoc/clean/blanket_impl.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
5050
name: Option<String>,
5151
) -> Vec<Item>
5252
where F: Fn(DefId) -> Def {
53+
debug!("get_blanket_impls(def_id={:?}, ...)", def_id);
5354
let mut impls = Vec::new();
5455
if self.cx
5556
.tcx
@@ -78,6 +79,8 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
7879
}
7980
self.cx.tcx.for_each_relevant_impl(trait_def_id, ty, |impl_def_id| {
8081
self.cx.tcx.infer_ctxt().enter(|infcx| {
82+
debug!("get_blanet_impls: Considering impl for trait '{:?}' {:?}",
83+
trait_def_id, impl_def_id);
8184
let t_generics = infcx.tcx.generics_of(impl_def_id);
8285
let trait_ref = infcx.tcx.impl_trait_ref(impl_def_id)
8386
.expect("Cannot get impl trait");
@@ -104,8 +107,8 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
104107
drop(obligations);
105108

106109
debug!(
107-
"invoking predicate_may_hold: {:?}",
108-
trait_ref,
110+
"invoking predicate_may_hold: param_env={:?}, trait_ref={:?}, ty={:?}",
111+
param_env, trait_ref, ty
109112
);
110113
let may_apply = match infcx.evaluate_obligation(
111114
&traits::Obligation::new(
@@ -117,6 +120,10 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
117120
Ok(eval_result) => eval_result.may_apply(),
118121
Err(traits::OverflowError) => true, // overflow doesn't mean yes *or* no
119122
};
123+
debug!("get_blanket_impls: found applicable impl: {}\
124+
for trait_ref={:?}, ty={:?}",
125+
may_apply, trait_ref, ty);
126+
120127
if !may_apply {
121128
return
122129
}

src/test/rustdoc/issue-55001.rs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Regression test for issue #55001. Previously, we would incorrectly
2+
// cache certain trait selection results when checking for blanket impls,
3+
// resulting in an ICE when we tried to confirm the cached ParamCandidate
4+
// against an obligation.
5+
6+
pub struct DefaultAllocator;
7+
pub struct Standard;
8+
pub struct Inner;
9+
10+
pub trait Rand {}
11+
12+
pub trait Distribution<T> {}
13+
pub trait Allocator<N> {}
14+
15+
impl<T> Rand for T where Standard: Distribution<T> {}
16+
17+
impl<A> Distribution<Point<A>> for Standard
18+
where
19+
DefaultAllocator: Allocator<A>,
20+
Standard: Distribution<A> {}
21+
22+
impl Distribution<Inner> for Standard {}
23+
24+
25+
pub struct Point<N>
26+
where DefaultAllocator: Allocator<N>
27+
{
28+
field: N
29+
}
30+
31+
fn main() {}

0 commit comments

Comments
 (0)