generic_array_struct/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use builder::impl_builder;
4use destr::impl_destr;
5use errs::{
6    panic_only_works_with_structs, panic_only_works_with_structs_with_named_fields,
7    panic_req_all_fields_same_generic, panic_req_single_generic,
8};
9use idents::{
10    array_len_ident, const_with_ident, field_idx_ident, ident_mut, set_ident, with_ident,
11};
12use proc_macro::TokenStream;
13use quote::quote;
14use syn::{
15    parse::{Parse, ParseStream},
16    parse_macro_input,
17    token::{Bracket, Paren, Semi},
18    Attribute, Data, DataStruct, DeriveInput, Expr, ExprPath, Field, Fields, FieldsNamed,
19    FieldsUnnamed, GenericParam, Ident, Type, TypeArray, TypePath, Visibility,
20};
21use utils::path_from_ident;
22
23use crate::{idents::assoc_field_idx_ident, trymap::impl_trymap};
24
25mod builder;
26mod destr;
27mod errs;
28mod idents;
29mod trymap;
30mod utils;
31
32const MACRO_NAME: &str = "generic_array_struct";
33
34#[repr(transparent)]
35struct GenericArrayStructParams(DeriveInput);
36
37/// Accessors
38impl GenericArrayStructParams {
39    #[inline]
40    pub fn struct_vis(&self) -> &Visibility {
41        &self.0.vis
42    }
43
44    #[inline]
45    pub fn struct_ident(&self) -> &Ident {
46        &self.0.ident
47    }
48
49    #[inline]
50    pub fn generic_ident(&self) -> &Ident {
51        let mut generic_iter = self.0.generics.params.iter();
52        let generic = match generic_iter.next() {
53            Some(GenericParam::Type(g)) => g,
54            _ => panic_req_single_generic(),
55        };
56        if generic_iter.next().is_some() {
57            panic_req_single_generic();
58        }
59        &generic.ident
60    }
61
62    #[inline]
63    pub fn data_struct(&self) -> &DataStruct {
64        match &self.0.data {
65            Data::Struct(ds) => ds,
66            _ => panic_only_works_with_structs(),
67        }
68    }
69
70    #[inline]
71    pub fn data_struct_mut(&mut self) -> &mut DataStruct {
72        match &mut self.0.data {
73            Data::Struct(ds) => ds,
74            _ => panic_only_works_with_structs(),
75        }
76    }
77
78    #[inline]
79    pub fn fields_named(&self) -> &FieldsNamed {
80        match &self.data_struct().fields {
81            Fields::Named(f) => f,
82            _ => panic_only_works_with_structs_with_named_fields(),
83        }
84    }
85
86    #[inline]
87    pub fn attrs(&self) -> &[Attribute] {
88        &self.0.attrs
89    }
90}
91
92struct AttrArgs {
93    array_field_vis: Visibility,
94    should_gen_builder: bool,
95    should_gen_destr: bool,
96    should_gen_trymap: bool,
97}
98
99impl Parse for AttrArgs {
100    fn parse(input: ParseStream) -> syn::Result<Self> {
101        let flags = [false; _];
102        let len = flags.len();
103        let [mut should_gen_builder, mut should_gen_destr, mut should_gen_trymap] = flags;
104
105        for _i in 0..len {
106            if !input.peek(Ident) {
107                break;
108            }
109            let id: Ident = input.parse()?;
110            // cant match here, ident is not str
111            if id == "builder" {
112                if should_gen_builder {
113                    panic!("`builder` already set");
114                } else {
115                    should_gen_builder = true;
116                }
117            } else if id == "destr" {
118                if should_gen_destr {
119                    panic!("`destr` already set");
120                } else {
121                    should_gen_destr = true;
122                }
123            } else if id == "trymap" {
124                if should_gen_trymap {
125                    panic!("`trymap` already set");
126                } else {
127                    should_gen_trymap = true;
128                }
129            } else {
130                panic!("Expected one of [`builder`, `destr`, `trymap`]")
131            }
132        }
133
134        if input.is_empty() {
135            return Ok(Self {
136                array_field_vis: Visibility::Inherited,
137                should_gen_builder,
138                should_gen_destr,
139                should_gen_trymap,
140            });
141        }
142
143        let array_field_vis = input.parse()?;
144        Ok(Self {
145            array_field_vis,
146            should_gen_builder,
147            should_gen_destr,
148            should_gen_trymap,
149        })
150    }
151}
152
153/// The main attribute proc macro. See crate docs for usage.
154#[proc_macro_attribute]
155pub fn generic_array_struct(attr_arg: TokenStream, input: TokenStream) -> TokenStream {
156    let AttrArgs {
157        array_field_vis,
158        should_gen_builder,
159        should_gen_destr,
160        should_gen_trymap,
161    } = parse_macro_input!(attr_arg as AttrArgs);
162
163    let input = parse_macro_input!(input as DeriveInput);
164    let mut params = GenericArrayStructParams(input);
165
166    let mut fields_idx_consts = quote! {};
167    let mut fields_idx_assoc_consts = quote! {};
168    let mut accessor_mutator_impls = quote! {};
169    let mut const_with_impls = quote! {};
170    let n_fields =
171        params
172            .fields_named()
173            .named
174            .iter()
175            .enumerate()
176            .fold(0usize, |n_fields, (i, field)| {
177                let expect_same_generic = match &field.ty {
178                    Type::Path(g) => g,
179                    _ => panic_req_all_fields_same_generic(),
180                };
181                if !expect_same_generic
182                    .path
183                    .get_ident()
184                    .map(|id| id == params.generic_ident())
185                    .unwrap_or(false)
186                {
187                    panic_req_all_fields_same_generic();
188                }
189
190                let field_vis = &field.vis;
191                // unwrap-safety: named field checked above
192                let field_ident = field.ident.as_ref().unwrap();
193
194                // pub const RGB_IDX_R: usize = 0;
195                let idx_ident = field_idx_ident(params.struct_ident(), field_ident);
196                fields_idx_consts.extend(quote! {
197                    #field_vis const #idx_ident: usize = #i;
198                });
199
200                // associated consts
201                // pub const IDX_R: usize = 0;
202                let assoc_idx_ident = assoc_field_idx_ident(field_ident);
203                fields_idx_assoc_consts.extend(quote! {
204                    #field_vis const #assoc_idx_ident: usize = #i;
205                });
206
207                // fn r(), r_mut(), set_r(), with_r()
208                let id_mut = ident_mut(field_ident);
209                let set_id = set_ident(field_ident);
210                let with_id = with_ident(field_ident);
211                // preserve attributes such as doc comments on getter method
212                let field_attrs = &field.attrs;
213                accessor_mutator_impls.extend(quote! {
214                    #(#field_attrs)*
215                    #[inline]
216                    #field_vis const fn #field_ident(&self) -> &T {
217                        &self.0[#idx_ident]
218                    }
219
220                    #[inline]
221                    #field_vis const fn #id_mut(&mut self) -> &mut T {
222                        &mut self.0[#idx_ident]
223                    }
224
225                    /// Returns the old field value
226                    #[inline]
227                    #field_vis const fn #set_id(&mut self, val: T) -> T {
228                        core::mem::replace(&mut self.0[#idx_ident], val)
229                    }
230
231                    #[inline]
232                    #field_vis fn #with_id(mut self, val: T) -> Self {
233                        self.0[#idx_ident] = val;
234                        self
235                    }
236                });
237
238                // fn const_with_r()
239                let const_with_id = const_with_ident(field_ident);
240                const_with_impls.extend(quote! {
241                    #[inline]
242                    #field_vis const fn #const_with_id(mut self, val: T) -> Self {
243                        self.0[#idx_ident] = val;
244                        self
245                    }
246                });
247
248                n_fields + 1
249            });
250
251    let len_ident = array_len_ident(params.struct_ident());
252
253    let struct_vis = params.struct_vis();
254    let struct_ident = params.struct_ident();
255    let mut res = quote! {
256        #struct_vis const #len_ident: usize = #n_fields;
257
258        impl<T> #struct_ident<T> {
259            #accessor_mutator_impls
260        }
261
262        impl<T: Copy> #struct_ident<T> {
263            #const_with_impls
264        }
265
266        impl<T> #struct_ident<T> {
267            #struct_vis const LEN: usize = #n_fields;
268
269            #fields_idx_assoc_consts
270        }
271
272        #fields_idx_consts
273    };
274
275    if should_gen_builder {
276        res.extend(impl_builder(&params, struct_vis));
277    }
278
279    if should_gen_destr {
280        res.extend(impl_destr(&params, struct_vis));
281    }
282
283    if should_gen_trymap {
284        res.extend(impl_trymap(&params));
285    }
286
287    // finally, replace the struct defn with a single array field tuple struct
288    params.data_struct_mut().fields = Fields::Unnamed(FieldsUnnamed {
289        paren_token: Paren::default(),
290        unnamed: core::iter::once(Field {
291            vis: array_field_vis,
292            attrs: Vec::new(),
293            mutability: syn::FieldMutability::None,
294            ident: None,
295            colon_token: None,
296            ty: Type::Array(TypeArray {
297                bracket_token: Bracket::default(),
298                elem: Box::new(Type::Path(TypePath {
299                    qself: None,
300                    path: path_from_ident(params.generic_ident().clone()),
301                })),
302                semi_token: Semi::default(),
303                len: Expr::Path(ExprPath {
304                    attrs: Vec::new(),
305                    qself: None,
306                    path: path_from_ident(len_ident),
307                }),
308            }),
309        })
310        .collect(),
311    });
312
313    // extend with original input with modified struct defn
314    let GenericArrayStructParams(input) = params;
315    res.extend(quote! { #input });
316
317    res.into()
318}