Skip to content

Commit 2d8385c

Browse files
committed
Implement ToTokens for ReflectiveInput
1 parent 803380d commit 2d8385c

File tree

3 files changed

+78
-67
lines changed

3 files changed

+78
-67
lines changed

proc_macro_example_derive/src/lib.rs

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,83 +9,20 @@ mod prelude;
99
mod reflective;
1010

1111
use prelude::*;
12-
use reflective::{ReflectiveInput, ReflectiveInputType};
12+
use reflective::ReflectiveInput;
1313

1414
// Only proc macros can be exported in `proc_macro` crates.
1515
#[proc_macro_derive(Reflective)]
1616
pub fn derive_reflective(input: TokenStream) -> TokenStream {
1717
// ^---------- TokenStream is provided by `proc_macro` which is
1818
// automatically added to all `[lib] proc_macro = true`
1919
// crates.
20-
use ReflectiveInputType as RIT;
2120

2221
let data = parse_macro_input!(input as ReflectiveInput);
2322
// ^----------------- This concise syntax is why we implemented `syn::parse::Parse`
2423
// for `ReflectiveInput`.
25-
let item_name = &data.0.ident;
26-
27-
let get_item = |fn_name: TokenStream2, iter_fields: Box<dyn Iterator<Item = String>>| {
28-
// ^-------------------- ^---------- Boxing since it's dynamically dispatched.
29-
// | You can do it without a callback, this just saves code
30-
// | writing.
31-
// |
32-
// |> This could be any token stream. It's unhygenic so we should be careful what
33-
// we call it with as it's supposed to be an indentifier. Fortunately, due to
34-
// everything working in the rust compiler, we'd get Rust compiler errors in case
35-
// we call it with something wrong. That's why it's important to have integration
36-
// tests with proc macros since they can quickly get complicated and mistakes like
37-
// this could be missed.
38-
//
39-
// NOTE: Notice how it's `TokenStream2` instead of `TokenStream`. This is because
40-
// the `quote` crate works with `TokenStream2` and the reason is that the
41-
// `proc_macro2` crate doesn't require the user to have a proc macro crate. This is
42-
// useful for making libraries that work with proc macro code without being proc
43-
// macros themselves (since you can only export proc macros in proc macro crates).
44-
45-
// This syntax is very similar to the traditional declarative macro syntax. The main
46-
// difference is instead of `$` we use `#`. Additionally we can use variables from our
47-
// #[proc_macro] function like so: `#variable`.
48-
quote! {
49-
impl #item_name {
50-
pub const fn #fn_name() -> &'static [&'static str] {
51-
// ^------- We can even substitute identifiers.
52-
53-
&[ #(#iter_fields),* ]
54-
// ^- ********** --- `#(),*` expands similarly to how a declarative macro would
55-
// be expanded.
56-
}
57-
}
58-
}
59-
};
60-
61-
match data.get_input_items() {
62-
RIT::Fields(fields) => get_item(
63-
quote!(get_fields),
64-
//^--------------- we need to quote the identifier to use it.
65-
Box::new(fields.iter().flat_map(|f| &f.ident).map(Ident::to_string)),
66-
// ^------- You can do it with `filter_map()` as well. `flat_map()`
67-
// is just more powerful though, since it combines: mapping,
68-
// filtering and flattening into one callback.
69-
//
70-
// Also, we flat_map it since the struct could also just be a tuple
71-
// struct so `Field.ident` is `Option<Ident>` because of it.
72-
),
73-
RIT::Variants(variants) => get_item(
74-
quote!(get_variants),
75-
Box::new(variants.iter().map(|v| v.ident.to_string())),
76-
),
77-
RIT::UnionFields(union) => get_item(
78-
quote!(get_fields),
79-
Box::new(
80-
union
81-
.named
82-
.iter()
83-
.flat_map(|f| &f.ident)
84-
.map(Ident::to_string),
85-
),
86-
),
87-
}
88-
.into()
8924
//^-- Notice how we convert the `proc_macro2::TokenStream` output of `quote!` to
9025
// `proc_macro::TokenStream`.
26+
27+
data.into_token_stream().into()
9128
}

