#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ]
#![ doc
(
html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico"
) ]
#![ doc( html_root_url = "https://docs.rs/variadic_from_meta/latest/variadic_from_meta/" ) ]
use macro_tools::{ proc_macro2, quote, quote::ToTokens, syn };
struct VariadicFromContext< 'a >
{
name : &'a syn::Ident,
field_types : Vec< &'a syn::Type >,
field_names_or_indices : Vec< proc_macro2::TokenStream >,
is_tuple_struct : bool,
num_fields : usize,
generics : &'a syn::Generics,
}
impl< 'a > VariadicFromContext< 'a >
{
fn new( ast : &'a syn::DeriveInput ) -> syn::Result< Self >
{
let name = &ast.ident;
let ( field_types, field_names_or_indices, is_tuple_struct ) : ( Vec< &syn::Type >, Vec< proc_macro2::TokenStream >, bool ) =
match &ast.data
{
syn::Data::Struct( data ) => match &data.fields
{
syn::Fields::Named( fields ) =>
{
let types = fields.named.iter().map( |f| &f.ty ).collect();
let names = fields
.named
.iter()
.map( |f| f.ident.as_ref().unwrap().to_token_stream() )
.collect();
( types, names, false )
}
syn::Fields::Unnamed( fields ) =>
{
let types = fields.unnamed.iter().map( |f| &f.ty ).collect();
let indices = ( 0..fields.unnamed.len() )
.map( |i| syn::Index::from( i ).to_token_stream() )
.collect();
( types, indices, true )
}
syn::Fields::Unit =>
{
return Err( syn::Error::new_spanned(
ast,
"VariadicFrom can only be derived for structs with named or unnamed fields.",
) )
}
},
_ => return Err( syn::Error::new_spanned( ast, "VariadicFrom can only be derived for structs." ) ),
};
let num_fields = field_types.len();
Ok( Self
{
name,
field_types,
field_names_or_indices,
is_tuple_struct,
num_fields,
generics : &ast.generics,
} )
}
fn constructor( &self, args : &[ proc_macro2::Ident ] ) -> proc_macro2::TokenStream
{
if self.is_tuple_struct
{
quote! { ( #( #args ),* ) }
}
else
{
let named_field_inits = self
.field_names_or_indices
.iter()
.zip( args.iter() )
.map( |( name, arg )| { quote! { #name : #arg } } )
.collect::< Vec< _ > >();
quote! { { #( #named_field_inits ),* } }
}
}
fn constructor_uniform( &self, arg : &proc_macro2::Ident ) -> proc_macro2::TokenStream
{
if self.is_tuple_struct
{
let repeated_args = ( 0..self.num_fields ).map( |_| arg ).collect::< Vec< _ > >();
quote! { ( #( #repeated_args ),* ) }
}
else
{
let named_field_inits = self
.field_names_or_indices
.iter()
.map( |name| { quote! { #name : #arg } } )
.collect::< Vec< _ > >();
quote! { { #( #named_field_inits ),* } }
}
}
fn are_all_field_types_identical( &self ) -> bool
{
if self.num_fields == 0
{
return true;
}
let first_type = &self.field_types[ 0 ];
self
.field_types
.iter()
.all( |ty| ty.to_token_stream().to_string() == first_type.to_token_stream().to_string() )
}
fn are_field_types_identical_from( &self, start_idx : usize ) -> bool
{
if start_idx >= self.num_fields
{
return true;
}
let first_type = &self.field_types[ start_idx ];
self.field_types[ start_idx.. ]
.iter()
.all( |ty| ty.to_token_stream().to_string() == first_type.to_token_stream().to_string() )
}
}
fn is_type_string( ty : &syn::Type ) -> bool
{
ty.to_token_stream().to_string() == quote! { String }.to_string()
}
#[ allow( clippy ::similar_names ) ]
fn generate_from_n_impls
(
context : &VariadicFromContext< '_ >,
from_fn_args : &[ proc_macro2::Ident ],
) -> proc_macro2::TokenStream
{
let mut impls = quote! {};
let name = context.name;
let num_fields = context.num_fields;
let ( impl_generics, ty_generics, where_clause ) = context.generics.split_for_impl();
if num_fields == 1
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let field_type = &context.field_types[ 0 ];
let constructor = context.constructor( core::slice::from_ref( from_fn_arg1 ) );
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From1< #field_type > for #name #ty_generics #where_clause
{
fn from1( #from_fn_arg1 : #field_type ) -> Self
{
Self #constructor
}
}
} );
}
else if num_fields == 2
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let from_fn_arg2 = &from_fn_args[ 1 ];
let field_type1 = &context.field_types[ 0 ];
let field_type2 = &context.field_types[ 1 ];
let constructor = context.constructor( &[ from_fn_arg1.clone(), from_fn_arg2.clone() ] );
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From2< #field_type1, #field_type2 > for #name #ty_generics #where_clause
{
fn from2( #from_fn_arg1 : #field_type1, #from_fn_arg2 : #field_type2 ) -> Self
{
Self #constructor
}
}
} );
}
else if num_fields == 3
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let from_fn_arg2 = &from_fn_args[ 1 ];
let from_fn_arg3 = &from_fn_args[ 2 ];
let field_type1 = &context.field_types[ 0 ];
let field_type2 = &context.field_types[ 1 ];
let field_type3 = &context.field_types[ 2 ];
let constructor = context.constructor( &[ from_fn_arg1.clone(), from_fn_arg2.clone(), from_fn_arg3.clone() ] );
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From3< #field_type1, #field_type2, #field_type3 > for #name #ty_generics #where_clause
{
fn from3( #from_fn_arg1 : #field_type1, #from_fn_arg2 : #field_type2, #from_fn_arg3 : #field_type3 ) -> Self
{
Self #constructor
}
}
} );
}
impls
}
#[ allow( clippy ::similar_names ) ]
fn generate_from_tuple_impl
(
context : &VariadicFromContext< '_ >,
from_fn_args : &[ proc_macro2::Ident ],
) -> proc_macro2::TokenStream
{
let mut impls = quote! {};
let name = context.name;
let num_fields = context.num_fields;
let ( impl_generics, ty_generics, where_clause ) = context.generics.split_for_impl();
if num_fields == 1
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let field_type = &context.field_types[ 0 ];
impls.extend( quote!
{
impl #impl_generics From< #field_type > for #name #ty_generics #where_clause
{
#[ inline( always ) ]
fn from( #from_fn_arg1 : #field_type ) -> Self
{
Self::from1( #from_fn_arg1.clone() )
}
}
} );
}
else if num_fields == 2
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let from_fn_arg2 = &from_fn_args[ 1 ];
let field_type1 = &context.field_types[ 0 ];
let field_type2 = &context.field_types[ 1 ];
let tuple_types = quote! { #field_type1, #field_type2 };
let from_fn_args_pattern = quote! { #from_fn_arg1, #from_fn_arg2 };
impls.extend( quote!
{
impl #impl_generics From< ( #tuple_types ) > for #name #ty_generics #where_clause
{
#[ inline( always ) ]
fn from( ( #from_fn_args_pattern ) : ( #tuple_types ) ) -> Self
{
Self::from2( #from_fn_arg1.clone(), #from_fn_arg2.clone() )
}
}
} );
}
else if num_fields == 3
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let from_fn_arg2 = &from_fn_args[ 1 ];
let from_fn_arg3 = &from_fn_args[ 2 ];
let field_type1 = &context.field_types[ 0 ];
let field_type2 = &context.field_types[ 1 ];
let field_type3 = &context.field_types[ 2 ];
let tuple_types = quote! { #field_type1, #field_type2, #field_type3 };
let from_fn_args_pattern = quote! { #from_fn_arg1, #from_fn_arg2, #from_fn_arg3 };
impls.extend( quote!
{
impl #impl_generics From< ( #tuple_types ) > for #name #ty_generics #where_clause
{
#[ inline( always ) ]
fn from( ( #from_fn_args_pattern ) : ( #tuple_types ) ) -> Self
{
Self::from3( #from_fn_arg1.clone(), #from_fn_arg2.clone(), #from_fn_arg3.clone() )
}
}
} );
}
impls
}
#[ allow( clippy ::similar_names ) ]
#[ allow( clippy ::too_many_lines ) ]
fn generate_convenience_impls
(
context : &VariadicFromContext< '_ >,
from_fn_args : &[ proc_macro2::Ident ],
) -> proc_macro2::TokenStream
{
let mut impls = quote! {};
let name = context.name;
let num_fields = context.num_fields;
let ( impl_generics, ty_generics, where_clause ) = context.generics.split_for_impl();
if num_fields == 2
{
if context.are_all_field_types_identical()
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let field_type = &context.field_types[ 0 ];
let constructor = context.constructor_uniform( from_fn_arg1 );
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From1< #field_type > for #name #ty_generics #where_clause
{
fn from1( #from_fn_arg1 : #field_type ) -> Self
{
Self #constructor
}
}
} );
}
}
else if num_fields == 3
{
let from_fn_arg1 = &from_fn_args[ 0 ];
let from_fn_arg2 = &from_fn_args[ 1 ];
let field_type1 = &context.field_types[ 0 ];
let constructor_uniform_all = context.constructor_uniform( from_fn_arg1 );
if context.are_all_field_types_identical()
{
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From1< #field_type1 > for #name #ty_generics #where_clause
{
fn from1( #from_fn_arg1 : #field_type1 ) -> Self
{
Self #constructor_uniform_all
}
}
} );
}
let field_type1 = &context.field_types[ 0 ];
let field_type2 = &context.field_types[ 1 ];
let constructor_uniform_last_two = if context.is_tuple_struct
{
let arg1 = from_fn_arg1;
let arg2_for_first_use = if is_type_string( context.field_types[ 1 ] )
{
quote! { #from_fn_arg2.clone() }
}
else
{
quote! { #from_fn_arg2 }
};
let arg2_for_second_use = if is_type_string( context.field_types[ 2 ] )
{
quote! { #from_fn_arg2.clone() }
}
else
{
quote! { #from_fn_arg2 }
};
quote! { ( #arg1, #arg2_for_first_use, #arg2_for_second_use ) }
}
else
{
let field_name_or_index1 = &context.field_names_or_indices[ 0 ];
let field_name_or_index2 = &context.field_names_or_indices[ 1 ];
let field_name_or_index3 = &context.field_names_or_indices[ 2 ];
let arg1 = from_fn_arg1;
let arg2_for_first_use = if is_type_string( context.field_types[ 1 ] )
{
quote! { #from_fn_arg2.clone() }
}
else
{
quote! { #from_fn_arg2 }
};
let arg2_for_second_use = if is_type_string( context.field_types[ 2 ] )
{
quote! { #from_fn_arg2.clone() }
}
else
{
quote! { #from_fn_arg2 }
};
quote! { { #field_name_or_index1 : #arg1, #field_name_or_index2 : #arg2_for_first_use, #field_name_or_index3 : #arg2_for_second_use } }
};
if context.are_field_types_identical_from( 1 )
{
impls.extend( quote!
{
impl #impl_generics ::variadic_from::exposed::From2< #field_type1, #field_type2 > for #name #ty_generics #where_clause
{
fn from2( #from_fn_arg1 : #field_type1, #from_fn_arg2 : #field_type2 ) -> Self
{
Self #constructor_uniform_last_two
}
}
} );
}
}
impls
}
#[ proc_macro_derive( VariadicFrom ) ]
pub fn variadic_from_derive( input : proc_macro::TokenStream ) -> proc_macro::TokenStream
{
let ast = syn::parse_macro_input!( input as syn::DeriveInput );
let context = match VariadicFromContext::new( &ast )
{
Ok( c ) => c,
Err( e ) => return e.to_compile_error().into(),
};
if context.num_fields == 0 || context.num_fields > 3
{
return proc_macro::TokenStream::new();
}
let from_fn_args : Vec< proc_macro2::Ident > = ( 0..context.num_fields )
.map( |i| proc_macro2::Ident::new( &format!( "__a{}", i + 1 ), proc_macro2::Span::call_site() ) )
.collect();
let mut impls = quote! {};
impls.extend( generate_from_n_impls( &context, &from_fn_args ) );
impls.extend( generate_from_tuple_impl( &context, &from_fn_args ) );
impls.extend( generate_convenience_impls( &context, &from_fn_args ) );
quote! { #impls }.into()
}