named_array/
lib.rs

1//! This crate provides the [`derive@named_array`] derive macro, which allows you to access fields of a
2//! struct as if they were elements of an array.
3//! This provides an impl's of [`Index`] and [`IndexMut`], which translates from a `usize` index to the fields, in the
4//! order in which they appear.
5//!
6//! The type of all the fields must be the same, and written identically.
7//! For example, if one field is `Option<()>`, and another is `core::option::Option<()>`, the code
8//! will be rejected.
9//! This is because type information does not exist at the time of macro expansion, so there is no
10//! way to confirm that the two refer to the same type.
11//!
12//! Indexing will panic if the index is out of bounds.
13//!
14//! # Example
15//! ```rust
16//! # use named_array::named_array;
17//! #[derive(named_array)]
18//! struct Example {
19//!     a: u32,
20//!     b: u32,
21//!     c: u32,
22//! }
23//!
24//! # fn main() {
25//! let example = Example { a: 1, b: 2, c: 3 };
26//! assert_eq!(example[0], example.a);
27//! assert_eq!(example[1], example.b);
28//! assert_eq!(example[2], example.c);
29//! # }
30//! ```
31//!
32//! # Tuple structs
33//!
34//! This can be used with tuple structs as well.
35//! However, you may be better off using `struct Foo([u32; 3])` instead of `struct Foo(u32, u32, u32)`.
36//!
37//! ```rust
38//! # use named_array::named_array;
39//! #[derive(named_array)]
40//! struct Example(u32, u32, u32);
41//! # fn main() {
42//! let example = Example(1, 2, 3);
43//! assert_eq!(example[0], example.0);
44//! assert_eq!(example[1], example.1);
45//! assert_eq!(example[2], example.2);
46//! # }
47//! ```
48//!
49//! [`Index`]: ::core::ops::Index
50//! [`IndexMut`]: ::core::ops::IndexMut
51
52use quote::quote;
53
54/// See the [crate] level documentation.
55#[proc_macro_derive(named_array)]
56pub fn named_array(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
57    let source = syn::parse_macro_input!(input as syn::DeriveInput);
58
59    let (name, fields) = if let syn::Data::Struct(data) = source.data {
60        (source.ident, data.fields)
61    } else {
62        panic!("Only structs are supported");
63    };
64
65    match fields {
66        syn::Fields::Named(fields) => make_named(name, fields),
67        syn::Fields::Unnamed(fields) => make_unnamed(name, fields),
68        _ => panic!("unit structs are not supported"),
69    }
70}
71
72fn make_named(name: syn::Ident, fields: syn::FieldsNamed) -> proc_macro::TokenStream {
73    let mut fields = fields.named.iter();
74
75    let mut errs = Vec::new();
76    let mut names = Vec::new();
77    let ty = fields
78        .next()
79        .map(|f| {
80            names.push(f.ident.as_ref().unwrap());
81            &f.ty
82        })
83        .expect("Expected at least one field");
84    for f in fields {
85        if f.ty != *ty {
86            errs.push(syn::Error::new_spanned(
87                &f.ty,
88                "All fields must have the same type",
89            ));
90        }
91        names.push(f.ident.as_ref().unwrap());
92    }
93
94    if !errs.is_empty() {
95        let errs = errs.into_iter().map(|e| e.to_compile_error());
96
97        // If there are any errors, return a dummy impl to avoid a flood of errors where indexing
98        // gets used.
99        return quote! {
100            #(#errs)*
101
102            impl ::core::ops::Index<usize> for #name {
103                type Output = #ty;
104                fn index(&self, _: usize) -> &Self::Output {
105                    unimplemented!("Unable to generate code due to previous errors");
106                }
107            }
108
109            impl ::core::ops::IndexMut<usize> for #name {
110                fn index_mut(&mut self, _: usize) -> &mut Self::Output {
111                    unimplemented!("Unable to generate code due to previous errors");
112                }
113            }
114        }
115        .into();
116    }
117
118    let len = names.len();
119    let panic_msg = format!("index out of bounds: the len is {len} but the index is {{}}");
120    let range1 = 0usize..;
121    let range2 = 0usize..;
122
123    quote! {
124        impl ::core::ops::Index<usize> for #name {
125            type Output = #ty;
126            fn index(&self, index: usize) -> &Self::Output {
127                match index {
128                    #(
129                        #range1 => &self.#names,
130                    )*
131                    i => panic!(#panic_msg, i),
132                }
133            }
134        }
135
136        impl ::core::ops::IndexMut<usize> for #name {
137            fn index_mut(&mut self, index: usize) -> &mut Self::Output {
138            match index {
139                #(
140                    #range2 => &mut self.#names,
141                )*
142                i => panic!(#panic_msg, i),
143            }
144            }
145        }
146    }
147    .into()
148}
149
150fn make_unnamed(name: syn::Ident, fields: syn::FieldsUnnamed) -> proc_macro::TokenStream {
151    let mut fields = fields.unnamed.iter();
152
153    let len = fields.len();
154    let mut errs = Vec::new();
155    let ty = fields
156        .next()
157        .map(|f| &f.ty)
158        .expect("Expected at least one field");
159    for f in fields {
160        if f.ty != *ty {
161            errs.push(syn::Error::new_spanned(
162                &f.ty,
163                "All fields must have the same type",
164            ));
165        }
166    }
167
168    if !errs.is_empty() {
169        let errs = errs.into_iter().map(|e| e.to_compile_error());
170        // If there are any errors, return a dummy impl to avoid a flood of errors where indexing
171        // gets used.
172        return quote! {
173            #(#errs)*
174            impl ::core::ops::Index<usize> for #name {
175                type Output = #ty;
176                fn index(&self, _: usize) -> &Self::Output {
177                    unimplemented!("Unable to generate code due to previous errors");
178                }
179            }
180            impl ::core::ops::IndexMut<usize> for #name {
181                fn index_mut(&mut self, _: usize) -> &mut Self::Output {
182                    unimplemented!("Unable to generate code due to previous errors");
183                }
184            }
185        }
186        .into();
187    }
188
189    let panic_msg = format!("index out of bounds: the len is {len} but the index is {{}}");
190    let range1 = 0usize..len;
191    let range2 = 0usize..len;
192    let index1 = (0usize..len).map(syn::Index::from);
193    let index2 = (0usize..len).map(syn::Index::from);
194
195    quote! {
196        impl ::core::ops::Index<usize> for #name {
197            type Output = #ty;
198            fn index(&self, index: usize) -> &Self::Output {
199                match index {
200                    #( #range1 => &self.#index1, )*
201                    i => panic!(#panic_msg, i),
202                }
203            }
204        }
205        impl ::core::ops::IndexMut<usize> for #name {
206            fn index_mut(&mut self, index: usize) -> &mut Self::Output {
207                match index {
208                    #( #range2 => &mut self.#index2, )*
209                    i => panic!(#panic_msg, i),
210                }
211            }
212        }
213    }
214    .into()
215}