diff options
Diffstat (limited to 'vendor/derive_more/src/add_like.rs')
-rw-r--r-- | vendor/derive_more/src/add_like.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/vendor/derive_more/src/add_like.rs b/vendor/derive_more/src/add_like.rs new file mode 100644 index 000000000..5d435f6ec --- /dev/null +++ b/vendor/derive_more/src/add_like.rs @@ -0,0 +1,139 @@ +use crate::add_helpers::{struct_exprs, tuple_exprs}; +use crate::utils::{ + add_extra_type_param_bound_op_output, field_idents, named_to_vec, numbered_vars, + unnamed_to_vec, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use std::iter; +use syn::{Data, DataEnum, DeriveInput, Field, Fields, Ident}; + +pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream { + let trait_name = trait_name.trim_end_matches("Self"); + let trait_ident = Ident::new(trait_name, Span::call_site()); + let method_name = trait_name.to_lowercase(); + let method_ident = Ident::new(&method_name, Span::call_site()); + let input_type = &input.ident; + + let generics = add_extra_type_param_bound_op_output(&input.generics, &trait_ident); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let (output_type, block) = match input.data { + Data::Struct(ref data_struct) => match data_struct.fields { + Fields::Unnamed(ref fields) => ( + quote!(#input_type#ty_generics), + tuple_content(input_type, &unnamed_to_vec(fields), &method_ident), + ), + Fields::Named(ref fields) => ( + quote!(#input_type#ty_generics), + struct_content(input_type, &named_to_vec(fields), &method_ident), + ), + _ => panic!("Unit structs cannot use derive({})", trait_name), + }, + Data::Enum(ref data_enum) => ( + quote!(::core::result::Result<#input_type#ty_generics, &'static str>), + enum_content(input_type, data_enum, &method_ident), + ), + + _ => panic!("Only structs and enums can use derive({})", trait_name), + }; + + quote!( + impl#impl_generics ::core::ops::#trait_ident for #input_type#ty_generics #where_clause { + type Output = #output_type; + #[inline] + fn #method_ident(self, rhs: #input_type#ty_generics) -> #output_type { + #block + } + } + ) +} + +fn tuple_content<T: ToTokens>( + input_type: &T, + fields: &[&Field], + method_ident: &Ident, +) -> TokenStream { + let exprs = tuple_exprs(fields, method_ident); + quote!(#input_type(#(#exprs),*)) +} + +fn struct_content( + input_type: &Ident, + fields: &[&Field], + method_ident: &Ident, +) -> TokenStream { + // It's safe to unwrap because struct fields always have an identifier + let exprs = struct_exprs(fields, method_ident); + let field_names = field_idents(fields); + + quote!(#input_type{#(#field_names: #exprs),*}) +} + +#[allow(clippy::cognitive_complexity)] +fn enum_content( + input_type: &Ident, + data_enum: &DataEnum, + method_ident: &Ident, +) -> TokenStream { + let mut matches = vec![]; + let mut method_iter = iter::repeat(method_ident); + + for variant in &data_enum.variants { + let subtype = &variant.ident; + let subtype = quote!(#input_type::#subtype); + + match variant.fields { + Fields::Unnamed(ref fields) => { + // The patern that is outputted should look like this: + // (Subtype(left_vars), TypePath(right_vars)) => Ok(TypePath(exprs)) + let size = unnamed_to_vec(fields).len(); + let l_vars = &numbered_vars(size, "l_"); + let r_vars = &numbered_vars(size, "r_"); + let method_iter = method_iter.by_ref(); + let matcher = quote! { + (#subtype(#(#l_vars),*), + #subtype(#(#r_vars),*)) => { + ::core::result::Result::Ok(#subtype(#(#l_vars.#method_iter(#r_vars)),*)) + } + }; + matches.push(matcher); + } + Fields::Named(ref fields) => { + // The patern that is outputted should look like this: + // (Subtype{a: __l_a, ...}, Subtype{a: __r_a, ...} => { + // Ok(Subtype{a: __l_a.add(__r_a), ...}) + // } + let field_vec = named_to_vec(fields); + let size = field_vec.len(); + let field_names = &field_idents(&field_vec); + let l_vars = &numbered_vars(size, "l_"); + let r_vars = &numbered_vars(size, "r_"); + let method_iter = method_iter.by_ref(); + let matcher = quote! { + (#subtype{#(#field_names: #l_vars),*}, + #subtype{#(#field_names: #r_vars),*}) => { + ::core::result::Result::Ok(#subtype{#(#field_names: #l_vars.#method_iter(#r_vars)),*}) + } + }; + matches.push(matcher); + } + Fields::Unit => { + let message = format!("Cannot {}() unit variants", method_ident); + matches.push(quote!((#subtype, #subtype) => ::core::result::Result::Err(#message))); + } + } + } + + if data_enum.variants.len() > 1 { + // In the strange case where there's only one enum variant this is would be an unreachable + // match. + let message = format!("Trying to {} mismatched enum variants", method_ident); + matches.push(quote!(_ => ::core::result::Result::Err(#message))); + } + quote!( + match (self, rhs) { + #(#matches),* + } + ) +} |