-
-
Notifications
You must be signed in to change notification settings - Fork 15.4k
Require coherence of supertrait associated item bounds for dyn-compability #158915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,17 @@ | |
|
|
||
| use std::ops::ControlFlow; | ||
|
|
||
| use itertools::Itertools; | ||
| use rustc_data_structures::fx::FxHashMap; | ||
| use rustc_errors::FatalError; | ||
| use rustc_hir::def_id::DefId; | ||
| use rustc_hir::{self as hir, LangItem}; | ||
| use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes}; | ||
| use rustc_middle::query::Providers; | ||
| use rustc_middle::ty::{ | ||
| self, EarlyBinder, GenericArgs, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, | ||
| TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Unnormalized, | ||
| Upcast, elaborate, | ||
| self, Clause, EarlyBinder, GenericArgs, PolyProjectionPredicate, Ty, TyCtxt, TypeFoldable, | ||
| TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, | ||
| TypeVisitor, TypingMode, Unnormalized, Upcast, elaborate, | ||
| }; | ||
| use rustc_span::{DUMMY_SP, Span}; | ||
| use smallvec::SmallVec; | ||
|
|
@@ -54,7 +57,8 @@ fn dyn_compatibility_violations( | |
| debug!("dyn_compatibility_violations: {:?}", trait_def_id); | ||
| tcx.arena.alloc_from_iter( | ||
| elaborate::supertrait_def_ids(tcx, trait_def_id) | ||
| .flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id)), | ||
| .flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id)) | ||
| .chain(incoherent_supertrait_assocs(tcx, trait_def_id)), | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -978,6 +982,91 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IllegalRpititVisitor<'tcx> { | |
| } | ||
| } | ||
|
|
||
| /// Computes [`DynCompatibilityViolation::IncoherentSupertraitAssocs`] | ||
| #[instrument(level = "debug", skip(tcx))] | ||
| fn incoherent_supertrait_assocs( | ||
| tcx: TyCtxt<'_>, | ||
| trait_def_id: DefId, | ||
| ) -> impl Iterator<Item = DynCompatibilityViolation> { | ||
| let predicates = tcx | ||
| .predicates_of(trait_def_id) | ||
| .instantiate_identity(tcx) | ||
| .predicates | ||
| .into_iter() | ||
| .map(Unnormalized::skip_normalization); | ||
| // Map from associated items to projection predicates that apply to them. | ||
| let mut preds_for_assoc = FxHashMap::<DefId, Vec<PolyProjectionPredicate<'_>>>::default(); | ||
| elaborate(tcx, predicates).filter_map(Clause::as_projection_clause).flat_map(move |proj| { | ||
| let prev_projs = preds_for_assoc.entry(proj.item_def_id()).or_default(); | ||
| let violations: Vec<_> = prev_projs | ||
| .iter() | ||
| .copied() | ||
| .filter(move |&prev_proj| { | ||
| !does_pair_have_coherent_supertrait_assocs(tcx, trait_def_id, prev_proj, proj) | ||
| }) | ||
| .map(move |_| { | ||
| DynCompatibilityViolation::IncoherentSupertraitAssocs( | ||
| tcx.item_name(proj.item_def_id()), | ||
| tcx.def_ident_span(proj.item_def_id()) | ||
| .expect("Associated items should have a def_ident_span"), | ||
| ) | ||
| }) | ||
| .collect(); | ||
| prev_projs.push(proj); | ||
| violations | ||
| }) | ||
| } | ||
|
|
||
| #[instrument(level = "debug", skip(tcx), ret)] | ||
| fn does_pair_have_coherent_supertrait_assocs<'tcx>( | ||
| tcx: TyCtxt<'tcx>, | ||
| trait_def_id: DefId, | ||
| proj_1: PolyProjectionPredicate<'tcx>, | ||
| proj_2: PolyProjectionPredicate<'tcx>, | ||
| ) -> bool { | ||
| // We syntactically compare the two terms. If they're equal, then | ||
| // they do not conflict with each other. | ||
| // FIXME: This is an overly strict definition of equality. Could this be done better? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we check for equality in the as in, what we prove here is and we under-approximate by which we impl as so to make this check more permissive, you could check the where-clauses of the trait in the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's a nested obligation? What's a nested region? What's "the param_env of the trait"? What does it mean to "check" the where clauses of a trait? |
||
| if proj_1.term() == proj_2.term() { | ||
| return true; | ||
| } | ||
|
|
||
| let infcx = tcx | ||
| .infer_ctxt() | ||
| .with_next_trait_solver(tcx.next_trait_solver_in_coherence()) | ||
| .build(TypingMode::Coherence); | ||
| // We instantiate type parameters in the two projections with the same | ||
| // fresh inference variables. | ||
| let trait_args = infcx.fresh_args_for_item(DUMMY_SP, trait_def_id); | ||
| let process_proj = |proj: PolyProjectionPredicate<'tcx>| { | ||
| let instantiated_proj = EarlyBinder::bind(tcx, proj).instantiate(tcx, trait_args); | ||
| infcx.instantiate_binder_with_fresh_vars( | ||
| DUMMY_SP, | ||
| BoundRegionConversionTime::AssocTypeProjection(proj.item_def_id()), | ||
| // FIXME: Normalizing here could maybe make more code compile? | ||
| instantiated_proj.skip_normalization(), | ||
| ) | ||
| }; | ||
| let proj_1 = process_proj(proj_1); | ||
| let proj_2 = process_proj(proj_2); | ||
| assert_eq!( | ||
| proj_1.projection_term.kind, proj_2.projection_term.kind, | ||
| "should compare the same projection kind" | ||
| ); | ||
| proj_1.projection_term.args.iter().zip_eq(proj_2.projection_term.args).any(|(arg_1, arg_2)| { | ||
| // Note that we discard any obligations we get here. | ||
| // We don't care about proving them. | ||
| // | ||
| // If this call returns an Err, then the two sets of generic args | ||
| // can't possibly be instantiated with the same concrete types. | ||
| // So, we return true from the function | ||
|
Comment on lines
+1057
to
+1062
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could do the proper coherence thing and actually use an makes slightly more compile
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, but I don't understand what you mean. What is an |
||
| infcx | ||
| .at(&ObligationCause::dummy(), tcx.param_env(trait_def_id)) | ||
| .eq(DefineOpaqueTypes::Yes, arg_1, arg_2) | ||
| .is_err() | ||
| }) | ||
| } | ||
|
|
||
| pub(crate) fn provide(providers: &mut Providers) { | ||
| *providers = Providers { | ||
| dyn_compatibility_violations, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| //@ run-pass | ||
|
|
||
| // This is a legitimate use case, where the `dyn Sub` trait object ends up | ||
| // having two different "values" for `Assoc`. This is allowed because we know | ||
| // that the two values are for `Super<i32>` and `Super<i64>`, which can't | ||
| // possibly be the same trait. | ||
|
|
||
| trait Super<T> { | ||
| type Assoc; | ||
| } | ||
|
|
||
| trait Sub: Super<i32, Assoc = u32> + Super<i64, Assoc = u64> { | ||
| fn method(&self) {} | ||
| } | ||
|
|
||
| fn foo(x: &dyn Sub) { | ||
| x.method(); | ||
| } | ||
|
|
||
| struct Thing; | ||
| impl Super<i32> for Thing { | ||
| type Assoc = u32; | ||
| } | ||
| impl Super<i64> for Thing { | ||
| type Assoc = u64; | ||
| } | ||
| impl Sub for Thing {} | ||
|
|
||
| fn main() { | ||
| foo(&Thing); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // We currently accept conflicting associated type bounds with different generics, | ||
| // which results in an ICE, since those generics can be instantiated with the | ||
| // same concrete type. | ||
| // See https://github.com/rust-lang/rust/issues/154662 | ||
|
|
||
| trait Super<T> { | ||
| type Assoc; | ||
| } | ||
|
|
||
| trait Sub<T, U>: Super<T, Assoc = u32> + Super<U, Assoc = u64> { | ||
| fn method(&self) {} | ||
| } | ||
|
|
||
| fn foo<T, U>(x: Option<&dyn Sub<T, U>>) { | ||
| //~^ ERROR the trait `Sub` is not dyn compatible | ||
| if false { | ||
| x.unwrap().method(); | ||
| //~^ ERROR the trait `Sub` is not dyn compatible | ||
| } | ||
| } | ||
|
|
||
| fn main() { | ||
| // This ends up proving that `dyn Sub<i16, i16>` implements `Super<i16>`. | ||
| // However, `dyn Sub<i16, i16>` has bounds for both `Assoc = u32` and `Assoc = u64`, | ||
| // which is nonsense. | ||
| foo::<i16, i16>(None); | ||
| //~^ ERROR the trait `Sub` is not dyn compatible | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| error[E0038]: the trait `Sub` is not dyn compatible | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:14:25 | ||
| | | ||
| LL | fn foo<T, U>(x: Option<&dyn Sub<T, U>>) { | ||
| | ^^^^^^^^^^^^^ `Sub` is not dyn compatible | ||
| | | ||
| note: for a trait to be dyn compatible it needs to allow building a vtable | ||
| for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:7:10 | ||
| | | ||
| LL | type Assoc; | ||
| | ^^^^^ ...because it has conflicting associated item bounds for Assoc in supertraits | ||
| ... | ||
| LL | trait Sub<T, U>: Super<T, Assoc = u32> + Super<U, Assoc = u64> { | ||
| | --- this trait is not dyn compatible... | ||
|
|
||
| error[E0038]: the trait `Sub` is not dyn compatible | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:17:9 | ||
| | | ||
| LL | x.unwrap().method(); | ||
| | ^^^^^^^^^^ `Sub` is not dyn compatible | ||
| | | ||
| note: for a trait to be dyn compatible it needs to allow building a vtable | ||
| for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:7:10 | ||
| | | ||
| LL | type Assoc; | ||
| | ^^^^^ ...because it has conflicting associated item bounds for Assoc in supertraits | ||
| ... | ||
| LL | trait Sub<T, U>: Super<T, Assoc = u32> + Super<U, Assoc = u64> { | ||
| | --- this trait is not dyn compatible... | ||
|
|
||
| error[E0038]: the trait `Sub` is not dyn compatible | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:26:21 | ||
| | | ||
| LL | foo::<i16, i16>(None); | ||
| | ^^^^ `Sub` is not dyn compatible | ||
| | | ||
| note: for a trait to be dyn compatible it needs to allow building a vtable | ||
| for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> | ||
| --> $DIR/conflicting-bounds-different-generics-simple.rs:7:10 | ||
| | | ||
| LL | type Assoc; | ||
| | ^^^^^ ...because it has conflicting associated item bounds for Assoc in supertraits | ||
| ... | ||
| LL | trait Sub<T, U>: Super<T, Assoc = u32> + Super<U, Assoc = u64> { | ||
| | --- this trait is not dyn compatible... | ||
|
|
||
| error: aborting due to 3 previous errors | ||
|
|
||
| For more information about this error, try `rustc --explain E0038`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // We currently accept conflicting associated type bounds with different generics, | ||
| // which results in unsoundness, since those generics can be instantiated with the | ||
| // same concrete type. | ||
| // See https://github.com/rust-lang/rust/issues/154662 | ||
|
|
||
| type Payload = Box<i32>; | ||
| type Src<'a> = &'a Payload; | ||
| type Dst = &'static Payload; | ||
|
|
||
| trait Super<T> { | ||
| type Assoc; | ||
| } | ||
|
|
||
| trait Sub<'a, A1, A2>: Super<A1, Assoc = Src<'a>> + Super<A2, Assoc = Dst> {} | ||
|
|
||
| trait Callback<A1, A2> { | ||
| fn callback<U: Super<A1> + Super<A2> + ?Sized>( | ||
| payload: <U as Super<A1>>::Assoc, | ||
| ) -> <U as Super<A2>>::Assoc; | ||
| } | ||
| struct CallbackStruct; | ||
| impl Callback<i16, i16> for CallbackStruct { | ||
| fn callback<U: Super<i16> + ?Sized>(payload: U::Assoc) -> U::Assoc { | ||
| payload | ||
| } | ||
| } | ||
|
|
||
| fn require_trait< | ||
| 'a, | ||
| A1, | ||
| A2, | ||
| U: Super<A1, Assoc = Src<'a>> + Super<A2, Assoc = Dst> + ?Sized, | ||
| C: Callback<A1, A2>, | ||
| >( | ||
| payload: Src<'a>, | ||
| ) -> Dst { | ||
| C::callback::<U>(payload) | ||
| } | ||
|
|
||
| fn use_dyn<'a, A1, A2, C: Callback<A1, A2>>(payload: Src<'a>) -> Dst { | ||
| require_trait::<'a, A1, A2, dyn Sub<'a, A1, A2>, C>(payload) | ||
| //~^ ERROR the trait `Sub` is not dyn compatible | ||
| } | ||
|
|
||
| fn extend<'a>(payload: Src<'a>) -> Dst { | ||
| // `dyn Sub<'a, i16, i16>` has both an `Assoc = Src<'a>` bound and an `Assoc = Dst` bound. | ||
| use_dyn::<i16, i16, CallbackStruct>(payload) | ||
| } | ||
|
|
||
| fn main() { | ||
| let payload: Box<Payload> = Box::new(Box::new(1)); | ||
| let wrong: &'static Payload = extend(&*payload); | ||
| drop(payload); | ||
| println!("{wrong}"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| error[E0038]: the trait `Sub` is not dyn compatible | ||
| --> $DIR/conflicting-bounds-different-generics-unsound.rs:41:37 | ||
| | | ||
| LL | require_trait::<'a, A1, A2, dyn Sub<'a, A1, A2>, C>(payload) | ||
| | ^^^^^^^^^^^^^^^ `Sub` is not dyn compatible | ||
| | | ||
| note: for a trait to be dyn compatible it needs to allow building a vtable | ||
| for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> | ||
| --> $DIR/conflicting-bounds-different-generics-unsound.rs:11:10 | ||
| | | ||
| LL | type Assoc; | ||
| | ^^^^^ ...because it has conflicting associated item bounds for Assoc in supertraits | ||
| ... | ||
| LL | trait Sub<'a, A1, A2>: Super<A1, Assoc = Src<'a>> + Super<A2, Assoc = Dst> {} | ||
| | --- this trait is not dyn compatible... | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0038`. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
View changes since the review