Stuck with builder pattern generating with procedural macros for an optional attribute

Hi,

I am doing procedural macro workshop by dtolnay. In the first project where we are supposed to generate builder patterns for a given struct, I am able to solve till
test 5.. You can find all tests here.

In test 6, we have an optional argument, which I am unable to understand where to start solving. Till test 5, the program I wrote is

extern crate proc_macro;

use proc_macro2::{Ident, Span};

use proc_macro::TokenStream;

use quote::{quote, quote_spanned};

use syn::spanned::Spanned;
use syn::{
    parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Fields, GenericParam, Generics,
    Index, Field
};

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let ast = parse_macro_input!(input as DeriveInput);

    // get the field name and types
    // This is taken from https://github.com/dtolnay/syn/issues/516
    let fields = match &ast.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => &fields.named,
        _ => panic!("expected a struct with named fields"),
    };

    let field_name = fields.iter().map(|field| &field.ident);
    let field_type = fields.iter().map(|field| &field.ty);

    let field_name_1 = fields.iter().map(|field| &field.ident);
    let field_type_1 = fields.iter().map(|field| &field.ty);

    let field_name_2 = fields.iter().map(|field| &field.ident);
    let field_type_2 = fields.iter().map(|field| &field.ty);

    let builder_name = Ident::new(
        &format!("{}Builder", ast.ident.to_string().to_owned()),
        Span::call_site(),
    );

    // Used in the quasi-quotation below as `#name`.
    let name = ast.ident;

    // // Add a bound `T: HeapSize` to every type parameter T.
    // let generics = add_trait_bounds(input.generics);
    // let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    // // // Generate an expression to sum up the heap size of each field.
    // let sum = heap_size_sum(&input.data);

    let expanded = quote! {
        // The generated impl.
        impl #name {
            fn builder() -> #builder_name{
                #builder_name {
                    executable: None,
                    args: None,
                    env: None,
                    current_dir: None,
                }

            }
        }

        pub struct #builder_name{
            #(
                #field_name: Option<#field_type>,
            )*
        }

        impl #builder_name{
            #(
                fn #field_name_1(&mut self, #field_name_1: #field_type_1) -> &mut Self{
                    self.#field_name_1 = Some(#field_name_1);
                    self
                }
            )*

            pub fn build(&mut self) -> Result<#name, String> {

                Ok(#name{
                    #(
                        #field_name_2: self.#field_name_2.as_ref().unwrap().clone(),
                    )*
                })
            }
        }
    };

    // Hand the output tokens back to the compiler.
    let output = proc_macro::TokenStream::from(expanded);
    // eprintln!("INPUT: {:#?}", output);
    output
}

Any help.

We want to special-case Option<T> type field to implement an optional field. As the comment of test case 6 describes, we cannot determine if an arbitrary type is Option<T> or not by a proc macro and we have to rely on the syntactic information.
field.ty is a syn::Type and we want to inspect this enum down to if it is syntactically Option<T> or not.
For example, Option<T> is not an array or any "primitive" kind of types, but it is a Type::Path variant. Let's start by this matching:

fn is_option_type(ty: &syn::Type) -> bool {
    let path = match ty {
        syn::Type::Path(path) => path,
        _ => return false,
    };
    unimplemented!("please implement");
}

When you implemented is_option_type correctly, you now can determine a field is optional or not.

let optional_fields = fields.iter().filter(|field| is_option_type(&field.ty)).collect::<Vec<_>>();
let required_fields = fields.iter().filter(|field| !is_option_type(&field.ty)).collect::<Vec<_>>();

(Another way is preserving the order of fields by using quote! macro for each field separately).

And then generate different code for each list.

Note: you are duplicating code such as field_name_1 and field_name_2 but you can instead either use iter.clone() or iter.collect::<Vec<_>>() to use a list multiple times.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.