rust-lang / rust

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

Error typechecking lifetime-GAT bound when "parent" bound is inferred #96230

Open audunhalland opened 2 years ago

audunhalland commented 2 years ago

I tried this code:

#![feature(generic_associated_types)]

use std::fmt::Debug;

trait Classic {
    type Assoc;
}

trait Gat {
    type Assoc<'a>;
}

struct Foo;

impl Classic for Foo {
    type Assoc = ();
}

impl Gat for Foo {
    type Assoc<'i> = ();
}

fn classic_debug<T: Classic>(_: T)
where
    T::Assoc: Debug,
{
}

fn gat_debug<T: Gat>(_: T)
where
    for<'a> T::Assoc<'a>: Debug,
{
}

fn main() {
    classic_debug::<Foo>(Foo); // fine
    classic_debug(Foo); // fine

    gat_debug::<Foo>(Foo); // fine
    gat_debug(Foo); // boom
}

I expected to see this happen: It compiles

Instead, this happened: It fails type checking, with:

error[[E0277]](https://doc.rust-lang.org/nightly/error-index.html#E0277): `<_ as Gat>::Assoc<'a>` doesn't implement `Debug`
  [--> src/main.rs:40:5
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)   |
40 |     gat_debug(Foo); // boom
   |     ^^^^^^^^^ `<_ as Gat>::Assoc<'a>` cannot be formatted using `{:?}` because it doesn't implement `Debug`
   |
   = help: the trait `for<'a> Debug` is not implemented for `<_ as Gat>::Assoc<'a>`
note: required by a bound in `gat_debug`
  [--> src/main.rs:31:27
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)   |
29 | fn gat_debug<T: Gat>(_: T)
   |    --------- required by a bound in this
30 | where
31 |     for<'a> T::Assoc<'a>: Debug,
   |                           ^^^^^ required by this bound in `gat_debug`

Meta

playground

audunhalland commented 2 years ago

@rustbot label: +F-generic_associated_types

compiler-errors commented 2 years ago

This is not really a GATs issue. It's an issue with associated types that have higher-ranked lifetimes in general [playground].

use std::fmt::Debug;

trait Trait<'a> {
    type Assoc;
}

impl<'a> Trait<'a> for () {
    type Assoc = ();
}

fn bad<T>(_: T)
where
    for<'a> T: Trait<'a>,
    for<'a> <T as Trait<'a>>::Assoc: Debug,
{
}

fn main() {
    bad(());
}

Technical description of the issue follows:

So we typecheck that function call in two steps:

  1. We call FnCtxt::instantiate_value_path. This registers the where-clause bounds of the function.
  2. We call FnCtxt::check_call. This infers the type _ based on the type signature and the arguments.

Importantly, we actually register the where-clause bounds on the function during (1.) instead of (2.)

Since we haven't inferred the generic type T at step (1.), and still have a type variable in the generics of the call, we end up registering something like for<'a> <_ as Trait<'a>>::Assoc: Debug. The fact that's a higher-ranked predicate is important as well, since we don't end up replacing that projection type it with a fresh type variable like we would with a projection that does not have escaping bound vars.

The issue happens when we call FnCtxt::structurally_resolved_type inside FnCtxt::check_call, which calls select_where_possible downstream. We then try to select for<'a> <_ as Trait<'a>>::Assoc: Debug. Since Debug has no impls which match the self type <_ as Trait<'a>>::Assoc, it fails to satisfy the obligation, leading to the issue.

I can't think of any good solutions off the top of my head. This ties back down to the problems we have with higher-ranked lifetimes in projection types. I guess we could solve this by doing something like treating an obligation with higher-ranked lifetimes in projections (and infer variables) as ambiguous... but that seems hacky.

compiler-errors commented 2 years ago

I guess we could solve this by doing something like treating an obligation with higher-ranked lifetimes in projections (and infer variables) as ambiguous... but that seems hacky.

This hack does fix the issue. See https://github.com/compiler-errors/rust/commit/5d785d5e3deb25b1d6d81a30035bd0ddd15fd130 -- but I won't actually put it up as a PR and refining it unless someone like @jackh726 thinks it's worthwhile, lol. Anywho, I don't think should be GATs blocking.

jackh726 commented 2 years ago

This is #89196. I haven't thought about how to fix this, really.

audunhalland commented 2 years ago

Even though not directly GAT-related, I think this bug will influence how API design is done around GAT traits, as library developers often put in some effort to avoid a required turbofish.