Skip to content

E0195 diagnostic should take into account GATs, etc #135350

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
QuineDot opened this issue Jan 10, 2025 · 2 comments
Open

E0195 diagnostic should take into account GATs, etc #135350

QuineDot opened this issue Jan 10, 2025 · 2 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@QuineDot
Copy link

QuineDot commented Jan 10, 2025

Code

pub trait Trait {
    type Gat<'a>;
    fn example(self, _: Self::Gat<'_>) -> Self::Gat<'_>;
}

impl Trait for () {
    type Gat<'a> = ();
    fn example(self, _: Self::Gat<'_>) {}
}

Current output

error[E0195]: lifetime parameters or bounds on method `example` do not match the trait declaration
 --> <source>:8:15
  |
3 |     fn example(self, _: Self::Gat<'_>) -> Self::Gat<'_>;
  |               - lifetimes in impl do not match this method in trait
...
8 |     fn example(self, _: Self::Gat<'_>) {}
  |               ^ lifetimes do not match method in trait

Desired output

error[E0195]: lifetime parameters or bounds on method `example` do not match the trait declaration
 --> <source>:8:15
  |
3 |     fn example(self, _: Self::Gat<'_>) -> Self::Gat<'_>;
  |                                   --                --
  |         lifetimes in impl do not match this method in trait
...
8 |     fn example(self, _: Gat<'_>) {}
  |                             --
  |         lifetimes do not match method in trait
  |

note: The lifetime in the trait does not constrain the lifetime parameter,
      but the lifetime in the implementation signature is constrained

hint: Make the lifetime in the implementation unconstrained by mentioning
      the lifetime in an explicit bound:

    fn example<'a:'a>(self, _: Self::Gat<'a>) {}
              +++++++                    ~~

Rationale and extra context

Context: #109476 and #87803. Those are filed as bugs, so I'm filing this diagnostic issue separately. It's plausible this will become moot if those issues are resolved, but it's not certain that will happen (or that it will happen soon).

When I encountered this, it took me awhile to figure out that E0195 was really about late-vs-early lifetimes parameters. Only when I figured that out did the error make sense. Late-vs-early lifetime parameters are niche knowledge, so I'm not entirely sure how to phrase the error message; it needs to probably spell out a workaround, as it's not obvious.

The current phrasing of E0195 makes sense when lifetime parameters are early bound due to appearing in explicit bounds. However, lifetime parameters can also become early bound implicitly/invisibly, such as in the example. There are similar cases (see #87803) when the return type is an RPIT/RPITIT (-> impl Trait) -- in which case the lifetime is early bound due to appearing in the implicit use<..> bounds.

Also in those cases, exactly matching the signature from the trait is not always an option or desirable. For example, when using refinement and/or precise capturing -- i.e. intentionally removing a lifetime from the return type in order to provide more functionality than the trait requires.

So for example while -> Self::Gat<'_> would be a usable suggestion for the code at the top of this issue, it is not a usable suggestion here:

pub trait Trait {
    type Gat<'a>;
    fn example(&self, _: Self::Gat<'_>) -> impl Sized;
}

impl Trait for () {
    type Gat<'a> = &'a str;
    #[allow(refining_impl_trait)]
    fn example(&self, _: Self::Gat<'_>) -> i32 { 0 }
}

Other cases

Being explicit about lifetimes doesn't improve things; the span highlighted has an "Expected X found X" flavor.

3 |     fn example<'a, 'b>(&'a self, _: Self::Gat<'b>) -> Self::Gat<'b>;
  |               -------- lifetimes in impl do not match this method in trait
...
8 |     fn example<'a, 'b>(&'a self, _: Self::Gat<'b>) {}
  |               ^^^^^^^^ lifetimes do not match method in trait

Rust Version

Rust Playground
Build using the Stable version: 1.84.0

Anything else?

No response

@QuineDot QuineDot added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 10, 2025
@QuineDot
Copy link
Author

The "constrained"/"unconstrained" in my desired output are definitely also not accurate, but I haven't thought of a good phrasing that avoids late/early bound so far. Those are discussed in E0794, so maybe E0195 could just bite the bullet and explain that too (in the long form at least), or refer to E0794 or some other official documentation (which may not yet exist for late/early bound lifetime parameters).

error[E0195]: lifetime parameters or bounds on method `example` do not match the trait declaration
 --> src/lib.rs:8:15
  |
3 |     fn example(self, _: Self::Gat<'_>) -> Self::Gat<'_>;
  |               - lifetimes in impl do not match this method in trait
...
8 |     fn example(self, _: Self::Gat<'_>) {}
  |               ^                    -- ^ the return value does not mention `'1`
  |               |                     |
  |               |                     call this lifetime `'1`
  | lifetimes do not match method in trait

note: The lifetime in the trait is early bound, but the lifetime in the impl is late bound.
      See E0794 for a discussion of late and early bound parameters.

hint: Match the signature from the trait exactly (though this may change the meaning of your signature):

    fn example(self, _: Self::Gat<'_>) -> Self::Gat<'_> {
                                       ++++++++++++++++

hint: Make the lifetime in the implementation early bound by mentioning
      the lifetime in an explicit bound:

    fn example<'a:'a>(self, _: Self::Gat<'a>) {}
              +++++++                    ~~

(Though also note that the definition of a late-bound lifetime parameter in E0794 is currently incomplete.)

@Kontinuation
Copy link

Just came across a similar issue using the latest stable rustc (rustc 1.86.0 (05f9846 2025-03-31)):

Playground

pub trait ArgTypeTrait {
    type Type<'a>;
}

pub trait ReturnTypeTrait {
    type Type<'a>;
}

pub struct VecInt32SliceArg;
impl ArgTypeTrait for VecInt32SliceArg {
    type Type<'a> = Vec<&'a [i32]>;
}

pub struct Int32SliceReturn;
impl ReturnTypeTrait for Int32SliceReturn {
    type Type<'a> = &'a [i32];
}

// Foo1: using 2 lifetimes, this is the real intention of eval function: the lifetime of
// return value is tied to the lifetime of the second arg.

pub trait Foo1<A: ArgTypeTrait, R: ReturnTypeTrait> {
    fn eval<'a, 'b>(&'a self, arg: &A::Type<'b>) -> R::Type<'b>;
}

pub struct Bar1;

impl Foo1<VecInt32SliceArg, Int32SliceReturn> for Bar1 {
    // However, this does not compile:
    // 
    // error[E0195]: lifetime parameters or bounds on method `eval` do not match the trait declaration
    //     --> src/lib.rs:40:12
    //      |
    //   23 |     fn eval<'a, 'b>(&'a self, arg: &A::Type<'b>) -> R::Type<'b>;
    //      |            -------- lifetimes in impl do not match this method in trait
    //   ...
    //   40 |     fn eval<'a, 'b>(&'a self, arg: &Vec<&'b [i32]>) -> &'b [i32] {
    //      |    
    //
    fn eval<'a, 'b>(&'a self, arg: &Vec<&'b [i32]>) -> &'b [i32] {
        arg[0]
    }
}

// Foo2: using 2 lifetimes, but using associated types for arg instead of expanded type.
// This compiles.

pub trait Foo2<A: ArgTypeTrait, R: ReturnTypeTrait> {
    fn eval<'a, 'b>(&'a self, arg: &A::Type<'b>) -> R::Type<'b>;
}

pub struct Bar2;

impl Foo2<VecInt32SliceArg, Int32SliceReturn> for Bar1 {
    fn eval<'a, 'b>(&'a self, arg: &<VecInt32SliceArg as ArgTypeTrait>::Type<'b>) -> &'b [i32] {
        arg[0]
    }
}

// Foo3: using 1 lifetime, this is more restrictive than Foo1 and Foo2, but when I use
// expanded type for arg, it compiles.

pub trait Foo3<A: ArgTypeTrait, R: ReturnTypeTrait> {
    fn eval<'a>(&'a self, arg: &A::Type<'a>) -> R::Type<'a>;
}

pub struct Bar3;

impl Foo3<VecInt32SliceArg, Int32SliceReturn> for Bar3 {
    fn eval<'a>(&'a self, arg: &Vec<&'a [i32]>) -> &'a [i32] {
        arg[0]
    }
}

The error message is confusing as it is, and it took me quite a long time to find out that this is related to early-bound/late-bound lifetime parameters, which is some arcane knowledge that only covered by Error code E0794 and the Rust Compiler Developer Guide. I'd really like to have this code compile cleanly, or having the compiler emitting useful error message if it does not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants