use quote::quote; use syn::spanned::Spanned; pub fn new(input: proc_macro2::TokenStream) -> syn::Result { let input: syn::DeriveInput = syn::parse2(input)?; let ident = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let data_struct = match &input.data { syn::Data::Struct(data) => data, _ => { return Err(syn::Error::new( input.span(), "this macro can only be used on a struct", )) } }; let struct_attrs = attrs(&input)?; if struct_attrs.default { return Ok(quote! { impl #ident { pub fn new() -> #ident { #ident::default() } } }); } let mut args = Vec::new(); let mut arg_fields = Vec::new(); let mut default_fields = Vec::new(); for field in data_struct.fields.iter() { let field_ident = &field.ident; let field_ty = &field.ty; let field_attrs = field_attrs(field)?; match field_attrs.default { None => { args.push(quote! { #field_ident: impl Into<#field_ty> }); arg_fields.push(quote! { #field_ident: #field_ident.into() }); } Some(FieldDefault::Default) => { default_fields.push(quote! { #field_ident: Default::default() }); } Some(FieldDefault::DefaultLit(lit)) => { default_fields.push(quote! { #field_ident: #lit }); } }; } Ok(quote! { impl#impl_generics #ident#ty_generics #where_clause { pub fn new(#(#args),*) -> #ident#ty_generics { #ident { #(#arg_fields,)* #(#default_fields,)* } } } }) } struct StructAttrs { default: bool, } fn attrs(input: &syn::DeriveInput) -> syn::Result { let attr = input.attrs.iter().find(|attr| attr.path.is_ident("new")); let attr = if let Some(attr) = attr { Some(attr.parse_meta()?) } else { None }; let mut default = None; if let Some(attr) = attr { let list = match attr { syn::Meta::List(list) => list, _ => { return Err(syn::Error::new( input.span(), "expected attribute to be a list", )) } }; for item in list.nested.iter() { match item { syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("default") => { default = Some(true) } _ => {} }; } } let default = default.unwrap_or(false); Ok(StructAttrs { default }) } struct FieldAttrs { default: Option, } enum FieldDefault { Default, DefaultLit(syn::Lit), } fn field_attrs(input: &syn::Field) -> syn::Result { let attr = input.attrs.iter().find(|attr| attr.path.is_ident("new")); let attr = if let Some(attr) = attr { Some(attr.parse_meta()?) } else { None }; let mut default = None; if let Some(attr) = attr { let list = match attr { syn::Meta::List(list) => list, _ => { return Err(syn::Error::new( input.span(), "expected attribute to be a list", )) } }; for item in list.nested.iter() { match item { syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("default") => { default = Some(FieldDefault::Default); } syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) if name_value.path.is_ident("default") => { default = Some(FieldDefault::DefaultLit(name_value.lit.clone())); } _ => {} }; } } Ok(FieldAttrs { default }) }