diff options
| author | Jubilee <workingjubilee@gmail.com> | 2024-10-04 19:19:22 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-04 19:19:22 -0700 |
| commit | 9510c7366d4a0adb24117bb312a78314963cb8d4 (patch) | |
| tree | 550c11e7dc6927d2b587760092504b4ec5774b9d | |
| parent | f09e5a785b6be5a6c9acacb80c44427bbb0fa2e9 (diff) | |
| parent | fd7ee484f9da99120116dc560452dd551d4c6496 (diff) | |
| download | rust-9510c7366d4a0adb24117bb312a78314963cb8d4.tar.gz rust-9510c7366d4a0adb24117bb312a78314963cb8d4.zip | |
Rollup merge of #130367 - compiler-errors:super-unconstrained, r=spastorino
Check elaborated projections from dyn don't mention unconstrained late bound lifetimes
Check that the projections that are *not* explicitly written but which we deduce from elaborating the principal of a `dyn` *also* do not reference unconstrained late-bound lifetimes, just like the ones that the user writes by hand.
That is to say, given:
```
trait Foo<T>: Bar<Assoc = T> {}
trait Bar {
type Assoc;
}
```
The type `dyn for<'a> Foo<&'a T>` (basically) elaborates to `dyn for<'a> Foo<&'a T> + for<'a> Bar<Assoc = &'a T>`[^1]. However, the `Bar` projection predicate is not well-formed, since `'a` must show up in the trait's arguments to be referenced in the term of a projection. We must error in this situation[^well], or else `dyn for<'a> Foo<&'a T>` is unsound.
We already detect this for user-written projections during HIR->rustc_middle conversion, so this largely replicates that logic using the helper functions that were already conveniently defined.
---
I'm cratering this first to see the fallout; if it's minimal or zero, then let's land it as-is. If not, the way that this is implemented is very conducive to an FCW.
---
Fixes #130347
[^1]: We don't actually elaborate it like that in rustc; we only keep the principal trait ref `Foo<&'a T>` and the projection part of `Bar<Assoc = ...>`, but it's useful to be a bit verbose here for the purpose of explaining the issue.
[^well]: Well, we could also make `dyn for<'a> Foo<&'a T>` *not* implement `for<'a> Bar<Assoc = &'a T>`, but this is inconsistent with the case where the user writes `Assoc = ...` in the type itself, and it overly complicates the implementation of trait objects' built-in impls.
8 files changed, 149 insertions, 18 deletions
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs index e7b8e6e69b0..394a263fbb5 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs @@ -13,6 +13,7 @@ use rustc_middle::ty::{ use rustc_span::{ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::traits::report_dyn_incompatibility; use rustc_trait_selection::traits::{self, hir_ty_lowering_dyn_compatibility_violations}; +use rustc_type_ir::elaborate::ClauseWithSupertraitSpan; use smallvec::{SmallVec, smallvec}; use tracing::{debug, instrument}; @@ -124,16 +125,19 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .into_iter() .filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id())); - for (base_trait_ref, span) in regular_traits_refs_spans { + for (base_trait_ref, original_span) in regular_traits_refs_spans { let base_pred: ty::Predicate<'tcx> = base_trait_ref.upcast(tcx); - for pred in traits::elaborate(tcx, [base_pred]).filter_only_self() { + for ClauseWithSupertraitSpan { pred, original_span, supertrait_span } in + traits::elaborate(tcx, [ClauseWithSupertraitSpan::new(base_pred, original_span)]) + .filter_only_self() + { debug!("observing object predicate `{pred:?}`"); let bound_predicate = pred.kind(); match bound_predicate.skip_binder() { ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { let pred = bound_predicate.rebind(pred); - associated_types.entry(span).or_default().extend( + associated_types.entry(original_span).or_default().extend( tcx.associated_items(pred.def_id()) .in_definition_order() .filter(|item| item.kind == ty::AssocKind::Type) @@ -172,8 +176,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // the discussion in #56288 for alternatives. if !references_self { // Include projections defined on supertraits. - projection_bounds.push((pred, span)); + projection_bounds.push((pred, original_span)); } + + self.check_elaborated_projection_mentions_input_lifetimes( + pred, + original_span, + supertrait_span, + ); } _ => (), } @@ -360,6 +370,56 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { Ty::new_dynamic(tcx, existential_predicates, region_bound, representation) } + + /// Check that elaborating the principal of a trait ref doesn't lead to projections + /// that are unconstrained. This can happen because an otherwise unconstrained + /// *type variable* can be substituted with a type that has late-bound regions. See + /// `elaborated-predicates-unconstrained-late-bound.rs` for a test. + fn check_elaborated_projection_mentions_input_lifetimes( + &self, + pred: ty::PolyProjectionPredicate<'tcx>, + span: Span, + supertrait_span: Span, + ) { + let tcx = self.tcx(); + + // Find any late-bound regions declared in `ty` that are not + // declared in the trait-ref or assoc_item. These are not well-formed. + // + // Example: + // + // for<'a> <T as Iterator>::Item = &'a str // <-- 'a is bad + // for<'a> <T as FnMut<(&'a u32,)>>::Output = &'a str // <-- 'a is ok + let late_bound_in_projection_term = + tcx.collect_constrained_late_bound_regions(pred.map_bound(|pred| pred.projection_term)); + let late_bound_in_term = + tcx.collect_referenced_late_bound_regions(pred.map_bound(|pred| pred.term)); + debug!(?late_bound_in_projection_term); + debug!(?late_bound_in_term); + + // FIXME: point at the type params that don't have appropriate lifetimes: + // struct S1<F: for<'a> Fn(&i32, &i32) -> &'a i32>(F); + // ---- ---- ^^^^^^^ + // NOTE(associated_const_equality): This error should be impossible to trigger + // with associated const equality constraints. + self.validate_late_bound_regions( + late_bound_in_projection_term, + late_bound_in_term, + |br_name| { + let item_name = tcx.item_name(pred.projection_def_id()); + struct_span_code_err!( + self.dcx(), + span, + E0582, + "binding for associated type `{}` references {}, \ + which does not appear in the trait input types", + item_name, + br_name + ) + .with_span_label(supertrait_span, "due to this supertrait") + }, + ); + } } fn replace_dummy_self_with_error<'tcx, T: TypeFoldable<TyCtxt<'tcx>>>( diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index fd4e8f1cd4e..d20cb368278 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -179,6 +179,10 @@ pub struct Clause<'tcx>( ); impl<'tcx> rustc_type_ir::inherent::Clause<TyCtxt<'tcx>> for Clause<'tcx> { + fn as_predicate(self) -> Predicate<'tcx> { + self.as_predicate() + } + fn instantiate_supertrait(self, tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>) -> Self { self.instantiate_supertrait(tcx, trait_ref) } diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 61736633cfa..dac45ff2aba 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -44,6 +44,46 @@ pub trait Elaboratable<I: Interner> { ) -> Self; } +pub struct ClauseWithSupertraitSpan<I: Interner> { + pub pred: I::Predicate, + // Span of the original elaborated predicate. + pub original_span: I::Span, + // Span of the supertrait predicatae that lead to this clause. + pub supertrait_span: I::Span, +} +impl<I: Interner> ClauseWithSupertraitSpan<I> { + pub fn new(pred: I::Predicate, span: I::Span) -> Self { + ClauseWithSupertraitSpan { pred, original_span: span, supertrait_span: span } + } +} +impl<I: Interner> Elaboratable<I> for ClauseWithSupertraitSpan<I> { + fn predicate(&self) -> <I as Interner>::Predicate { + self.pred + } + + fn child(&self, clause: <I as Interner>::Clause) -> Self { + ClauseWithSupertraitSpan { + pred: clause.as_predicate(), + original_span: self.original_span, + supertrait_span: self.supertrait_span, + } + } + + fn child_with_derived_cause( + &self, + clause: <I as Interner>::Clause, + supertrait_span: <I as Interner>::Span, + _parent_trait_pred: crate::Binder<I, crate::TraitPredicate<I>>, + _index: usize, + ) -> Self { + ClauseWithSupertraitSpan { + pred: clause.as_predicate(), + original_span: self.original_span, + supertrait_span: supertrait_span, + } + } +} + pub fn elaborate<I: Interner, O: Elaboratable<I>>( cx: I, obligations: impl IntoIterator<Item = O>, diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 59a83ea5412..69665df4bfc 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -460,6 +460,8 @@ pub trait Clause<I: Interner<Clause = Self>>: + IntoKind<Kind = ty::Binder<I, ty::ClauseKind<I>>> + Elaboratable<I> { + fn as_predicate(self) -> I::Predicate; + fn as_trait_clause(self) -> Option<ty::Binder<I, ty::TraitPredicate<I>>> { self.kind() .map_bound(|clause| if let ty::ClauseKind::Trait(t) = clause { Some(t) } else { None }) diff --git a/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs new file mode 100644 index 00000000000..b174776c596 --- /dev/null +++ b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs @@ -0,0 +1,24 @@ +// Make sure that when elaborating the principal of a dyn trait for projection predicates +// we don't end up in a situation where we have an unconstrained late-bound lifetime in +// the output of a projection. + +// Fix for <https://github.com/rust-lang/rust/issues/130347>. + +trait A<T>: B<T = T> {} + +trait B { + type T; +} + +struct Erase<T: ?Sized + B>(T::T); + +fn main() { + let x = { + let x = String::from("hello"); + + Erase::<dyn for<'a> A<&'a _>>(x.as_str()) + //~^ ERROR binding for associated type `T` references lifetime `'a`, which does not appear in the trait input types + }; + + dbg!(x.0); +} diff --git a/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr new file mode 100644 index 00000000000..067eaf5e7f3 --- /dev/null +++ b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr @@ -0,0 +1,12 @@ +error[E0582]: binding for associated type `T` references lifetime `'a`, which does not appear in the trait input types + --> $DIR/elaborated-predicates-unconstrained-late-bound.rs:19:21 + | +LL | trait A<T>: B<T = T> {} + | ----- due to this supertrait +... +LL | Erase::<dyn for<'a> A<&'a _>>(x.as_str()) + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0582`. diff --git a/tests/ui/traits/object/pretty.rs b/tests/ui/traits/object/pretty.rs index 6660ff040f7..603d7af5260 100644 --- a/tests/ui/traits/object/pretty.rs +++ b/tests/ui/traits/object/pretty.rs @@ -13,7 +13,7 @@ trait SuperGeneric<'a> { } trait AnyGeneric<'a>: SuperGeneric<'a> {} trait FixedGeneric1<'a>: SuperGeneric<'a, Assoc2 = &'a u8> {} -trait FixedGeneric2<'a>: Super<Assoc = &'a u8> {} +// trait FixedGeneric2<'a>: Super<Assoc = &'a u8> {} // Unsound! trait FixedHrtb: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> {} trait AnyDifferentBinders: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> + Super {} trait FixedDifferentBinders: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> + Super<Assoc = u8> {} @@ -32,7 +32,7 @@ fn dyn_fixed_static(x: &dyn FixedStatic) { x } //~ERROR mismatched types fn dyn_super_generic(x: &dyn for<'a> SuperGeneric<'a, Assoc2 = &'a u8>) { x } //~ERROR mismatched types fn dyn_any_generic(x: &dyn for<'a> AnyGeneric<'a, Assoc2 = &'a u8>) { x } //~ERROR mismatched types fn dyn_fixed_generic1(x: &dyn for<'a> FixedGeneric1<'a>) { x } //~ERROR mismatched types -fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x } //~ERROR mismatched types +// fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x } // Unsound! fn dyn_fixed_generic_multi(x: &dyn for<'a> FixedGeneric1<'a, Assoc2 = &u8>) { x } //~ERROR mismatched types fn dyn_fixed_hrtb(x: &dyn FixedHrtb) { x } //~ERROR mismatched types fn dyn_any_different_binders(x: &dyn AnyDifferentBinders<Assoc = u8>) { x } //~ERROR mismatched types diff --git a/tests/ui/traits/object/pretty.stderr b/tests/ui/traits/object/pretty.stderr index ca56bdbb67a..af941e69c5f 100644 --- a/tests/ui/traits/object/pretty.stderr +++ b/tests/ui/traits/object/pretty.stderr @@ -107,17 +107,6 @@ LL | fn dyn_fixed_generic1(x: &dyn for<'a> FixedGeneric1<'a>) { x } found reference `&dyn for<'a> FixedGeneric1<'a>` error[E0308]: mismatched types - --> $DIR/pretty.rs:35:60 - | -LL | fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x } - | - ^ expected `()`, found `&dyn FixedGeneric2<'a>` - | | - | help: try adding a return type: `-> &dyn for<'a> FixedGeneric2<'a>` - | - = note: expected unit type `()` - found reference `&dyn for<'a> FixedGeneric2<'a>` - -error[E0308]: mismatched types --> $DIR/pretty.rs:36:79 | LL | fn dyn_fixed_generic_multi(x: &dyn for<'a> FixedGeneric1<'a, Assoc2 = &u8>) { x } @@ -172,6 +161,6 @@ LL | fn dyn_has_gat(x: &dyn HasGat<u8, Assoc<bool> = ()>) { x } = note: expected unit type `()` found reference `&dyn HasGat<u8, Assoc<bool> = ()>` -error: aborting due to 15 previous errors; 1 warning emitted +error: aborting due to 14 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0308`. |
