blob: 144e27316cbb47d0fd47763c6a1d689e46b97e57 [file] [view] [edit]
# Having separate `Trait` and `Projection` bounds
Given `T: Foo<AssocA = u32, AssocB = i32>` where-bound, we currently lower it to a `Trait(Foo<T>)` and separate `Projection(<T as Foo>::AssocA, u32)` and `Projection(<T as Foo>::AssocB, i32)` bounds.
Why do we not represent this as a single `Trait(Foo[T], [AssocA = u32, AssocB = u32]` bound instead?
The way we prove `Projection` bounds directly relies on proving the corresponding `Trait` bound: [old solver](https://github.com/rust-lang/rust/blob/461e9738a47e313e4457957fa95ff6a19a4b88d4/compiler/rustc_trait_selection/src/traits/project.rs#L898) [new solver](https://github.com/rust-lang/rust/blob/461e9738a47e313e4457957fa95ff6a19a4b88d4/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs#L37-L41).
It feels like it might make more sense to just have a single implementation which checks whether a trait is implemented and returns (a way to compute) its associated types.
This is unfortunately quite difficult, as we may use a different candidate for norm than for the corresponding trait bound.
See [alias-bound vs where-bound](https://rustc-dev-guide.rust-lang.org/solve/candidate-preference.html#we-always-consider-aliasbound-candidates) and [global where-bound vs impl](https://rustc-dev-guide.rust-lang.org/solve/candidate-preference.html#we-prefer-global-where-bounds-over-impls).
There are also some other subtle reasons for why we can't do so.
The most stupid is that for rigid aliases;
trying to normalize them does not consider any lifetime constraints from proving the trait bound.
This is necessary due to a lack of assumptions on binders - https://github.com/rust-lang/trait-system-refactor-initiative/issues/177 - and should be fixed longterm.
A separate issue is that, right now,
fetching the `type_of` associated types for `Trait` goals or in shadowed `Projection` candidates can cause query cycles for RPITIT.
See https://github.com/rust-lang/trait-system-refactor-initiative/issues/185.
There are also slight differences between candidates for some of the builtin impls, these do all seem generally undesirable and I consider them to be bugs which would be fixed if we had a unified approach here.
Finally, not having this split makes lowering where-clauses more annoying.
With the current system having duplicate where-clauses is not an issue and it can easily happen when elaborating super trait bounds.
We now need to make sure we merge all associated type constraints, e.g.:
```rust
trait Super {
type A;
type B;
}
trait Trait: Super<A = i32> {}
// how to elaborate Trait<B = u32>
```
Or even worse
```rust
trait Super<'a> {
type A;
type B;
}
trait Trait<'a>: Super<'a, A = i32> {}
// how to elaborate
// T: Trait<'a> + for<'b> Super<'b, B = u32>
```