rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.32k stars 12.46k forks source link

Borrow checker fails to pick up existing region bounds in the environment when aliases are given to assoicated types in trait bounds. #121601

Open dingxiangfei2009 opened 6 months ago

dingxiangfei2009 commented 6 months ago

It occurs to me that adding named type terms to associated type of trait bounds can lead to borrow check failure, while the same code without "naming" the associated type passes the borrow check.

I tried this code:

pub trait Trait1 {
    type Output1;
    fn call<'z>(&'z self) -> &'z Self::Output1;
}

pub trait Trait2<T> {
    type Output2;
    fn call2<'x>(_: &'x T) -> &'x Self::Output2;
}

impl<A, B, T: Trait1<Output1 = A>> Trait2<T> for B // Mind this `A` here
where
    B: Trait2<T::Output1>,
{
    type Output2 = <B as Trait2<T::Output1>>::Output2;
    fn call2<'y>(source: &'y T) -> &'y Self::Output2 {
        let t = source.call();
        B::call2(t)
    }
}

I expected to see this happen: It should compile just like the following rewrite, without naming the Output1 to A.

pub trait Trait1 {
    type Output1;
    fn call<'z>(&'z self) -> &'z Self::Output1;
}

pub trait Trait2<T> {
    type Output2;
    fn call2<'x>(_: &'x T) -> &'x Self::Output2;
}

impl<B, T: Trait1> Trait2<T> for B
where
    B: Trait2<T::Output1>,
{
    type Output2 = <B as Trait2<T::Output1>>::Output2;
    fn call2<'y>(source: &'y T) -> &'y Self::Output2 {
        let t = source.call();
        B::call2(t)
    }
}

Instead, this happened:

error[E0309]: the parameter type `A` may not live long enough
  --> lifetime.rs:17:17
   |
16 |     fn call2<'y>(source: &'y T) -> &'y Self::Output2 {
   |              -- the parameter type `A` must be valid for the lifetime `'y` as defined here...
17 |         let t = source.call();
   |                 ^^^^^^^^^^^^^ ...so that the type `A` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
16 |     fn call2<'y>(source: &'y T) -> &'y Self::Output2 where A: 'y {
   |                                                      +++++++++++

error[E0309]: the parameter type `A` may not live long enough
  --> lifetime.rs:18:9
   |
16 |     fn call2<'y>(source: &'y T) -> &'y Self::Output2 {
   |              -- the parameter type `A` must be valid for the lifetime `'y` as defined here...
17 |         let t = source.call();
18 |         B::call2(t)
   |         ^^^^^^^^^^^ ...so that the type `A` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
16 |     fn call2<'y>(source: &'y T) -> &'y Self::Output2 where A: 'y {
   |                                                      +++++++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0309`.

Mind that due to the trait definition, it is not possible to apply the suggestion that is emitted by rustc.

Meta

This is found on the latest master, as of writing, and stable.

rustc --version --verbose:

rustc 1.78.0-nightly (256b6fb19 2024-02-06)
binary: rustc
commit-hash: 256b6fb19a2c018eaad4806d2369d1f6a71fc6ec
commit-date: 2024-02-06
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 17.0.6
Backtrace

Not an ICE, so not applicable.

dingxiangfei2009 commented 6 months ago

As pointed out in #121602, here is a similar issue #121670 but only for trait solver.

lcnr commented 5 months ago

I consider there to be two separate issues here, firstly the following:

pub trait Trait<'a> {
    type Assoc: 'a;
}

// Removing `Assoc = A` here makes this compile because you only get the
// alias-bound of `Assoc` if it is rigid
fn foo<'a, T: Trait<'a, Assoc = A>, A>() {
    let _: &'a T::Assoc = todo!();
}

fn main() {}
lcnr commented 5 months ago

and secondly, the one which is imo more applicable to your example:

pub trait Id<T> {
    type This;
}

impl<T, U> Id<U> for T {
    type This = T;
}

trait Trait {
    type Assoc;
}

// Removing `Assoc = U` makes this compile. This is because
// `<T as Id<T::Assoc>>::This` normalizes to `T`, resulting in an implied `T: 'a`
// bound. No bound on either `T::Assoc` or `U` is added here. Instead, `T::Assoc: 'a`
// holds as `T: 'a` holds.
fn foo<'a, T: Trait<Assoc = U>, U>(x: &'a <T as Id<T::Assoc>>::This) {
    let _: &'a T::Assoc = todo!();
}

fn main() {}
lcnr commented 5 months ago

I wanted to write that fixing the second issue is trivial, we can just structurally walk types involved in implied bounds. That's unfortunately not true as doing so is unsound: Alias<U>: 'a can hold regardless of whether U: 'a holds 😅