danielhenrymantilla / nougat.rs

(lifetime) GATs on stable Rust
https://docs.rs/nougat
Apache License 2.0
53 stars 6 forks source link

GATs can not refer to other GATs in trait requirement's associated types #11

Open chrysn opened 1 year ago

chrysn commented 1 year ago

Evaluating whether nougat could help get coap-message rid of its nightly wart, I found what appears to be another limitation of nougat that might warrant being pointed out in the list (or maybe fixed if it's easy): GATs do not work when referring to each other in a their required traits' associated types.

This code works with #![feature(generic_associated_types)], but not with #[nougat::gat]:

pub trait Trait {
    type Item<'a> where Self: 'a;
    type Iterator<'b>: Iterator<Item=Self::Item<'b>> where Self: 'b;
}

The relevant error message with macro-backtrace is:

error[E0277]: the trait bound `Self: TraitඞItem<'b>` is not satisfied
  --> src/lib.rs:3:1
   |
3  |   #[nougat::gat]
   |   ^^^^^^^^^^^^^^
   |   |
   |   the trait `TraitඞItem<'b>` is not implemented for `Self`
   |   in this procedural macro expansion
   |
  ::: /home/chrysn/.cargo/registry/src/github.com-1ecc6299db9ec823/nougat-proc_macros-0.2.1/mod.rs:41:25
   |
41 |   #[proc_macro_attribute] pub
   |  _________________________-
42 | | fn gat (
43 | |     attrs: TokenStream,
44 | |     input: TokenStream,
45 | | ) -> TokenStream
   | |________________- in this expansion of `#[nougat::gat]`
danielhenrymantilla commented 1 year ago

Thanks for reporting this, I was indeed unaware of this issue.

A minimal reproduction of the problematic code emitted by nougat is, I believe:

macro_rules! Where {(
    $T:ty : $lt:lifetime $(,)?
) => (
    [&$lt $T; 0]
)}

trait Trait
:
    for<'any> TraitItem<'any> +
    for<'any> TraitIterator<'any> +
{}

trait TraitItem<'a, Bound = Where!(Self : 'a)> {
    type T;
}

trait TraitIterator<'b, Bound = Where!(Self : 'b)> {
    type T : Iterator<Item = <Self as TraitItem<'b>>::T>;
}
danielhenrymantilla commented 1 year ago

Now, generating something like:

  trait Trait
  :
      for<'any> TraitItem<'any> +
      for<'any> TraitIterator<'any> +
  {}

  trait TraitItem<'a, Bound = Where!(Self : 'a)>
  :
-     'a + for<'any> TraitIterator<'any> +
+     // if it were added, we'd have a "cyclic requirements" error
  {
      type T;
  }

  trait TraitIterator<'b, Bound = Where!(Self : 'b)>
  :
+     'b + for<'any> TraitItem<'any>
  {
      type T : Iterator<Item = <Self as TraitItem<'b>>::T>;
  }

seems to fix the issue, but now we have another problem: the macro cannot really distinguish between the nature of the Item and Iterator associated types (at least not without involving some probably unaccurate heuristics regarding the complexity of the assoc item bounds), and yet the only expansion that works is by introducing some form of hierarchy between them: the Iterator assoc type comes after the Item one.

So in order to support this, we'd need some kind of annotation on the original trait definition to nudge nougat in the right direction:

  #[gat]
  pub trait Trait {
      type Item<'a> where Self : 'a;

+     #[gat(needs(Item))]
      type Iterator<'b> : Iterator<Item=Self::Item<'b>> where Self : 'b;
  }

Otherwise we could risk a heuristic looking for Self::Assoc<'lt> kind of types, verbatim, in the bounds of other assoc types 🤷

Thoughts?

chrysn commented 1 year ago

I don't follow the details, but the annotations would be easily manageable.

My first gut feeling was that there might arise the need to indicate how deeply chained the GATs are in the trait. These explicit annotations are simpler to provide than that full information.

Speaking of nesting: Would the last item in this extended example be annotated like that?

#[gat]
pub trait Trait {
    type Item<'a> where Self : 'a;

    #[gat(needs(Item))]
    type Iterator<'b> : Iterator<Item=Self::Item<'b>> where Self : 'b;

    #[gat(needs(Iterator, Item)]
    type DeepIterator<'c>: Iterator<Item=Self::Iterator<'c>> + Foo<Bar=Self::Item> where Self: 'c;
  }

(Disclaimer: pure curiosity, my types don't go that deep AFAIR; probably a "this doesn't nest deeper than ..." would even work for me.)

danielhenrymantilla commented 1 year ago

I think the "chaining" would be automatically figured out, so I suspect that last theoretical example could just have needs(Item) on Iterator and needs(Iterator) on DeepIterator 🙂