How do I get the inner type of an Option field in a procedural macro?

282 Views Asked by At

I'm trying to figure out what the inner type is in an Option type. I'm creating a derive macro called Builder

Say I have this struct:

#[derive(Debug, Default, Builder)]
pub struct Test {
    foo: Option<String>,
}

Then I want to refer to the String part. I figured out how to check if it's an Option by reading this question: How can a procedural macro check a generic type for Option<Option<T>> and flatten it down to a single Option?

But I can only see that the type is an Option, not what its generics are. How do I find them? It must be stored somewhere

This is a small portion of what I have:

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast = syn::parse_macro_input!(input as DeriveInput);
    impl_builder(&ast).into()
}

fn impl_builder(ast: &syn::DeriveInput) -> TokenStream {
    let fields = get_fields(&ast.data);
    let setters = fields.named.iter().map(|field| {
        let name = field.ident.as_ref().unwrap();
        let ty = &field.ty;
        if is_option(&ty) {
            // ----------------------------------------------------------
            // | I want to know what the inner type of Test.foo is here |
            // | What to do here to get String?                         |
            // ----------------------------------------------------------
        } else {
            // Default behaviour
            let function_signature = quote!(pub fn #name (self, input: #ty) -> Self);
            quote!(
                #function_signature {
                    let mut data = self.data;
                    data.#name = input;
                    Self {
                        data
                    }
                }
            )
        }
    });

    quote!{
        #(#setters)*
    }
}

fn get_fields(data: &Data) -> FieldsNamed {
    if let Data::Struct(strct) = data {
        if let Fields::Named(fields) = &strct.fields {
            fields.to_owned()
        } else {
            panic!("Can only handle named fields")
        }
    } else {
        panic!("Can only handle structs");
    }
}
1

There are 1 best solutions below

0
Chayim Friedman On

A little(?) juggling and you get it:

fn option_type(ty: &syn::Type) -> Option<&syn::Type> {
    let syn::Type::Path(ty) = ty else { return None };
    if ty.qself.is_some() {
        return None;
    }

    let ty = &ty.path;

    if ty.segments.is_empty() || ty.segments.last().unwrap().ident.to_string() != "Option" {
        return None;
    }

    if !(ty.segments.len() == 1
        || (ty.segments.len() == 3
            && ["core", "std"].contains(&ty.segments[0].ident.to_string().as_str())
            && ty.segments[1].ident.to_string() == "option"))
    {
        return None;
    }

    let last_segment = ty.segments.last().unwrap();
    let syn::PathArguments::AngleBracketed(generics) = &last_segment.arguments else {
        return None;
    };
    if generics.args.len() != 1 {
        return None;
    }
    let syn::GenericArgument::Type(inner_type) = &generics.args[0] else {
        return None;
    };

    Some(inner_type)
}