Skip to content
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

ImplGenerics should not emit attributes from generics declaration #422

Closed
TedDriggs opened this issue May 10, 2018 · 3 comments
Closed

ImplGenerics should not emit attributes from generics declaration #422

TedDriggs opened this issue May 10, 2018 · 3 comments

Comments

@TedDriggs
Copy link
Contributor

TedDriggs commented May 10, 2018

On syn@0.13.7, given this input struct from one of darling's examples:

#[derive(FromDeriveInput)]
#[darling(attributes(lorem))]
struct LoremReceiver<#[darling(bound = "::darling::FromGenericParam")] T> {
    generics: ast::Generics<ast::GenericParam<T>>,
}

The compiler produces the following error when trying to run the example:

error[E0658]: The attribute `darling` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
  --> examples/generics.rs:10:10
   |
10 | #[derive(FromDeriveInput)]
   |          ^^^^^^^^^^^^^^^
   |
   = help: add #![feature(custom_attribute)] to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.
error: Could not compile `darling`.

Adding the attribute and running cargo expand produces:

#[darling(attributes(lorem))]
struct LoremReceiver<#[darling(bound = "::darling::FromGenericParam")] T> {
    generics: ast::Generics<ast::GenericParam<T>>,
}

impl<#[darling(bound = "::darling::FromGenericParam")] T: ::darling::FromGenericParam> ::darling::FromDeriveInput for LoremReceiver<T> {
    fn from_derive_input(__di: &::syn::DeriveInput) -> ::darling::Result<Self> {
        // ... remainder elided
    }
}

The compiler appears to be complaining about the attribute in the ImplGeneric position; I've modified my code to strip all attributes from GenericParam before calling split_for_impl and it works fine without the feature flag. This theory is corroborated by rust-lang/rust#48848, which doesn't mention allowing attributes in this position.

I think the fix is as simple as removing this line, though there might be legitimate uses of this I don't know about, maybe?

@dtolnay
Copy link
Owner

dtolnay commented May 10, 2018

there might be legitimate uses of this I don't know about, maybe?

One legitimate use for attributes in impl generics would be the #[may_dangle] attribute, for example here. Any ideas for how to support both your use case and that one well?

unsafe impl<#[may_dangle] T: ?Sized> Drop for Rc<T> {

@TedDriggs
Copy link
Contributor Author

Good find - I'd never seen that before. What are the other cases where it can occur? Are doc comments allowed in that position?

Option 1

In that case #[may_dangle] doesn't appear in the declaration of the struct, which is useful. Add a new method, split_decl_for_impl(&self), which has the same behavior as split_for_impl but it tells ImplGenerics not to emit the underlying attributes. It might make sense to deprecate the current method and give the attribute-preserving version a more descriptive name.

Option 2

Have ImplGenerics keep a list of non-custom attrs known to the compiler in that position, and filter out others unless a (currently non-existent) emit_custom_attrs flag is set.

@dtolnay
Copy link
Owner

dtolnay commented Jul 22, 2018

I am closing this issue because I would like to see fewer generic parameter attributes in my life. I find the syntax struct S<#[...] T> obtrusive. In darling's case maybe this would work better on two lines the way Serde's bound attribute is represented:

