rust-lang / rust

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

Typecheck error when using a equality-constrained GAT in a trait. #91762

Open Kimundi opened 2 years ago

Kimundi commented 2 years ago

In this code:

pub trait Functor {
    type With<T>;

    fn fmap<T, U>(this: Self::With<T>) -> Self::With<U>;
}

pub trait FunctorExt<T>: Sized {
    type Base: Functor<With<T> = Self>;

    fn fmap<U>(self) {
        let arg: <Self::Base as Functor>::With<T>;
        let ret: <Self::Base as Functor>::With<U>;

        arg = self;
        ret = <Self::Base as Functor>::fmap(arg);
    }
}

I would expect the code in FunctorExt to compile, as I'm constraining Self::Base::With<T> = Self, while Self::Base::With<U> should just be whatever With<T> is without any extra constraints.

But I'm getting this compiler error:

error[E0308]: mismatched types
  --> src/case3.rs:15:15
   |
7  | / pub trait FunctorExt<T>: Sized {
8  | |     type Base: Functor<With<T> = Self>;
9  | |
10 | |     fn fmap<U>(self) {
...  |
15 | |         ret = <Self::Base as Functor>::fmap(arg);
   | |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found type parameter `Self`
16 | |     }
17 | | }
   | |_- this type parameter
   |
   = note: expected associated type `<<Self as FunctorExt<T>>::Base as Functor>::With<U>`
               found type parameter `Self`
   = note: you might be missing a type parameter or trait bound

For more information about this error, try `rustc --explain E0308`.
error: could not compile `hkt` due to previous error

It seems to me as if the Base: Functor<With<T> = Self>; constraint causes rustc to think that any With<X> should be the same type as Self, rather than just the one With<T> for the specific T in scope.

Meta

rustc --version --verbose:

rustc 1.58.0-nightly (495322d77 2021-11-08)
binary: rustc
commit-hash: 495322d776fd6f679cd8cd4ca02b8fa834da654b
commit-date: 2021-11-08
host: x86_64-unknown-linux-gnu
release: 1.58.0-nightly
LLVM version: 13.0.0
jackh726 commented 2 years ago

Okay, so here's what's happening:

So, we look up the function <<Self as FunctorExt<T>>::Base as Functor>::fmap, and get something that's roughly fn(<<Self as FunctorExt<T>>::Base as Functor>::With<?0>) -> <<Self as FunctorExt<T>>::Base as Functor>::With<?1>. Then, we try to normalize <<Self as FunctorExt<T>>::Base as Functor>::With<?1> and we find the predicate (ProjectionCandidate) <<Self as FunctorExt<T>>::Base as Functor>::With<T> = Self, so we set ?1 = T, normalize and get Self. So, after complete normalization, we get fn(Self) -> Self. Well, that second return type shouldn't have normalized, because T != U, and we never actually compared that.

Now, two potential ways to fix this: 1) Maybe just delay normalizing the function signature so we don't end up trying to set ?1 = T and normalizing so early. This is potentially tricky, since we might need to normalize for other type checking. 2) Use T and U instead of inference vars. But that might mess with caching.

Will look into this more soon.

jackh726 commented 2 years ago

I have a "working" branch here: https://github.com/jackh726/rust/tree/issue-91762

But it's absolutely awful and probably not a "correct" solution.

jackh726 commented 2 years ago

no_std repro, for reference:

#![feature(generic_associated_types)]
#![feature(lang_items)]
#![feature(no_core)]
#![no_core]
#![crate_type = "rlib"]

#[lang = "sized"]
pub trait Sized {}

#[lang = "copy"]
pub trait Copy {}

pub trait Functor {
    type With<T>;

    fn fmap<T, U>(this: Self::With<T>) -> Self::With<U>;
}

pub trait FunctorExt<T>: Sized {
    type Base: Functor<With<T> = Self>;

    fn fmap<U>(self) {
        let arg: <Self::Base as Functor>::With<T>;
        let ret: <Self::Base as Functor>::With<U>;

        arg = self;
        ret = <Self::Base as Functor>::fmap(arg);
    }
}
jackh726 commented 2 years ago

GATs issue triage: not blocking. The example can compile by changing the ret = line to ret = <Self::Base as Functor>::fmap::<T, U>(arg). We've updated the code so that we no longer arbitrarily constrain inference vars in GAT substs when projecting an associated type, which would have been a backwards-compatibility hazard. We would like the current test to compile eventually, but doing so is backwards-compatible and not doing so right now isn't a huge ergonomic hurdle given the fix above.