Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dingxiangfei2009 opened this issue Feb 25, 2024 · 4 comments
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@dingxiangfei2009
Copy link
Contributor

dingxiangfei2009 commented Feb 25, 2024

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 dingxiangfei2009 added the C-bug Category: This is a bug. label Feb 25, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 25, 2024
@jieyouxu jieyouxu added A-borrow-checker Area: The borrow checker T-types Relevant to the types team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Feb 26, 2024
@dingxiangfei2009
Copy link
Contributor Author

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

@lcnr
Copy link
Contributor

lcnr commented Mar 21, 2024

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
Copy link
Contributor

lcnr commented Mar 21, 2024

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
Copy link
Contributor

lcnr commented Mar 21, 2024

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 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants