array_as_struct_derive/
lib.rs

1use itertools::multiunzip;
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use proc_macro_crate::{crate_name, FoundCrate};
5use proc_macro_error::{abort, abort_if_dirty, emit_error, proc_macro_error};
6use quote::quote;
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{
10    parse_macro_input, Data, DeriveInput, GenericParam, Ident, LifetimeParam, Member, Token, Type,
11    TypeParam, TypeTuple,
12};
13
14/// A derive-like macro which replaces a field-struct declaration with a
15/// tuple-struct declaration containing a single array. All fields in the
16/// original declaration must share the same type.
17///
18/// This attribute should almost always come before to any `derive` macros.
19#[proc_macro_error]
20#[proc_macro_attribute]
21pub fn array_as_struct(attr: TokenStream, item: TokenStream) -> TokenStream {
22    array_as_struct_helper(attr, item, false)
23}
24
25#[doc(hidden)]
26#[proc_macro_error]
27#[proc_macro_attribute]
28pub fn array_as_struct_doctest(attr: TokenStream, item: TokenStream) -> TokenStream {
29    array_as_struct_helper(attr, item, true)
30}
31
32fn array_as_struct_helper(_attr: TokenStream, item: TokenStream, doctest: bool) -> TokenStream {
33    let found_crate =
34        crate_name("array-as-struct").expect("array-as-struct is present in `Cargo.toml`");
35    let found_crate = match found_crate {
36        FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
37        FoundCrate::Itself if doctest => Ident::new("array_as_struct", Span::call_site()),
38        FoundCrate::Itself => <Token![crate]>::default().into(),
39    };
40
41    let ast = parse_macro_input!(item as DeriveInput);
42    let ast_span = ast.span();
43
44    let DeriveInput {
45        ident,
46        generics,
47        attrs,
48        vis,
49        data,
50    } = ast;
51
52    let data = match data {
53        Data::Struct(data) => data,
54        _ => abort!(ast_span, "only named-field structs are supported"),
55    };
56
57    // Converts `<F, const D: usize>` (sans `<` and `>`) to
58    //          `<F, D>` (sans `<` and `>`)
59    let generic_params = generics.params;
60    let generic_params_no_attr: Punctuated<GenericParam, Token![,]> = generic_params
61        .iter()
62        .map(|gen| match gen {
63            GenericParam::Lifetime(x) => GenericParam::Lifetime(LifetimeParam {
64                attrs: vec![],
65                lifetime: x.lifetime.clone(),
66                colon_token: None,
67                bounds: Punctuated::new(),
68            }),
69            GenericParam::Type(x) => GenericParam::Type(x.clone()),
70            GenericParam::Const(x) => GenericParam::Type(TypeParam {
71                ident: x.ident.clone(),
72                attrs: vec![],
73                colon_token: None,
74                bounds: Punctuated::new(),
75                eq_token: None,
76                default: None,
77            }),
78        })
79        .collect();
80
81    let mut field_ty = None;
82    let field_info = data.fields.into_iter().map(|field| {
83        let ident = match field.ident {
84            Some(ident) => Member::Named(ident.clone()),
85            None => abort!(ast_span, "only named-field structs are supported"),
86        };
87        match field_ty.take() {
88            None => field_ty = Some(field.ty),
89            Some(field_ty) if field_ty != field.ty => {
90                emit_error!(field_ty, "type did not match future fields");
91                abort!(field.ty, "type did not match previous fields");
92            }
93            Some(x) => field_ty = Some(x),
94        }
95        (field.attrs, field.vis, ident)
96    });
97    let (attr_fields, vis_fields, ident_fields): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(field_info);
98    let field_ty = field_ty.unwrap_or(Type::Tuple(TypeTuple {
99        paren_token: Default::default(),
100        elems: Punctuated::new(),
101    }));
102
103    let field_index = 0usize..;
104    let field_count = vis_fields.len();
105    let field_count_str = field_count.to_string();
106
107    abort_if_dirty();
108
109    let v = quote!(
110        #(#attrs)*
111        #[repr(transparent)]
112        #vis struct #ident<#generic_params>(
113            /// The array of
114            #[doc = #field_count_str]
115            /// values
116            pub [#field_ty; #field_count]
117        );
118
119        impl<#generic_params> #ident<#generic_params_no_attr> {
120            #[inline(always)]
121            /// Construct the tuple-struct type from the named-field type
122            #vis const fn from_val(value: <Self as #found_crate::ArrayStruct>::Value) -> Self {
123                #(#attrs)*
124                #vis struct Value<#generic_params>{#(
125                    #(#attr_fields)*
126                    #vis_fields #ident_fields: #field_ty
127                ),*};
128                #[allow(dead_code)]
129                #vis struct Refs<'__array_as_struct, #generic_params>{#(
130                    #(#attr_fields)*
131                    #vis_fields #ident_fields: &'__array_as_struct #field_ty),*
132                };
133                #[allow(dead_code)]
134                #vis struct Muts<'__array_as_struct, #generic_params>{#(
135                    #(#attr_fields)*
136                    #vis_fields #ident_fields: &'__array_as_struct mut #field_ty),*
137                };
138                #[allow(dead_code)]
139                #vis struct Index;
140
141                impl<#generic_params> Value<#generic_params_no_attr> {
142                    ///
143                    #[inline(always)]
144                    pub const fn to_array_struct(self) -> #ident<#generic_params_no_attr> {
145                        #ident::from_val(self)
146                    }
147                }
148
149                impl Index {#(
150                    #[inline(always)]
151                    pub const fn #ident_fields() -> usize { #field_index }
152                )*}
153
154                impl<#generic_params> #found_crate::ArrayStruct for #ident<#generic_params_no_attr> {
155                    type Value = Value<#generic_params_no_attr>;
156                    type Array = [#field_ty; #field_count];
157                    type Refs<'__array_as_struct> = Refs<'__array_as_struct, #generic_params_no_attr>;
158                    type Muts<'__array_as_struct> = Muts<'__array_as_struct, #generic_params_no_attr>;
159                    type Index = Index;
160                    #[inline(always)]
161                    fn from_val(value: Self::Value) -> Self {
162                        <#ident::<#generic_params_no_attr>>::from_val(value)
163                    }
164                    #[inline(always)]
165                    fn val(self) -> Self::Value {
166                        <#ident::<#generic_params_no_attr>>::val(self)
167                    }
168                    #[inline(always)]
169                    fn to_array(self) -> Self::Array {
170                        self.0
171                    }
172                    #[inline(always)]
173                    fn from_array(array: Self::Array) -> Self {
174                        Self(array)
175                    }
176                    #[inline(always)]
177                    fn refs(&'_ self) -> Self::Refs<'_> {
178                        <#ident::<#generic_params_no_attr>>::refs(self)
179                    }
180                    #[inline(always)]
181                    fn muts(&'_ mut self) -> Self::Muts<'_> {
182                        <#ident::<#generic_params_no_attr>>::muts(self)
183                    }
184
185                }
186
187                Self([#(value.#ident_fields),*])
188            }
189
190            #[inline(always)]
191            /// Construct the named-field type from the tuple-struct type
192            #vis const fn val(self) -> <Self as #found_crate::ArrayStruct>::Value {
193                let Self([#(#ident_fields),*]) = self;
194                type Value = <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Value;
195                Value {
196                    #(#ident_fields),*
197                }
198            }
199
200            #[inline(always)]
201            /// Construct the reference-named-field type from the tuple-struct type.
202            #vis const fn refs(&'_ self) -> <Self as #found_crate::ArrayStruct>::Refs<'_> {
203                let Self([#(#ident_fields),*]) = self;
204                type Refs<'__array_as_struct> = <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Refs<'__array_as_struct>;
205                Refs {
206                    #(#ident_fields),*
207                }
208            }
209
210            #[inline(always)]
211            /// Construct the mutable-reference-named-field type from the tuple-struct type
212            #vis fn muts(&'_ mut self) -> <Self as #found_crate::ArrayStruct>::Muts<'_> {
213                let Self([#(#ident_fields),*]) = self;
214                type Muts<'__array_as_struct> = <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Muts<'__array_as_struct>;
215                Muts {
216                    #(#ident_fields),*
217                }
218            }
219        }
220
221        impl<#generic_params> ::core::convert::From<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Value> for #ident<#generic_params_no_attr> {
222            #[inline(always)]
223            fn from(value: <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Value) -> Self {
224                Self::from_val(value)
225            }
226        }
227        impl<#generic_params> ::core::convert::From<#ident<#generic_params_no_attr>> for <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Value {
228            #[inline(always)]
229            fn from(strct: #ident<#generic_params_no_attr>) -> Self {
230                strct.val()
231            }
232        }
233
234        impl<#generic_params> ::core::convert::From<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array> for #ident<#generic_params_no_attr> {
235            #[inline(always)]
236            fn from(array: <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array) -> Self {
237                Self(array)
238            }
239        }
240        impl<#generic_params> ::core::convert::From<#ident<#generic_params_no_attr>> for <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array {
241            #[inline(always)]
242            fn from(strct: #ident<#generic_params_no_attr>) -> Self {
243                strct.0
244            }
245        }
246
247        impl<#generic_params> ::core::convert::AsRef<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array> for #ident<#generic_params_no_attr> {
248            #[inline(always)]
249            fn as_ref(&self) -> &<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array {
250                &self.0
251            }
252        }
253        impl<#generic_params> ::core::convert::AsMut<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array> for #ident<#generic_params_no_attr> {
254            #[inline(always)]
255            fn as_mut(&mut self) -> &mut <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array {
256                &mut self.0
257            }
258        }
259
260        impl<#generic_params> ::core::borrow::Borrow<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array> for #ident<#generic_params_no_attr> {
261            #[inline(always)]
262            fn borrow(&self) -> &<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array {
263                &self.0
264            }
265        }
266        impl<#generic_params> ::core::borrow::BorrowMut<<#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array> for #ident<#generic_params_no_attr> {
267            #[inline(always)]
268            fn borrow_mut(&mut self) -> &mut <#ident::<#generic_params_no_attr> as #found_crate::ArrayStruct>::Array {
269                &mut self.0
270            }
271        }
272
273        impl<#generic_params> ::core::ops::Deref for #ident<#generic_params_no_attr> {
274            type Target = [#field_ty; #field_count];
275            #[inline(always)]
276            fn deref(&self) -> &Self::Target {
277                &self.0
278            }
279        }
280        impl<#generic_params> ::core::ops::DerefMut for #ident<#generic_params_no_attr> {
281            #[inline(always)]
282            fn deref_mut(&mut self) -> &mut Self::Target {
283                &mut self.0
284            }
285        }
286
287        impl<I> core::ops::Index<I> for #ident<#generic_params_no_attr>
288        where [#field_ty; #field_count]: core::ops::Index<I> {
289            type Output = <[#field_ty; #field_count] as core::ops::Index<I>>::Output;
290
291            #[inline(always)]
292            fn index(&self, index: I) -> &Self::Output {
293                &self.0[index]
294            }
295        }
296
297        impl<I> core::ops::IndexMut<I> for #ident<#generic_params_no_attr>
298        where [#field_ty; #field_count]: core::ops::IndexMut<I> {
299            #[inline(always)]
300            fn index_mut(&mut self, index: I) -> &mut Self::Output {
301                &mut self.0[index]
302            }
303        }
304    );
305
306    v.into()
307}