proc_macro_example_derive/src/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use proc_macro::TokenStream;
99
pub use proc_macro2::TokenStream as TokenStream2;
1010
// ^----------- Personal preference
1111

12-
pub use quote::quote;
12+
pub use quote::{quote, ToTokens};
1313
pub use syn::{
1414
parse::{Parse, ParseStream},
1515
parse_macro_input,

proc_macro_example_derive/src/reflective.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ certain logic parts into their own data structures. While this isn't needed, I s
66
you work with proc macros this way because otherwise it'd quickly become a mess to navigate through.
77
*/
88

9+
use quote::ToTokens;
10+
911
use crate::prelude::*;
1012

1113
/// Represents a item's fields/variants depending on the item type.
@@ -60,3 +62,75 @@ impl Parse for ReflectiveInput {
6062
Ok(Self(input.parse()?))
6163
}
6264
}
65+
66+
impl ToTokens for ReflectiveInput {
67+
fn to_tokens(&self, tokens: &mut TokenStream2) {
68+
use ReflectiveInputType as RIT;
69+
70+
let item_name = &self.0.ident;
71+
72+
let get_item = |fn_name: TokenStream2, iter_fields: Box<dyn Iterator<Item = String>>| {
73+
// ^-------------------- ^---------- Boxing since it's dynamically dispatched.
74+
// | You can do it without a callback, this just saves code
75+
// | writing.
76+
// |
77+
// |> This could be any token stream. It's unhygenic so we should be careful what
78+
// we call it with as it's supposed to be an indentifier. Fortunately, due to
79+
// everything working in the rust compiler, we'd get Rust compiler errors in case
80+
// we call it with something wrong. That's why it's important to have integration
81+
// tests with proc macros since they can quickly get complicated and mistakes like
82+
// this could be missed.
83+
//
84+
// NOTE: Notice how it's `TokenStream2` instead of `TokenStream`. This is because
85+
// the `quote` crate works with `TokenStream2` and the reason is that the
86+
// `proc_macro2` crate doesn't require the user to have a proc macro crate. This is
87+
// useful for making libraries that work with proc macro code without being proc
88+
// macros themselves (since you can only export proc macros in proc macro crates).
89+
90+
// This syntax is very similar to the traditional declarative macro syntax. The main
91+
// difference is instead of `$` we use `#`. Additionally we can use variables from our
92+
// #[proc_macro] function like so: `#variable`.
93+
quote! {
94+
impl #item_name {
95+
pub const fn #fn_name() -> &'static [&'static str] {
96+
// ^------- We can even substitute identifiers.
97+
98+
&[ #(#iter_fields),* ]
99+
// ^- ********** --- `#(),*` expands similarly to how a declarative macro would
100+
// be expanded.
101+
}
102+
}
103+
}
104+
};
105+
106+
let res = match self.get_input_items() {
107+
RIT::Fields(fields) => get_item(
108+
quote!(get_fields),
109+
//^--------------- we need to quote the identifier to use it.
110+
Box::new(fields.iter().flat_map(|f| &f.ident).map(Ident::to_string)),
111+
// ^------- You can do it with `filter_map()` as well. `flat_map()`
112+
// is just more powerful though, since it combines: mapping,
113+
// filtering and flattening into one callback.
114+
//
115+
// Also, we flat_map it since the struct could also just be a tuple
116+
// struct so `Field.ident` is `Option<Ident>` because of it.
117+
),
118+
RIT::Variants(variants) => get_item(
119+
quote!(get_variants),
120+
Box::new(variants.iter().map(|v| v.ident.to_string())),
121+
),
122+
RIT::UnionFields(union) => get_item(
123+
quote!(get_fields),
124+
Box::new(
125+
union
126+
.named
127+
.iter()
128+
.flat_map(|f| &f.ident)
129+
.map(Ident::to_string),
130+
),
131+
),
132+
};
133+
134+
tokens.extend(res);
135+
}
136+
}

0 commit comments

Comments
 (0)