uniform_array_derive/
lib.rs

1extern crate proc_macro;
2
3use darling::{FromDeriveInput, FromField, FromMeta};
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Type};
7
8#[derive(Default, Debug, FromMeta)]
9#[darling(default)]
10struct UniformArrayAttributes {
11    #[darling(rename = "safety_gate")]
12    unsafe_feature: String,
13    docs_rs: Option<String>,
14}
15
16#[derive(Debug, FromField)]
17struct FieldData {
18    ident: Option<syn::Ident>,
19    ty: Type,
20}
21
22#[derive(Debug, FromDeriveInput)]
23#[darling(supports(struct_any), attributes(uniform_array))]
24struct UniformArrayType {
25    ident: syn::Ident,
26    data: darling::ast::Data<darling::util::Ignored, FieldData>,
27    #[darling(flatten)]
28    uniform_array: UniformArrayAttributes,
29}
30
31#[proc_macro_derive(UniformArray, attributes(uniform_array))]
32pub fn derive_uniform_array(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as DeriveInput);
34    let data = UniformArrayType::from_derive_input(&input).expect("Failed to parse input");
35
36    let config = data.uniform_array;
37    let unsafe_feature = config.unsafe_feature;
38
39    let struct_name = &data.ident;
40    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
41
42    let mut implementations = Vec::new();
43
44    if let darling::ast::Data::Struct(fields) = &data.data {
45        let num_fields = fields.fields.len();
46        let is_empty = num_fields == 0;
47
48        let len_doc = format!(" Always `{}`.", num_fields);
49        let is_empty_doc = format!(" Always `{}`.", is_empty);
50
51        implementations.push(quote! {
52            impl #impl_generics #struct_name #type_generics
53            #where_clause
54            {
55                /// Returns the number of fields.
56                #[doc = #len_doc]
57                pub const fn len(&self) -> usize {
58                    #num_fields
59                }
60
61                /// Indicates whether this type is zero-length.
62                #[doc = #is_empty_doc]
63                pub const fn is_empty(&self) -> bool {
64                    #is_empty
65                }
66            }
67        });
68
69        let mut index_match_arms = Vec::new();
70        let mut index_mut_match_arms = Vec::new();
71
72        if fields.fields.is_empty() {
73        } else {
74            // Assume the first field type is the required uniform size type
75            let first_field_type_ty = fields.fields.first().unwrap().ty.clone();
76
77            // HACK: We cannot compare syn::Type instances directly, so we instead compare them by name.
78            let first_field_type = quote!(#first_field_type_ty).to_string();
79
80            for (field_idx, field) in fields.fields.iter().enumerate() {
81                let field_type = &field.ty;
82                let field_type = quote!(#field_type).to_string();
83
84                if let Some(name) = &field.ident {
85                    // named field
86                    if first_field_type != field_type {
87                        let error_message = format!(
88                            "Struct \"{}\" has fields of different types. Expected uniform use of {}, found {} in field \"{}\".",
89                            struct_name,
90                            first_field_type,
91                            field_type,
92                            name
93                        );
94                        return syn::Error::new_spanned(input, error_message)
95                            .to_compile_error()
96                            .into();
97                    }
98
99                    index_match_arms.push(quote! {
100                        #field_idx => &self . #name,
101                    });
102
103                    index_mut_match_arms.push(quote! {
104                        #field_idx => &mut self . #name,
105                    });
106                } else {
107                    // tuple field
108                    if first_field_type != field_type {
109                        let error_message = format!(
110                            "Struct \"{}\" has fields of different types. Expected uniform use of {}, found {} in field .{}.",
111                            struct_name,
112                            first_field_type,
113                            field_type,
114                            field_idx
115                        );
116                        return syn::Error::new_spanned(input, error_message)
117                            .to_compile_error()
118                            .into();
119                    }
120
121                    let i = syn::Index::from(field_idx);
122                    index_match_arms.push(quote! {
123                        #field_idx => &self . #i,
124                    });
125                    index_mut_match_arms.push(quote! {
126                        #field_idx => &mut self . #i,
127                    });
128                };
129            }
130
131            let unsafe_docsrs = if let Some(name) = &config.docs_rs {
132                quote! { #[cfg_attr(#name, doc(cfg(feature = #unsafe_feature)))] }
133            } else {
134                quote! {}
135            };
136
137            implementations.push(quote! {
138                impl #impl_generics core::ops::Index<usize> for #struct_name #type_generics
139                #where_clause
140                {
141                    type Output = #first_field_type_ty;
142
143                    #[allow(clippy::inline_always)]
144                    #[inline(always)]
145                    fn index(&self, index: usize) -> &Self::Output {
146                        match index {
147                            #( #index_match_arms )*
148                            _ => panic!("Index out of bounds: Invalid access of index {index} for type with {} fields.", #num_fields),
149                        }
150                    }
151                }
152
153                impl #impl_generics core::ops::IndexMut<usize> for #struct_name #type_generics
154                #where_clause
155                {
156                    #[allow(clippy::inline_always)]
157                    #[inline(always)]
158                    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
159                        match index {
160                            #( #index_mut_match_arms )*
161                            _ => panic!("Index out of bounds: Invalid access of index {index} for type with {} fields.", #num_fields),
162                        }
163                    }
164                }
165
166                #[cfg(feature = #unsafe_feature)]
167                #unsafe_docsrs
168                impl #impl_generics #struct_name #type_generics
169                #where_clause
170                {
171                    /// Constructs a new instance from a slice.
172                    #[allow(unused)]
173                    #[inline]
174                    pub fn from_slice(slice: &[#first_field_type_ty]) -> &Self {
175                        core::assert_eq!(
176                            slice.len(),
177                            core::mem::size_of::<Self>() / core::mem::size_of::<#first_field_type_ty>()
178                        );
179
180                        // SAFETY: $type_name only contains `$type_param` fields and is `repr(C)`
181                        unsafe { &*(slice.as_ptr() as *const Self) }
182                    }
183
184                    /// Constructs a new instance from a mutable slice.
185                    #[allow(unused)]
186                    #[inline]
187                    pub fn from_mut_slice(slice: &mut [#first_field_type_ty]) -> &mut Self {
188                        core::assert_eq!(
189                            slice.len(),
190                            core::mem::size_of::<Self>() / core::mem::size_of::<#first_field_type_ty>()
191                        );
192
193                        // SAFETY: $type_name only contains `$type_param` fields and is `repr(C)`
194                        unsafe { &mut *(slice.as_mut_ptr() as *mut Self) }
195                    }
196                }
197
198                #[cfg(feature = #unsafe_feature)]
199                #unsafe_docsrs
200                impl #impl_generics core::convert::AsRef<[#first_field_type_ty]> for #struct_name #type_generics
201                #where_clause
202                {
203                    fn as_ref(&self) -> &[#first_field_type_ty] {
204                        unsafe {
205                            // SAFETY: $type_name only contains `$type_param` fields and is `repr(C)`
206                            core::slice::from_raw_parts(
207                                self as *const _ as *const #first_field_type_ty,
208                                core::mem::size_of::<#struct_name #type_generics>()
209                                    / core::mem::size_of::<#first_field_type_ty>(),
210                            )
211                        }
212                    }
213                }
214
215                #[cfg(feature = #unsafe_feature)]
216                #unsafe_docsrs
217                impl #impl_generics core::convert::AsMut<[#first_field_type_ty]> for #struct_name #type_generics
218                #where_clause
219                {
220                    fn as_mut(&mut self) -> &mut [#first_field_type_ty] {
221                        unsafe {
222                            // SAFETY: $type_name only contains `$type_param` fields and is `repr(C)`
223                            core::slice::from_raw_parts_mut(
224                                self as *mut _ as *mut #first_field_type_ty,
225                                core::mem::size_of::<#struct_name #type_generics>()
226                                    / core::mem::size_of::<#first_field_type_ty>(),
227                            )
228                        }
229                    }
230                }
231
232                #[cfg(feature = #unsafe_feature)]
233                #unsafe_docsrs
234                impl #impl_generics core::ops::Deref for #struct_name #type_generics
235                #where_clause
236                {
237                    type Target = [#first_field_type_ty];
238
239                    fn deref(&self) -> &Self::Target {
240                        self.as_ref()
241                    }
242                }
243
244                #[cfg(feature = #unsafe_feature)]
245                #unsafe_docsrs
246                impl #impl_generics core::ops::DerefMut for #struct_name #type_generics
247                #where_clause
248                {
249                    fn deref_mut(&mut self) -> &mut Self::Target {
250                        self.as_mut()
251                    }
252                }
253            });
254        }
255    } else {
256        // Covered by darling's acceptance rules.
257        unreachable!()
258    };
259
260    let expanded = quote! {
261        #( #implementations )*
262    };
263    TokenStream::from(expanded)
264}