1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
use syn::spanned::Spanned as _;
use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, LitStr};

use quote::quote;

use proc_macro2::TokenStream;

#[proc_macro_derive(FromMeta)]
pub fn derive_from_meta(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item = parse_macro_input!(item as DeriveInput);

    // get name
    let type_name = item.ident;

    let item = match item.data {
        Data::Struct(s) => s,
        Data::Enum(e) => {
            return Error::new(e.enum_token.span(), "only structs are supported")
                .into_compile_error()
                .into()
        }
        Data::Union(e) => {
            return Error::new(e.union_token.span(), "only structs are supported")
                .into_compile_error()
                .into()
        }
    };

    let fields = match item.fields {
        Fields::Named(fields) => fields.named,
        e => {
            return Error::new(e.span(), "struct can only have named fields")
                .into_compile_error()
                .into()
        }
    };

    // these are the pre-definitions of each field.
    let definitions = fields
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            let ty = &field.ty;

            quote! {
                let mut #name: ::std::option::Option<#ty> = None;
            }
        })
        .collect::<TokenStream>();

    // this is us actually looking for the field
    let searches = fields.iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            let name_str = LitStr::new(&name.to_string(), name.span());

            quote! {
                #name_str => #name = Some(
                    __m.next_value().unwrap_or(
                        // TODO: intelligent spans
                        Err(::macrotk::syn::Error::new(::macrotk::Span::call_site(), ::std::concat!("expected value for ", #name_str)))
                    )?
                ),
            }
        })
        .collect::<TokenStream>();

    // this is us unwrapping all of the fields
    let unwrapper = fields.iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            let name_str = LitStr::new(&name.to_string(), name.span());

            quote! {
                #name: #name.ok_or(::macrotk::syn::Error::new(::macrotk::Span::call_site(), ::std::concat!("missing value for ", #name_str)))?,
            }
        })
        .collect::<TokenStream>();

    let expanded = quote! {
        impl ::macrotk::meta::FromMeta for #type_name {
            fn from_meta(
                __m: ::macrotk::meta::MetaStream,
            ) -> ::std::result::Result<Self, ::macrotk::syn::Error> {
                #definitions

                while let Some(__name) = __m.next_name() {
                    match __name?.as_str() {
                        #searches
                        s => return Err(::macrotk::syn::Error::new(::macrotk::Span::call_site(), ::std::format!("unexpected value: \"{}\"", s))),
                    }
                }

                Ok(#type_name {
                    #unwrapper
                })
            }
        }
    };

    proc_macro::TokenStream::from(expanded)
}