tuco_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, GenericParam, Type};
4struct TucoType {
5    original: Type,
6    tuple: Type,
7}
8
9enum FieldTypeInfo {
10    Tuco(TucoType),
11    Standard(Type),
12}
13
14struct StructFieldEntry {
15    single: bool,
16    index: usize,
17    ident: Option<syn::Ident>,
18    t: FieldTypeInfo,
19}
20
21impl StructFieldEntry {
22    fn get_original_type(&self) -> proc_macro2::TokenStream {
23        match &self.t {
24            FieldTypeInfo::Tuco(tuco) => tuco.original.to_token_stream(),
25            FieldTypeInfo::Standard(x) => x.to_token_stream(),
26        }
27    }
28
29    fn get_tuple_type(&self) -> proc_macro2::TokenStream {
30        match &self.t {
31            FieldTypeInfo::Tuco(tuco) => tuco.tuple.to_token_stream(),
32            FieldTypeInfo::Standard(x) => x.to_token_stream(),
33        }
34    }
35
36    fn get_convert_to_tuple_row(&self) -> proc_macro2::TokenStream {
37        match &self.ident {
38            Some(ident) => match &self.t {
39                FieldTypeInfo::Tuco(_) => {
40                    quote! { self.#ident.into_tuple() }
41                }
42                FieldTypeInfo::Standard(_) => {
43                    quote! { self.#ident }
44                }
45            },
46            None => {
47                let index = syn::Index::from(self.index);
48                match &self.t {
49                    FieldTypeInfo::Tuco(_) => {
50                        quote! { self.#index.into_tuple() }
51                    }
52                    FieldTypeInfo::Standard(_) => {
53                        quote! { self.#index }
54                    }
55                }
56            }
57        }
58    }
59
60    fn get_convert_from_tuple_row(&self) -> proc_macro2::TokenStream {
61        let index = syn::Index::from(self.index);
62        let type_token = self.get_original_type();
63        let input_select = if self.single {
64            quote! { tuple }
65        } else {
66            quote! { tuple.#index }
67        };
68
69        match &self.ident {
70            Some(ident) => match &self.t {
71                FieldTypeInfo::Tuco(_) => {
72                    quote! { #ident : <#type_token as Tuco>::from_tuple(#input_select) }
73                }
74                FieldTypeInfo::Standard(_) => {
75                    quote! { #ident : #input_select }
76                }
77            },
78            None => match &self.t {
79                FieldTypeInfo::Tuco(_) => {
80                    quote! { <#type_token as Tuco>::from_tuple(#input_select) }
81                }
82                FieldTypeInfo::Standard(_) => {
83                    quote! { #input_select }
84                }
85            },
86        }
87    }
88}
89
90fn has_tuco_meta_tag(field: &Field) -> bool {
91    field.attrs.iter().any(|x| x.path().is_ident("tuco"))
92}
93
94fn parse_struct_field(
95    index: usize,
96    field: &Field,
97    single: bool,
98) -> Result<StructFieldEntry, String> {
99    if has_tuco_meta_tag(field) {
100        let type_as_string = field.ty.to_token_stream().to_string().replace(" ", "");
101        let tuple_type = format!("<{} as Tuco>::Tuple", type_as_string);
102        let parsed_type: syn::Type = match syn::parse_str(&tuple_type) {
103            Ok(t) => t,
104            _ => return Err("Failed to parse type".to_string()),
105        };
106
107        Ok(StructFieldEntry {
108            single,
109            index,
110            ident: field.ident.clone(),
111            t: FieldTypeInfo::Tuco(TucoType {
112                original: field.ty.clone(),
113                tuple: parsed_type,
114            }),
115        })
116    } else {
117        Ok(StructFieldEntry {
118            single,
119            index,
120            ident: field.ident.clone(),
121            t: FieldTypeInfo::Standard(field.ty.clone()),
122        })
123    }
124}
125
126#[proc_macro_attribute]
127pub fn tuco(_: TokenStream, _: TokenStream) -> TokenStream {
128    TokenStream::new()
129}
130
131#[proc_macro_derive(Tuco, attributes(tuco))]
132pub fn derive_tuco(input: TokenStream) -> TokenStream {
133    let input = parse_macro_input!(input as DeriveInput);
134
135    // Get the struct or enum name
136    let struct_name = input.ident;
137
138    // Generate the generic parameters
139    let generics = input.generics;
140    let where_clause: &Option<syn::WhereClause> = &generics.where_clause;
141    let generic_params: Vec<_> = generics
142        .params
143        .iter()
144        .map(|param| match param {
145            GenericParam::Type(type_param) => quote! { #type_param },
146            GenericParam::Lifetime(lifetime) => quote! { #lifetime },
147            GenericParam::Const(const_param) => quote! { #const_param },
148        })
149        .collect();
150
151    let impl_statement = quote! { impl<#(#generic_params),*> Tuco for #struct_name<#(#generic_params),*> #where_clause };
152
153    let Data::Struct(data_struct) = &input.data else {
154        panic!("Tuco can only be used with structs.");
155    };
156    let single = data_struct.fields.len() == 1;
157    let fields: Vec<StructFieldEntry> = match &data_struct.fields {
158        Fields::Named(fields_named) => fields_named
159            .named
160            .iter()
161            .enumerate()
162            .map(|(i, f)| parse_struct_field(i, f, single).unwrap())
163            .collect(),
164        Fields::Unnamed(fields) => fields
165            .unnamed
166            .iter()
167            .enumerate()
168            .map(|(i, f)| parse_struct_field(i, f, single).unwrap())
169            .collect(),
170        Fields::Unit => Vec::new(),
171    };
172
173    let tuple_types: Vec<_> = fields
174        .iter()
175        .map(StructFieldEntry::get_tuple_type)
176        .collect();
177
178    let from_tuple_rows: Vec<_> = fields
179        .iter()
180        .map(StructFieldEntry::get_convert_from_tuple_row)
181        .collect();
182
183    let from_tuple_impl = match &data_struct.fields {
184        Fields::Named(_) => match fields.len() {
185            0 => quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{} }},
186            1 => {
187                let first = from_tuple_rows.first().unwrap();
188                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{#first} }}
189            }
190            _ => {
191                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{#(#from_tuple_rows),*} }}
192            }
193        },
194        Fields::Unnamed(_) => match fields.len() {
195            0 => quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name() }},
196            1 => {
197                let first = from_tuple_rows.first().unwrap();
198                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name(#first) }}
199            }
200            _ => {
201                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name(#(#from_tuple_rows),*) }}
202            }
203        },
204        Fields::Unit => {
205            quote! {fn from_tuple(tuple: Self::Tuple) -> Self {#struct_name} }
206        }
207    };
208
209    let into_tuple_rows: Vec<_> = fields
210        .iter()
211        .map(StructFieldEntry::get_convert_to_tuple_row)
212        .collect();
213
214    let into_tuple_impl = match &data_struct.fields {
215        Fields::Unnamed(_) | Fields::Named(_) => match fields.len() {
216            0 => quote! {fn into_tuple(self) -> Self::Tuple { () }},
217            1 => {
218                let first = into_tuple_rows.first().unwrap();
219                quote! {fn into_tuple(self) -> Self::Tuple { #first }}
220            }
221            _ => quote! {fn into_tuple(self) -> Self::Tuple { (#(#into_tuple_rows),*,) }},
222        },
223        Fields::Unit => {
224            quote! {fn into_tuple(self) -> Self::Tuple { () }}
225        }
226    };
227
228    let associated_type_assign = match &data_struct.fields {
229        Fields::Unnamed(_) | Fields::Named(_) => match fields.len() {
230            0 => quote! {type Tuple = ();},
231            1 => {
232                let first = tuple_types.first().unwrap();
233                quote! {type Tuple = #first;}
234            }
235            _ => quote! {type Tuple = (#(#tuple_types),*,);},
236        },
237        Fields::Unit => {
238            quote! {type Tuple = ();}
239        }
240    };
241
242    let decompose_impl = quote! {
243        #[automatically_derived]
244        #impl_statement {
245            #associated_type_assign
246
247            #into_tuple_impl
248
249            #from_tuple_impl
250
251            fn from_tuco<TFromTuco: Tuco<Tuple = Self::Tuple>>(value: TFromTuco) -> Self {
252                Self::from_tuple(value.into_tuple())
253            }
254        }
255    };
256
257    decompose_impl.into()
258}