I'm creating a custom derive that operates on an enum. I'd like to generate code like
match *enum_instance {
EnumName::VariantName1 => "dummy",
EnumName::VariantName2 => "dummy",
EnumName::VariantName3 => "dummy",
}
I've been able to get this to work using code like this:
let enum_name = &ast.ident;
let mut q = quote! {};
q.append_all(e.iter().map(|variant| {
let variant_name = &variant.ident;
quote! { #enum_name::#variant_name => "dummy", }
}));
quote! {
impl FullName for #name {
fn full_name(&self) -> &'static str {
match *self {
#q
}
}
}
}
Requiring the temporary variable and performing the append_all
feels very inelegant. Is there a cleaner solution?
src/main.rs
#[macro_use]
extern crate my_derive;
#[derive(FullName)]
enum Example {
One,
Two,
Three,
}
trait FullName {
fn full_name(&self) -> &'static str;
}
fn main() {
assert_eq!(Example::One.full_name(), "dummy");
}
Cargo.toml
[package]
name = "example"
version = "0.0.0"
[dependencies]
my-derive = { path = "my-derive" }
my-derive/Cargo.toml
[package]
name = "my-derive"
version = "0.0.0"
[lib]
proc-macro = true
[dependencies]
quote = "0.3.12"
syn = "0.11.10"
my-derive/src/lib.rs
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
#[proc_macro_derive(FullName)]
pub fn has_extent_derive(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_macro_input(&s).expect("Unable to parse input");
let gen = impl_full_name(&ast);
gen.parse().expect("Unable to generate")
}
fn impl_full_name(ast: &syn::MacroInput) -> quote::Tokens {
use syn::Body;
let name = &ast.ident;
match ast.body {
Body::Enum(ref e) => {
let enum_name = &ast.ident;
let mut q = quote! {};
q.append_all(e.iter().map(|variant| {
let variant_name = &variant.ident;
quote! { #enum_name::#variant_name => "dummy", }
}));
quote! {
impl FullName for #name {
fn full_name(&self) -> &'static str {
match *self {
#q
}
}
}
}
}
_ => {
panic!("Only implemented for enums");
}
}
}
When you have an iterator of tokens xs = [x1, x2, …, xN]
, you can use the repetition syntax #( #xs & stuff );*
to expand it into x1 & stuff; x2 & stuff; …; xN & stuff
inside the quote!
macro.
Likewise, you could repeat multiple iterators in parallel, e.g. #(#ks => #vs,)*
would become k1 => v1, k2 => v2, …, kN => vN,
.
The repetition syntax of quote
is similar to that of Rust's own macro system, only changing the $
to #
. Ideally, you should be able to write:
Body::Enum(ref e) => {
let enum_name = &ast.ident;
let variant_names = e.iter().map(|variant| &variant.ident);
quote! {
impl FullName for #name {
fn full_name(&self) -> &'static str {
match *self {
#(#enum_name::#variant_names => "dummy",)*
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
}
}
}
}
However, currently quote
requires every variable inside #(…)*
be an iterator: compiling the above will cause the E0599 method-not-found error. This is a known bug of the quote
crate. As explained the bug report, this can be worked around by creating an iterator that repeats enum_name
forever using std::iter::repeat()
:
Body::Enum(ref e) => {
use std::iter::repeat;
let enum_names = repeat(&ast.ident);
// ^~~~~~~~~~~~~~~~~~
// creates the iterator [Example, Example, Example, Example, …]
let variant_names = e.iter().map(|variant| &variant.ident);
quote! {
impl FullName for #name {
fn full_name(&self) -> &'static str {
match *self {
#(#enum_names::#variant_names => "dummy",)*
}
}
}
}
}
This would generate
impl FullName for Example {
fn full_name(&self) -> &'static str {
match *self {
Example::One => "dummy",
Example::Two => "dummy",
Example::Three => "dummy",
}
}
}
in the final output.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With