use proc_macro_error::abort; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics}; pub fn derive_into_command_definition_impl( item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let DeriveInput { ident, data, generics, .. }: DeriveInput = parse_macro_input!(item as DeriveInput); let data = match data { Data::Struct(data) => data, Data::Enum(data) => abort!( data.enum_token, "IntoCommandDefinition not supported for enum" ), Data::Union(data) => abort!( data.union_token, "IntoCommandDefinition not supported for union" ), }; let generics = add_trait_bounds(generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let num_fields = data.fields.len(); let create_param_stream = match &data.fields { Fields::Named(fields) => { let field_entries = fields.named.iter().map(|field| { let name = field.ident.clone().map(|id| id.to_string()); let field_type = &field.ty; quote_spanned! { field.span() => parameters.push(api::messages::command::CommandParameterDefinition { name: #name.to_string(), data_type: <#field_type as api::data_type::ToDataType>::DATA_TYPE, }); } }); quote! { #(#field_entries)* } } Fields::Unnamed(fields) => abort!( fields, "IntoCommandDefinition not supported for unnamed structs" ), Fields::Unit => quote! {}, }; let parse_param_stream = match &data.fields { Fields::Named(fields) => { let field_entries = fields.named.iter().map(|field| { let name = &field.ident; let name_string = field.ident.clone().map(|id| id.to_string()); let field_type = &field.ty; quote_spanned! { field.span() => let #name: #field_type = (*command .parameters .get(#name_string) .ok_or_else(|| api::messages::command::IntoCommandDefinitionError::ParameterMissing(#name_string.to_string()))?) .try_into() .map_err(|_| api::messages::command::IntoCommandDefinitionError::MismatchedType { parameter: #name_string.to_string(), expected: <#field_type as api::data_type::ToDataType>::DATA_TYPE, })?; } }); quote! { #(#field_entries)* } } Fields::Unnamed(_) => unreachable!("Already checked this"), Fields::Unit => quote! {}, }; let param_name_stream = match &data.fields { Fields::Named(fields) => { let field_entries = fields.named.iter().map(|field| { let name = &field.ident; quote_spanned! { field.span() => #name, } }); quote! { #(#field_entries)* } } Fields::Unnamed(_) => unreachable!("Already checked this"), Fields::Unit => quote! {}, }; let result = quote! { impl #impl_generics api::messages::command::IntoCommandDefinition for #ident #ty_generics #where_clause { fn create(name: std::string::String) -> api::messages::command::CommandDefinition { let mut parameters = std::vec::Vec::with_capacity( #num_fields ); #create_param_stream api::messages::command::CommandDefinition { name: name, parameters: parameters, } } fn parse(command: api::messages::command::Command) -> core::result::Result { #parse_param_stream Ok(Self { #param_name_stream }) } } }; result.into() } fn add_trait_bounds(mut generics: Generics) -> Generics { for param in &mut generics.params { if let GenericParam::Type(ref mut type_param) = *param { type_param .bounds .push(parse_quote!(api::data_type::ToDataType)); type_param.bounds.push(parse_quote!( core::convert::TryFrom )); } } generics }