use macro_tools ::prelude :: *;
use macro_tools :: { attr, diag };
#[ allow(clippy ::too_many_lines, clippy ::manual_let_else, clippy ::explicit_iter_loop) ]
pub fn component_model( input: proc_macro ::TokenStream ) -> Result< proc_macro2 ::TokenStream, syn ::Error >
{
let original_input = input.clone();
let parsed = syn ::parse :: < syn ::DeriveInput >( input )?;
let debug = attr ::has_debug( parsed.attrs.iter() )?;
let struct_name = &parsed.ident;
let generics = &parsed.generics;
let ( impl_generics, ty_generics, where_clause ) = generics.split_for_impl();
let data_struct = match &parsed.data
{
syn ::Data ::Struct( data_struct ) => data_struct,
_ => return Err( syn_err!( parsed.span(), "ComponentModel can only be applied to structs" ) ),
};
let fields = match &data_struct.fields
{
syn ::Fields ::Named( fields ) => &fields.named,
_ => return Err( syn_err!( parsed.span(), "ComponentModel requires named fields" ) ),
};
let mut result = proc_macro2 ::TokenStream ::new();
let mut seen_types = std ::collections ::HashSet ::new();
let mut unique_fields = Vec ::new();
for field in fields.iter()
{
let field_type = &field.ty;
let type_string = quote ::quote!( #field_type ).to_string();
if seen_types.insert( type_string )
{
unique_fields.push( field );
}
}
for field in fields.iter()
{
let field_name = field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
let clean_field_name = if field_name_str.starts_with("r#")
{
field_name_str.trim_start_matches("r#")
} else {
&field_name_str
};
let set_method_name = syn ::Ident ::new( &format!( "{clean_field_name}_set" ), field_name.span() );
let with_method_name = syn ::Ident ::new( &format!( "{clean_field_name}_with" ), field_name.span() );
let field_specific_methods = if generics.params.is_empty()
{
quote ::quote!
{
impl #struct_name
{
#[ inline( always ) ]
pub fn #set_method_name < IntoT >( &mut self, component: IntoT )
where
IntoT: Into< #field_type >
{
self.#field_name = component.into();
}
#[ inline( always ) ]
#[ must_use ]
pub fn #with_method_name < IntoT >( mut self, component: IntoT ) -> Self
where
IntoT: Into< #field_type >
{
self.#field_name = component.into();
self
}
}
}
} else {
quote ::quote!
{
impl #impl_generics #struct_name #ty_generics
#where_clause
{
#[ inline( always ) ]
pub fn #set_method_name < IntoT >( &mut self, component: IntoT )
where
IntoT: Into< #field_type >
{
self.#field_name = component.into();
}
#[ inline( always ) ]
#[ must_use ]
pub fn #with_method_name < IntoT >( mut self, component: IntoT ) -> Self
where
IntoT: Into< #field_type >
{
self.#field_name = component.into();
self
}
}
}
};
result.extend( field_specific_methods );
}
for field in unique_fields.iter()
{
let field_name = field.ident.as_ref().unwrap();
let field_type = &field.ty;
let _type_str = quote ::quote!( #field_type ).to_string();
let popular_impls = crate ::popular_types ::generate_popular_type_assigns(
struct_name,
field_name,
field_type,
generics,
&impl_generics,
&ty_generics,
where_clause
);
if popular_impls.is_empty()
{
let assign_impl = if generics.params.is_empty()
{
quote ::quote!
{
impl< IntoT > component_model_types ::Assign< #field_type, IntoT > for #struct_name
where
IntoT: Into< #field_type >
{
#[ inline( always ) ]
fn assign( &mut self, component: IntoT )
{
self.#field_name = component.into();
}
}
}
} else {
quote ::quote!
{
impl< #impl_generics, IntoT > component_model_types ::Assign< #field_type, IntoT > for #struct_name #ty_generics
where
IntoT: Into< #field_type >,
#where_clause
{
#[ inline( always ) ]
fn assign( &mut self, component: IntoT )
{
self.#field_name = component.into();
}
}
}
};
result.extend( assign_impl );
}
else
{
for impl_tokens in popular_impls
{
result.extend( impl_tokens );
}
}
}
for field in unique_fields.iter()
{
let field_name = field.ident.as_ref().unwrap();
let field_type = &field.ty;
let _component_from_impl = quote ::quote!
{
impl From< &#struct_name #ty_generics > for #field_type
where
#field_type: Clone,
#where_clause
{
#[ inline( always ) ]
fn from( src: &#struct_name #ty_generics ) -> Self
{
src.#field_name.clone()
}
}
};
}
if debug
{
let about = format!("derive: ComponentModel\nstructure: {struct_name}");
diag ::report_print(about, original_input, &result);
}
Ok( result )
}