Skip to content

Commit

Permalink
Unrolled build for rust-lang#130367
Browse files Browse the repository at this point in the history
Rollup merge of rust-lang#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 rust-lang#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.
  • Loading branch information
rust-timer authored Oct 5, 2024
2 parents 495f75a + fd7ee48 commit c8ffae9
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
);
}
_ => (),
}
Expand Down Expand Up @@ -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>>>(
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/ty/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
40 changes: 40 additions & 0 deletions compiler/rustc_type_ir/src/elaborate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_type_ir/src/inherent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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`.
4 changes: 2 additions & 2 deletions tests/ui/traits/object/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {}
Expand All @@ -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
Expand Down
13 changes: 1 addition & 12 deletions tests/ui/traits/object/pretty.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,6 @@ LL | fn dyn_fixed_generic1(x: &dyn for<'a> FixedGeneric1<'a>) { x }
= note: expected unit type `()`
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
|
Expand Down Expand Up @@ -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`.

0 comments on commit c8ffae9

Please sign in to comment.