mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-23 07:14:28 +00:00
Fix Rustdoc ICE when checking blanket impls
Fixes #55001, #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.
This commit is contained in:
parent
ca2639e82e
commit
4f2624cac9
@ -251,6 +251,7 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
|
||||
dir: RelationDir)
|
||||
-> RelateResult<'tcx, Generalization<'tcx>>
|
||||
{
|
||||
debug!("generalize(ty={:?}, for_vid={:?}, dir={:?}", ty, for_vid, dir);
|
||||
// Determine the ambient variance within which `ty` appears.
|
||||
// The surrounding equation is:
|
||||
//
|
||||
@ -273,8 +274,15 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
|
||||
root_ty: ty,
|
||||
};
|
||||
|
||||
let ty = generalize.relate(&ty, &ty)?;
|
||||
let ty = match generalize.relate(&ty, &ty) {
|
||||
Ok(ty) => ty,
|
||||
Err(e) => {
|
||||
debug!("generalize: failure {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let needs_wf = generalize.needs_wf;
|
||||
debug!("generalize: success {{ {:?}, {:?} }}", ty, needs_wf);
|
||||
Ok(Generalization { ty, needs_wf })
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ impl<'combine, 'infcx, 'gcx, 'tcx> TypeRelation<'infcx, 'gcx, 'tcx>
|
||||
let infcx = self.fields.infcx;
|
||||
let a = infcx.type_variables.borrow_mut().replace_if_possible(a);
|
||||
let b = infcx.type_variables.borrow_mut().replace_if_possible(b);
|
||||
|
||||
debug!("{}.tys: replacements ({:?}, {:?})", self.tag(), a, b);
|
||||
|
||||
match (&a.sty, &b.sty) {
|
||||
(&ty::Infer(TyVar(a_id)), &ty::Infer(TyVar(b_id))) => {
|
||||
infcx.type_variables.borrow_mut().equate(a_id, b_id);
|
||||
|
@ -1522,6 +1522,33 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||
.map(|v| v.get(tcx))
|
||||
}
|
||||
|
||||
/// Determines whether can we safely cache the result
|
||||
/// of selecting an obligation. This is almost always 'true',
|
||||
/// except when dealing with certain ParamCandidates.
|
||||
///
|
||||
/// Ordinarily, a ParamCandidate will contain no inference variables,
|
||||
/// since it was usually produced directly from a DefId. However,
|
||||
/// certain cases (currently only librustdoc's blanket impl finder),
|
||||
/// a ParamEnv may be explicitly constructed with inference types.
|
||||
/// When this is the case, we do *not* want to cache the resulting selection
|
||||
/// candidate. This is due to the fact that it might not always be possible
|
||||
/// to equate the obligation's trait ref and the candidate's trait ref,
|
||||
/// if more constraints end up getting added to an inference variable.
|
||||
///
|
||||
/// Because of this, we always want to re-run the full selection
|
||||
/// process for our obligation the next time we see it, since
|
||||
/// we might end up picking a different SelectionCandidate (or none at all)
|
||||
fn can_cache_candidate(&self,
|
||||
result: &SelectionResult<'tcx, SelectionCandidate<'tcx>>
|
||||
) -> bool {
|
||||
match result {
|
||||
Ok(Some(SelectionCandidate::ParamCandidate(trait_ref))) => {
|
||||
!trait_ref.skip_binder().input_types().any(|t| t.walk().any(|t_| t_.is_ty_infer()))
|
||||
},
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_candidate_cache(
|
||||
&mut self,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
@ -1531,6 +1558,14 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||
) {
|
||||
let tcx = self.tcx();
|
||||
let trait_ref = cache_fresh_trait_pred.skip_binder().trait_ref;
|
||||
|
||||
if !self.can_cache_candidate(&candidate) {
|
||||
debug!("insert_candidate_cache(trait_ref={:?}, candidate={:?} -\
|
||||
candidate is not cacheable", trait_ref, candidate);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if self.can_use_global_caches(param_env) {
|
||||
if let Err(Overflow) = candidate {
|
||||
// Don't cache overflow globally; we only produce this
|
||||
|
@ -50,6 +50,7 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
|
||||
name: Option<String>,
|
||||
) -> Vec<Item>
|
||||
where F: Fn(DefId) -> Def {
|
||||
debug!("get_blanket_impls(def_id={:?}, ...)", def_id);
|
||||
let mut impls = Vec::new();
|
||||
if self.cx
|
||||
.tcx
|
||||
@ -78,6 +79,8 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
|
||||
}
|
||||
self.cx.tcx.for_each_relevant_impl(trait_def_id, ty, |impl_def_id| {
|
||||
self.cx.tcx.infer_ctxt().enter(|infcx| {
|
||||
debug!("get_blanet_impls: Considering impl for trait '{:?}' {:?}",
|
||||
trait_def_id, impl_def_id);
|
||||
let t_generics = infcx.tcx.generics_of(impl_def_id);
|
||||
let trait_ref = infcx.tcx.impl_trait_ref(impl_def_id)
|
||||
.expect("Cannot get impl trait");
|
||||
@ -104,8 +107,8 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
|
||||
drop(obligations);
|
||||
|
||||
debug!(
|
||||
"invoking predicate_may_hold: {:?}",
|
||||
trait_ref,
|
||||
"invoking predicate_may_hold: param_env={:?}, trait_ref={:?}, ty={:?}",
|
||||
param_env, trait_ref, ty
|
||||
);
|
||||
let may_apply = match infcx.evaluate_obligation(
|
||||
&traits::Obligation::new(
|
||||
@ -117,6 +120,10 @@ impl<'a, 'tcx, 'rcx, 'cstore> BlanketImplFinder <'a, 'tcx, 'rcx, 'cstore> {
|
||||
Ok(eval_result) => eval_result.may_apply(),
|
||||
Err(traits::OverflowError) => true, // overflow doesn't mean yes *or* no
|
||||
};
|
||||
debug!("get_blanket_impls: found applicable impl: {}\
|
||||
for trait_ref={:?}, ty={:?}",
|
||||
may_apply, trait_ref, ty);
|
||||
|
||||
if !may_apply {
|
||||
return
|
||||
}
|
||||
|
31
src/test/rustdoc/issue-55001.rs
Normal file
31
src/test/rustdoc/issue-55001.rs
Normal file
@ -0,0 +1,31 @@
|
||||
// Regression test for issue #55001. Previously, we would incorrectly
|
||||
// cache certain trait selection results when checking for blanket impls,
|
||||
// resulting in an ICE when we tried to confirm the cached ParamCandidate
|
||||
// against an obligation.
|
||||
|
||||
pub struct DefaultAllocator;
|
||||
pub struct Standard;
|
||||
pub struct Inner;
|
||||
|
||||
pub trait Rand {}
|
||||
|
||||
pub trait Distribution<T> {}
|
||||
pub trait Allocator<N> {}
|
||||
|
||||
impl<T> Rand for T where Standard: Distribution<T> {}
|
||||
|
||||
impl<A> Distribution<Point<A>> for Standard
|
||||
where
|
||||
DefaultAllocator: Allocator<A>,
|
||||
Standard: Distribution<A> {}
|
||||
|
||||
impl Distribution<Inner> for Standard {}
|
||||
|
||||
|
||||
pub struct Point<N>
|
||||
where DefaultAllocator: Allocator<N>
|
||||
{
|
||||
field: N
|
||||
}
|
||||
|
||||
fn main() {}
|
Loading…
Reference in New Issue
Block a user