Skip to content

Commit

Permalink
generic_derive: Worked in the where clause
Browse files Browse the repository at this point in the history
There may be bounds that apply to types other than the
generic parameters, so it makes sense to approve the where clause
in this proposal.

This calls for a struct to organize all the generic-carrying inputs to
`proc_macro_derive_with_generics`, which was mentioned previously as
a question. Name it DeriveGenerics.

The extended derive syntax is now fully specified. It's now clear
to me that adorning each comma-separated item with its own generic
parameters is possible, but
the where clause either needs ugly enclosing delimiters or can
only come last. As it relates to some parameters, make it so
that the where clause can only be appended to a single item.
  • Loading branch information
mzabaluev committed Nov 11, 2019
1 parent eef30d9 commit eaaa257
Showing 1 changed file with 62 additions and 42 deletions.
104 changes: 62 additions & 42 deletions text/0000-generic-derive.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ impl<T: Bound1, U: Bound2> Frob<T> for Foo<U> {
}
```

A `where` clause is also permitted in the single-item form, allowing
bounds that do not apply directly to the parameters, or just as a more
readable alternative to giving bounds in the angle bracket syntax:

```rust
#[derive(<T, U> Frob<T> where T: Bound1, Bar<U>: Bound2)]
struct Foo<U> {
// ...
}
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

Expand All @@ -116,28 +127,56 @@ language that can occur in a trait implementation item between the keywords
> _DeriveItem_ :\
> &nbsp;&nbsp; _Generics_<sup>?</sup> _TypePath_
The procedural macro can optionally support generic parameters to `derive` by
In the single-item form of the `derive` attribute, the item may be
appended by a `where` clause:

> _DeriveAttrInputWithWhere_ :\
> &nbsp;&nbsp; _Generics_ _TypePath_ _WhereClause_
The overall `derive` attribute syntax is:

> _DeriveAttrInput_:\
> &nbsp;&nbsp; _DeriveItem_ (`,` _DeriveItem_)<sup>\*</sup> `,`<sup>?</sup>\
> &nbsp;&nbsp; | _DeriveAttrInputWithWhere_
A procedural macro can optionally support generic parameters to `derive` by
defining an entry point annotated with the `proc_macro_derive_with_generics`
attribute:

```rust
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro::{DeriveGenerics, TokenStream};

#[proc_macro_derive_with_generics(Frob)]
pub fn derive_frob_with_generics(
generics: TokenStream,
trait_args: Option<TokenStream>,
generics: DeriveGenerics,
item: TokenStream,
) -> TokenStream {
// ...
}
```

Invoked in the example above, the function will receive the token stream of
`<T: Bound1, U: Bound2>` as the first argument, a `Some` value with the token
stream of `<T>` as the second argument, and the token stream with the
`struct Foo` item as the third.
The `DeriveGenerics` struct is provided by `proc_macro` as follows:

```rust
pub struct DeriveGenerics {
/// List of impl parameters, including the enclosing angle brackets.
/// Empty if the derive input item has no generics.
pub impl_generics: TokenStream,
/// Generic arguments of the trait path including the angle brackets
/// or functional syntax, or empty if the trait has no generic parameters.
pub trait_args: TokenStream,
/// Where clause, if present.
pub where_clause: Option<TokenStream>,
}
```

Invoked in the example featuring the `where` clause above,
the `DeriveGenerics` parameter of the function will receive:
- the token stream of `<T, U>` in the `impl_generics` member,
- the token stream of `<T>` in the `trait_args` member,
- `Some` with the token stream of `where T: Bound1, Bar<U>: Bound2`
as the `where_clause` member.

If the compiler does not find a matching `proc_macro_derive_with_generics`
symbol in the procedural macro crate that it has resolved for a `derive` item
Expand All @@ -157,8 +196,9 @@ This extension complicates the syntax of the `derive` attribute.

Extending `derive` this way, we can solve its current shortcomings and
open it to more uses and experimentation. The proposed syntax should be
familiar to the developers, as it forms a part of the syntax of the intended
trait impl item.
familiar to the developers, as it forms parts of the syntax of the intended
trait impl item. The same property makes the extended attribute input data
easier to use in the derive macros.

An [earlier proposal][rust-lang/rfcs#2353] to control generic bounds on
derived items introduces two attributes used on the generic parameters of
Expand All @@ -167,8 +207,8 @@ attributes, however, visually distances the declaration from its effect
on the behavior on the `derive` attribute, and in many cases would be
more verbose. It also splits the solution across multiple attributes, whereas
the extended `derive` syntax proposed here is holistic, consistent with the
syntax of the generated impl item to the extent of being a literal
subsequence of it, and may allow further extension also in holistic ways.
syntax of the generated impl item to the extent of informing literal parts
of it, and may allow further extension in similarly holistic ways.
The extension proposed here is opted into by the macro authors if and when
they wish to do so, while the solution proposed in RFC 2353 expects all
macro authors to implement support for the new attributes "so that a consistent
Expand Down Expand Up @@ -200,31 +240,15 @@ sufficient for this RFC as well.

## The syntax

- Is it advisable, or even possible syntactically, to extend the general
`derive` syntax with optional generics for each comma-separated item,
or should this be only permitted as an alternative form of `derive`
with a single item? An alternative combining syntax
`#[derive(<T: Bound> Trait1 + Trait2 + Trait3)]` is also possible,
either standalone or as an item in a comma-separated list.
- It's possible to extend the syntax even further by supporting a `where`
clause, allowing more complex bounds, or just as a more readable alternative
to bounds in the angle bracket syntax:

```rust
#[derive(<Fut1, Fut2, F> Future where
Fut1: TryFuture,
Fut2: TryFuture<Error = Fut1::Error>,
F: FnOnce(<Fut1 as TryFuture>::Ok) -> Fut2,
)]
enum AndThen<Fut1, Fut2, F> {
// ...
}
```

The `where` clause syntax could be chosen as the only available way to
- A combining syntax `#[derive(<T: Bound> Trait1 + Trait2 + Trait3)]` is also
possible, either standalone or as an item in a comma-separated list.
Should it be included while we are at radically extending `derive`, or
should it wait for another stabilization round just to be careful?
- The `where` clause syntax could be chosen as the only available way to
specify generics in preference to the angle bracketed parameter list.
If so, unbounded parameters would look a little weird, though permitted
in the current syntax for `where` clauses:
in the current syntax for `where` clauses (and hey, we have a chance to
legitimize smileys in Rust here):

```rust
#[derive(Unwrap where St: Unwrap, F:)]
Expand All @@ -250,11 +274,6 @@ sufficient for this RFC as well.
and needing a policy on coexistence of the two kinds as per the questions
above (that is, disallow coexistence by uniting both kinds under a single
`proc_macro_derive` registry)?
This may lead to confusion, as the only distinguishing factor here would be
the number of parameters and their types, and two-to-three `TokenStream`
parameters do not exactly jump out and say "generics be here". A more
disciplined struct could be added to the `proc_macro` API for the new
function signature.

# Future possibilities
[future-possibilities]: #future-possibilities
Expand All @@ -266,5 +285,6 @@ to far wider use and experimentation than what is possible today; the
# Acknowledgements
[acknowledgements]: #acknowledgements

Thanks to David Tolnay [@dtolnay](https://github.com/dtolnay) for suggesting
alternative ideas and offering constructive criticism.
Thanks to David Tolnay [@dtolnay](https://github.com/dtolnay) for proposing
the `where` clause, suggesting alternative ideas and offering constructive
criticism.

0 comments on commit eaaa257

Please sign in to comment.