- struct LoremReceiver<#[darling(bound = "::darling::FromGenericParam")] T> {
+ #[darling(bound = "T: darling::FromGenericParam")]
+ struct LoremReceiver<T> {

@dtolnay dtolnay closed this as completed Jul 22, 2018
github-merge-queue bot pushed a commit to bevyengine/bevy that referenced this issue Jan 28, 2024
# Objective

Fixes #8965.

#### Background

For convenience and to ensure everything is setup properly, we
automatically add certain bounds to the derived types. The current
implementation does this by taking the types from all active fields and
adding them to the where-clause of the generated impls. I believe this
method was chosen because it won't add bounds to types that are
otherwise ignored.

```rust
#[derive(Reflect)]
struct Foo<T, U: SomeTrait, V> {
  t: T,
  u: U::Assoc,
  #[reflect(ignore)]
  v: [V; 2]
}

// Generates something like:
impl<T, U: SomeTrait, V> for Foo<T, U, V>
where
  // Active:
  T: Reflect,
  U::Assoc: Reflect,

  // Ignored:
  [V; 2]: Send + Sync + Any
{
  // ...
}
```

The self-referential type fails because it ends up using _itself_ as a
type bound due to being one of its own active fields.

```rust
#[derive(Reflect)]
struct Foo {
  foo: Vec<Foo>
}

// Foo where Vec<Foo>: Reflect -> Vec<T> where T: Reflect -> Foo where Vec<Foo>: Reflect -> ...
```

## Solution

We can't simply parse all field types for the name of our type. That
would be both complex and prone to errors and false-positives. And even
if it wasn't, what would we replace the bound with?

Instead, I opted to go for a solution that only adds the bounds to what
really needs it: the type parameters. While the bounds on concrete types
make errors a bit cleaner, they aren't strictly necessary. This means we
can change our generated where-clause to only add bounds to generic type
parameters.

Doing this, though, returns us back to the problem of over-bounding
parameters that don't need to be bounded. To solve this, I added a new
container attribute (based on
[this](dtolnay/syn#422 (comment))
comment and @nicopap's
[comment](#9046 (comment)))
that allows us to pass in a custom where clause to modify what bounds
are added to these type parameters.

This allows us to do stuff like:

```rust
trait Trait {
  type Assoc;
}

// We don't need `T` to be reflectable since we only care about `T::Assoc`.
#[derive(Reflect)]
#[reflect(where T::Assoc: FromReflect)]
struct Foo<T: Trait>(T::Assoc);

#[derive(TypePath)]
struct Bar;

impl Trait for Bar {
  type Assoc = usize;
}

#[derive(Reflect)]
struct Baz {
  a: Foo<Bar>,
}
```

> **Note**
> I also
[tried](dc139ea)
allowing `#[reflect(ignore)]` to be used on the type parameters
themselves, but that proved problematic since the derive macro does not
consume the attribute. This is why I went with the container attribute
approach.

### Alternatives

One alternative could possibly be to just not add reflection bounds
automatically (i.e. only add required bounds like `Send`, `Sync`, `Any`,
and `TypePath`).

The downside here is we add more friction to using reflection, which
already comes with its own set of considerations. This is a potentially
viable option, but we really need to consider whether or not the
ergonomics hit is worth it.

If we did decide to go the more manual route, we should at least
consider something like #5772 to make it easier for users to add the
right bounds (although, this could still become tricky with
`FromReflect` also being automatically derived).

### Open Questions

1. Should we go with this approach or the manual alternative?
2. ~~Should we add a `skip_params` attribute to avoid the `T: 'static`
trick?~~ ~~Decided to go with `custom_where()` as it's the simplest~~
Scratch that, went with a normal where clause
3. ~~`custom_where` bikeshedding?~~ No longer needed since we are using
a normal where clause

### TODO

- [x] Add compile-fail tests

---

## Changelog

- Fixed issue preventing recursive types from deriving `Reflect`
- Changed how where-clause bounds are generated by the `Reflect` derive
macro
- They are now only applied to the type parameters, not to all active
fields
- Added `#[reflect(where T: Trait, U::Assoc: Trait, ...)]` container
attribute

## Migration Guide

When deriving `Reflect`, generic type params that do not need the
automatic reflection bounds (such as `Reflect`) applied to them will
need to opt-out using a custom where clause like: `#[reflect(where T:
Trait, U::Assoc: Trait, ...)]`.

The attribute can define custom bounds only used by the reflection
impls. To simply opt-out all the type params, we can pass in an empty
where clause: `#[reflect(where)]`.

```rust
// BEFORE:
#[derive(Reflect)]
struct Foo<T>(#[reflect(ignore)] T);

// AFTER:
#[derive(Reflect)]
#[reflect(where)]
struct Foo<T>(#[reflect(ignore)] T);
```

---------

Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
tjamaan pushed a commit to tjamaan/bevy that referenced this issue Feb 6, 2024
# Objective

Fixes bevyengine#8965.

#### Background

For convenience and to ensure everything is setup properly, we
automatically add certain bounds to the derived types. The current
implementation does this by taking the types from all active fields and
adding them to the where-clause of the generated impls. I believe this
method was chosen because it won't add bounds to types that are
otherwise ignored.

```rust
#[derive(Reflect)]
struct Foo<T, U: SomeTrait, V> {
  t: T,
  u: U::Assoc,
  #[reflect(ignore)]
  v: [V; 2]
}

// Generates something like:
impl<T, U: SomeTrait, V> for Foo<T, U, V>
where
  // Active:
  T: Reflect,
  U::Assoc: Reflect,

  // Ignored:
  [V; 2]: Send + Sync + Any
{
  // ...
}
```

The self-referential type fails because it ends up using _itself_ as a
type bound due to being one of its own active fields.

```rust
#[derive(Reflect)]
struct Foo {
  foo: Vec<Foo>
}

// Foo where Vec<Foo>: Reflect -> Vec<T> where T: Reflect -> Foo where Vec<Foo>: Reflect -> ...
```

## Solution

We can't simply parse all field types for the name of our type. That
would be both complex and prone to errors and false-positives. And even
if it wasn't, what would we replace the bound with?

Instead, I opted to go for a solution that only adds the bounds to what
really needs it: the type parameters. While the bounds on concrete types
make errors a bit cleaner, they aren't strictly necessary. This means we
can change our generated where-clause to only add bounds to generic type
parameters.

Doing this, though, returns us back to the problem of over-bounding
parameters that don't need to be bounded. To solve this, I added a new
container attribute (based on
[this](dtolnay/syn#422 (comment))
comment and @nicopap's
[comment](bevyengine#9046 (comment)))
that allows us to pass in a custom where clause to modify what bounds
are added to these type parameters.

This allows us to do stuff like:

```rust
trait Trait {
  type Assoc;
}

// We don't need `T` to be reflectable since we only care about `T::Assoc`.
#[derive(Reflect)]
#[reflect(where T::Assoc: FromReflect)]
struct Foo<T: Trait>(T::Assoc);

#[derive(TypePath)]
struct Bar;

impl Trait for Bar {
  type Assoc = usize;
}

#[derive(Reflect)]
struct Baz {
  a: Foo<Bar>,
}
```

> **Note**
> I also
[tried](bevyengine@dc139ea)
allowing `#[reflect(ignore)]` to be used on the type parameters
themselves, but that proved problematic since the derive macro does not
consume the attribute. This is why I went with the container attribute
approach.

### Alternatives

One alternative could possibly be to just not add reflection bounds
automatically (i.e. only add required bounds like `Send`, `Sync`, `Any`,
and `TypePath`).

The downside here is we add more friction to using reflection, which
already comes with its own set of considerations. This is a potentially
viable option, but we really need to consider whether or not the
ergonomics hit is worth it.

If we did decide to go the more manual route, we should at least
consider something like bevyengine#5772 to make it easier for users to add the
right bounds (although, this could still become tricky with
`FromReflect` also being automatically derived).

### Open Questions

1. Should we go with this approach or the manual alternative?
2. ~~Should we add a `skip_params` attribute to avoid the `T: 'static`
trick?~~ ~~Decided to go with `custom_where()` as it's the simplest~~
Scratch that, went with a normal where clause
3. ~~`custom_where` bikeshedding?~~ No longer needed since we are using
a normal where clause

### TODO

- [x] Add compile-fail tests

---

## Changelog

- Fixed issue preventing recursive types from deriving `Reflect`
- Changed how where-clause bounds are generated by the `Reflect` derive
macro
- They are now only applied to the type parameters, not to all active
fields
- Added `#[reflect(where T: Trait, U::Assoc: Trait, ...)]` container
attribute

## Migration Guide

When deriving `Reflect`, generic type params that do not need the
automatic reflection bounds (such as `Reflect`) applied to them will
need to opt-out using a custom where clause like: `#[reflect(where T:
Trait, U::Assoc: Trait, ...)]`.

The attribute can define custom bounds only used by the reflection
impls. To simply opt-out all the type params, we can pass in an empty
where clause: `#[reflect(where)]`.

```rust
// BEFORE:
#[derive(Reflect)]
struct Foo<T>(#[reflect(ignore)] T);

// AFTER:
#[derive(Reflect)]
#[reflect(where)]
struct Foo<T>(#[reflect(ignore)] T);
```

---------

Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants