1 | use crate::utils::{ |
2 | add_extra_generic_param, numbered_vars, AttrParams, DeriveType, MultiFieldData, |
3 | State, |
4 | }; |
5 | use proc_macro2::TokenStream; |
6 | use quote::{quote, ToTokens}; |
7 | use syn::{DeriveInput, Result}; |
8 | |
9 | use crate::utils::HashMap; |
10 | |
11 | /// Provides the hook to expand `#[derive(TryInto)]` into an implementation of `TryInto` |
12 | #[allow (clippy::cognitive_complexity)] |
13 | pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> { |
14 | let state = State::with_attr_params( |
15 | input, |
16 | trait_name, |
17 | quote!(::core::convert), |
18 | String::from("try_into" ), |
19 | AttrParams { |
20 | enum_: vec!["ignore" , "owned" , "ref" , "ref_mut" ], |
21 | variant: vec!["ignore" , "owned" , "ref" , "ref_mut" ], |
22 | struct_: vec!["ignore" , "owned" , "ref" , "ref_mut" ], |
23 | field: vec!["ignore" ], |
24 | }, |
25 | )?; |
26 | assert!( |
27 | state.derive_type == DeriveType::Enum, |
28 | "Only enums can derive TryInto" |
29 | ); |
30 | |
31 | let mut variants_per_types = HashMap::default(); |
32 | |
33 | for variant_state in state.enabled_variant_data().variant_states { |
34 | let multi_field_data = variant_state.enabled_fields_data(); |
35 | let MultiFieldData { |
36 | variant_info, |
37 | field_types, |
38 | .. |
39 | } = multi_field_data.clone(); |
40 | for ref_type in variant_info.ref_types() { |
41 | variants_per_types |
42 | .entry((ref_type, field_types.clone())) |
43 | .or_insert_with(Vec::new) |
44 | .push(multi_field_data.clone()); |
45 | } |
46 | } |
47 | |
48 | let mut tokens = TokenStream::new(); |
49 | |
50 | for ((ref_type, ref original_types), ref multi_field_datas) in variants_per_types { |
51 | let input_type = &input.ident; |
52 | |
53 | let pattern_ref = ref_type.pattern_ref(); |
54 | let lifetime = ref_type.lifetime(); |
55 | let reference_with_lifetime = ref_type.reference_with_lifetime(); |
56 | |
57 | let mut matchers = vec![]; |
58 | let vars = &numbered_vars(original_types.len(), "" ); |
59 | for multi_field_data in multi_field_datas { |
60 | let patterns: Vec<_> = |
61 | vars.iter().map(|var| quote!(#pattern_ref #var)).collect(); |
62 | matchers.push( |
63 | multi_field_data.matcher(&multi_field_data.field_indexes, &patterns), |
64 | ); |
65 | } |
66 | |
67 | let vars = if vars.len() == 1 { |
68 | quote!(#(#vars)*) |
69 | } else { |
70 | quote!((#(#vars),*)) |
71 | }; |
72 | |
73 | let output_type = if original_types.len() == 1 { |
74 | format!(" {}" , quote!(#(#original_types)*)) |
75 | } else { |
76 | let types = original_types |
77 | .iter() |
78 | .map(|t| format!(" {}" , quote!(#t))) |
79 | .collect::<Vec<_>>(); |
80 | format!("( {})" , types.join(", " )) |
81 | }; |
82 | let variant_names = multi_field_datas |
83 | .iter() |
84 | .map(|d| { |
85 | format!( |
86 | " {}" , |
87 | d.variant_name.expect("Somehow there was no variant name" ) |
88 | ) |
89 | }) |
90 | .collect::<Vec<_>>() |
91 | .join(", " ); |
92 | let message = |
93 | format!("Only {} can be converted to {}" , variant_names, output_type); |
94 | |
95 | let generics_impl; |
96 | let (_, ty_generics, where_clause) = input.generics.split_for_impl(); |
97 | let (impl_generics, _, _) = if ref_type.is_ref() { |
98 | generics_impl = add_extra_generic_param(&input.generics, lifetime.clone()); |
99 | generics_impl.split_for_impl() |
100 | } else { |
101 | input.generics.split_for_impl() |
102 | }; |
103 | |
104 | let try_from = quote! { |
105 | impl#impl_generics ::core::convert::TryFrom<#reference_with_lifetime #input_type#ty_generics> for |
106 | (#(#reference_with_lifetime #original_types),*) #where_clause { |
107 | type Error = &'static str; |
108 | |
109 | #[allow(unused_variables)] |
110 | #[inline] |
111 | fn try_from(value: #reference_with_lifetime #input_type#ty_generics) -> ::core::result::Result<Self, Self::Error> { |
112 | match value { |
113 | #(#matchers)|* => ::core::result::Result::Ok(#vars), |
114 | _ => ::core::result::Result::Err(#message), |
115 | } |
116 | } |
117 | } |
118 | }; |
119 | try_from.to_tokens(&mut tokens) |
120 | } |
121 | Ok(tokens) |
122 | } |
123 | |