Skip to content

Commit 476ea9e

Browse files
committed
Always try to project predicates when finding auto traits in rustdoc
Fixes #60726 Previous, AutoTraitFinder would only try to project predicates when the predicate type contained an inference variable. When finding auto traits, we only project to try to unify inference variables - we don't otherwise learn any new information about the required bounds. However, this lead to failing to properly generate a negative auto trait impl (indicating that a type never implements a certain auto trait) in the following unusual scenario: In almost all cases, a type has an (implicit) negative impl of an auto trait due some other type having an explicit *negative* impl of that auto trait. For example: struct MyType<T> { field: *const T } has an implicit 'impl<T> !Send for MyType<T>', due to the explicit negative impl (in libcore) 'impl<T: ?Sized> !Send for *const T'. However, as exposed by the 'abi_stable' crate, this isn't always the case. This minimzed example shows how a type can never implement 'Send', due to a projection error: ``` pub struct True; pub struct False; pub trait MyTrait { type Project; } pub struct MyStruct<T> { field: T } impl MyTrait for u8 { type Project = False; } unsafe impl<T> Send for MyStruct<T> where T: MyTrait<Project=True> {} pub struct Wrapper { inner: MyStruct<u8> } ``` In this example, `<u8 as MyTrait>::Project == True' must hold for 'MyStruct<u8>: Send' to hold. However, '<u8 as MyTrait>::Project == False' holds instead To properly account for this unusual case, we need to call 'poly_project_and_unify' on *all* predicates, not just those with inference variables. This ensures that we catch the projection error that occurs above, and don't incorrectly determine that 'Wrapper: Send' holds.
1 parent af39a1f commit 476ea9e

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

src/librustc/traits/auto_trait.rs

+68-17
Original file line numberDiff line numberDiff line change
@@ -700,22 +700,64 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
700700
}
701701
}
702702

703-
// We can only call poly_project_and_unify_type when our predicate's
704-
// Ty contains an inference variable - otherwise, there won't be anything to
705-
// unify
706-
if p.ty().skip_binder().has_infer_types() {
707-
debug!("Projecting and unifying projection predicate {:?}",
708-
predicate);
709-
match poly_project_and_unify_type(select, &obligation.with(p)) {
710-
Err(e) => {
711-
debug!(
712-
"evaluate_nested_obligations: Unable to unify predicate \
713-
'{:?}' '{:?}', bailing out",
714-
ty, e
715-
);
716-
return false;
717-
}
718-
Ok(Some(v)) => {
703+
// There are three possible cases when we project a predicate:
704+
//
705+
// 1. We encounter an error. This means that it's impossible for
706+
// our current type to implement the auto trait - there's bound
707+
// that we could add to our ParamEnv that would 'fix' this kind
708+
// of error, as it's not caused by an unimplemented type.
709+
//
710+
// 2. We succesfully project the predicate (Ok(Some(_))), generating
711+
// some subobligations. We then process these subobligations
712+
// like any other generated sub-obligations.
713+
//
714+
// 3. We receieve an 'ambiguous' result (Ok(None))
715+
// If we were actually trying to compile a crate,
716+
// we would need to re-process this obligation later.
717+
// However, all we care about is finding out what bounds
718+
// are needed for our type to implement a particular auto trait.
719+
// We've already added this obligation to our computed ParamEnv
720+
// above (if it was necessary). Therefore, we don't need
721+
// to do any further processing of the obligation.
722+
//
723+
// Note that we *must* try to project *all* projection predicates
724+
// we encounter, even ones without inference variable.
725+
// This ensures that we detect any projection errors,
726+
// which indicate that our type can *never* implement the given
727+
// auto trait. In that case, we will generate an explicit negative
728+
// impl (e.g. 'impl !Send for MyType'). However, we don't
729+
// try to process any of the generated subobligations -
730+
// they contain no new information, since we already know
731+
// that our type implements the projected-through trait,
732+
// and can lead to weird region issues.
733+
//
734+
// Normally, we'll generate a negative impl as a result of encountering
735+
// a type with an explicit negative impl of an auto trait
736+
// (for example, raw pointers have !Send and !Sync impls)
737+
// However, through some **interesting** manipulations of the type
738+
// system, it's actually possible to write a type that never
739+
// implements an auto trait due to a projection error, not a normal
740+
// negative impl error. To properly handle this case, we need
741+
// to ensure that we catch any potential projection errors,
742+
// and turn them into an explicit negative impl for our type.
743+
debug!("Projecting and unifying projection predicate {:?}",
744+
predicate);
745+
746+
match poly_project_and_unify_type(select, &obligation.with(p)) {
747+
Err(e) => {
748+
debug!(
749+
"evaluate_nested_obligations: Unable to unify predicate \
750+
'{:?}' '{:?}', bailing out",
751+
ty, e
752+
);
753+
return false;
754+
}
755+
Ok(Some(v)) => {
756+
// We only care about sub-obligations
757+
// when we started out trying to unify
758+
// some inference variables. See the comment above
759+
// for more infomration
760+
if p.ty().skip_binder().has_infer_types() {
719761
if !self.evaluate_nested_obligations(
720762
ty,
721763
v.clone().iter().cloned(),
@@ -728,7 +770,16 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
728770
return false;
729771
}
730772
}
731-
Ok(None) => {
773+
}
774+
Ok(None) => {
775+
// It's ok not to make progress when hvave no inference variables -
776+
// in that case, we were only performing unifcation to check if an
777+
// error occured (which would indicate that it's impossible for our
778+
// type to implement the auto trait).
779+
// However, we should always make progress (either by generating
780+
// subobligations or getting an error) when we started off with
781+
// inference variables
782+
if p.ty().skip_binder().has_infer_types() {
732783
panic!("Unexpected result when selecting {:?} {:?}", ty, obligation)
733784
}
734785
}

src/test/rustdoc/issue-60726.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::marker::PhantomData;
2+
3+
pub struct True;
4+
pub struct False;
5+
6+
pub trait InterfaceType{
7+
type Send;
8+
}
9+
10+
11+
pub struct FooInterface<T>(PhantomData<fn()->T>);
12+
13+
impl<T> InterfaceType for FooInterface<T> {
14+
type Send=False;
15+
}
16+
17+
18+
pub struct DynTrait<I>{
19+
_interface:PhantomData<fn()->I>,
20+
_unsync_unsend:PhantomData<::std::rc::Rc<()>>,
21+
}
22+
23+
unsafe impl<I> Send for DynTrait<I>
24+
where
25+
I:InterfaceType<Send=True>
26+
{}
27+
28+
// @has issue_60726/struct.IntoIter.html
29+
// @has - '//*[@id="synthetic-implementations-list"]/*[@class="impl"]//code' "impl<T> !Send for \
30+
// IntoIter<T>"
31+
// @has - '//*[@id="synthetic-implementations-list"]/*[@class="impl"]//code' "impl<T> !Sync for \
32+
// IntoIter<T>"
33+
pub struct IntoIter<T>{
34+
hello:DynTrait<FooInterface<T>>,
35+
}

0 commit comments

Comments
 (0)