uniform_array_derive/
lib.rs1extern 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 #[doc = #len_doc]
57 pub const fn len(&self) -> usize {
58 #num_fields
59 }
60
61 #[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 let first_field_type_ty = fields.fields.first().unwrap().ty.clone();
76
77 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 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 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 #[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 unsafe { &*(slice.as_ptr() as *const Self) }
182 }
183
184 #[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 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 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 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 unreachable!()
258 };
259
260 let expanded = quote! {
261 #( #implementations )*
262 };
263 TokenStream::from(expanded)
264}