Procedural Macros, pt. 3: Code

The synstructure crate provides a good starting point for our derive macro via decl_derive.

decl_derive!([propertese, attributes(propertese)] => #[proc_macro_error] derive_propertese);

fn derive_propertese(s: synstructure::Structure) -> proc_macro2::TokenStream {
  // Macro code to be filled in
}

Let’s break down the code snippet above

This essentially allows us to do something like the following, and have our getters automatically generated.

#[derive(propertese)]
struct Person {
    name: String,
    age: u32
}

Let’s take an initial stab at filling in our function derive_propertese.

fn derive_propertese(s: synstructure::Structure) -> proc_macro2::TokenStream {
    let mut propertese_token_stream = proc_macro2::TokenStream::new();
    match &s.ast().data {
        syn::Data::Struct(data_struct) => match &data_struct.fields {
            syn::Fields::Named(fields_named) => {
                fields\_named.named.iter\().for\_each\(|field| {
                    let getter = derive_getter(field);
                    propertese_token_stream.extend(getter);
                })
            },
            _ => panic!("macro propertese can only be called on structs with named fileds")
        }
        _ => panic!("macro propertese can only be called on structs")
    }

    #propertese_token_stream
}

fn derive_getter(
    field: &syn::Field,
) -> proc_macro2::TokenStream {
    proc_macro2::TokenStream::new()
}

Break it down again

While it looks a bit, the snippet above can be broken down neatly

Stay calm. Do not panic

In the above snippet, our code generates a panic anytime it encounters an error. To handle these better, let’s convert these panics into errors. We do so with the help of the macro emit_error (from the crate proc_macro_error), which takes in an error message and a span for the error.

fn derive_propertese(s: synstructure::Structure) -> proc_macro2::TokenStream {
    let span = span_of(&s);
    ...
    match &s.ast().data {
        syn::Data::Struct(data_struct) => match &data_struct.fields {
            syn::Fields::Named(fields_named) => {
                ...
            },
            _ => emit_error!(span, "macro propertese can only be called on structs with named fileds")
        }
        _ => emit_error!(span, "macro propertese can only be called on structs")
    }

    quote! {
      ...
    }
}

fn span_of(s: &synstructure::Structure) -> proc_macro2::Span {
    match &s.ast().data {
        syn::Data::Struct(data_struct) => data_struct.struct_token.span,
        syn::Data::Enum(data_enum) => data_enum.enum_token.span,
        syn::Data::Union(data_union) => data_union.union_token.span,
    }
}

We have a good base here. Let’s follow it up with writing the code to generate our getters in the next part to this series.