Repository navigation
super trait bounds can result in unconstrained regions #136547
Description
trait Super {
type SAssoc;
}
trait Trait<'a>: Super<SAssoc = <Self as Trait<'a>>::TAssoc> {
type TAssoc;
}
fn hr_where_bound<T: for<'a> Trait<'a>>() {}T: for<'a> Trait<'a>> currently elaborates to T: for<'a> Super<SAssoc = <T as Trait<'a>>::TAssoc>. THe projection clause uses 'a in the term without constraining it in the alias. Manually writing such a where-clause results in an error: https://rust.godbolt.org/z/d8G6GvMbe
error[E0582]: binding for associated type `SAssoc` references lifetime `'a`, which does not appear in the trait input types
--> <source>:10:22
|
10 | T: for<'a> Super<SAssoc = <T as Trait<'a>>::TAssoc>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^One can still implement and use Trait as long as TAssoc does not mention 'a. People use such super trait bounds in the wild, as discovered by a crater run in #136303.
Supporting unconstrained regions is difficult as it causes normalization of <T as SUper>::SAssoc to use a different unconstrained region variable each time. This can prevent us from proving type outlive bounds, as they rely on region equality when applying assumptions:
fn test<'a, T: 'a>() {}
fn unconstrained_lt1<'arg, T: for<'a> Trait<'a>>(x: &'arg <T as Super>::SAssoc) {
test::<'arg, <T as Super>::SAssoc>();
//~^ ERROR `<T as Trait<'_>>::TAssoc` does not live long enough
}
// Has an implied `<T as Super>::SAssoc: 'arg` bound.
struct Struct<'arg, T: Super>(&'arg <T as Super>::SAssoc);
fn unconstrained_lt2<'arg, T: for<'a> Trait<'a>>(x: &'arg <T as Super>::SAssoc) {
let _ = Struct::<T>(x);
//~^ ERROR `<T as Trait<'_>>::TAssoc` does not live long enough
}I've discussed this issue with @compiler-errors and believe that we need to keep supporting such code as it's 'reasonable' and forbidding it results in significant crater breakage.
Our current idea is to encode "One can still implement and use Trait as long as TAssoc does not mention 'a" in the type system by adding support for bivariant region parameters on associated types. We would then change 'a to be bivariant in <T as Trait<'a>>::TAssoc and ignore it when checking outlives bounds. We'd still get unconstrained regions this way, but they should no longer cause issues.
Activity
aliemjay commentedon Feb 5, 2025
I’ve encountered this issue before with implied bounds and was fairly certain that I had opened a ticket for it, but I couldn’t find one when checked.
The issue is independent of
TAssoc:How the proposed solution deals with the code above?
lcnr commentedon Feb 5, 2025
Good point. There are two ways to implement trait here
With both of these impls
T: for<'a> Trait<'a>is unsatisfiable. We can probably not do anything here and just accept the broken type outlives constraints. I don't think such unconstrained regions are unsound, they are just difficult to support correctly. The reason this is a problem right now is that it makes it significantly more difficult to compute implied bounds outside of mir borrowck, as we rely on the normalized signature being used by the implied bounds computation being the same as the normalized signature used in borrowck.We could check that elaboration does not result in unconstrained regions outside of aliases? Checking that elaboration does not result in unconstrained regions being mentioned in the term at all also breaks the following code as the
foowhere-clauses elaborates tofor<'a> T: FnOnce<I::Item<'a>> -> <Self as FnOnceDup<I::Item<'a>>>::Output:That doesn't seem too helpful and kinda hacky however 🤔