enum_map_derive/
derive_struct.rs

1// SPDX-FileCopyrightText: 2021 - 2022 Kamila Borowska <kamila@borowska.pw>
2// SPDX-FileCopyrightText: 2021 Bruno Corrêa Zimmermann <brunoczim@gmail.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use crate::type_length;
7use proc_macro2::TokenStream;
8use quote::quote;
9use syn::{DataStruct, Fields, FieldsNamed, FieldsUnnamed, Ident, Index};
10
11pub fn generate(name: Ident, data_struct: DataStruct) -> TokenStream {
12    StructGenerator::from_fields(&data_struct.fields).finish(&name)
13}
14
15/// Total length is the product of each member's length. To represent a struct, one can
16/// think of this as representing a little-endian number. First member is simply added, but
17/// next members are multiplied before being added.
18#[derive(Debug)]
19struct StructGenerator {
20    length: TokenStream,
21    from_usize: TokenStream,
22    into_usize: TokenStream,
23}
24
25impl StructGenerator {
26    fn from_fields(fields: &Fields) -> Self {
27        match fields {
28            Fields::Unit => Self::from_unit_fields(),
29            Fields::Unnamed(fields_data) => Self::from_unnamed_fields(fields_data),
30            Fields::Named(fields_data) => Self::from_named_fields(fields_data),
31        }
32    }
33
34    fn from_unit_fields() -> Self {
35        Self {
36            length: quote! { 1usize },
37            from_usize: quote! { Self },
38            into_usize: quote! { 0usize },
39        }
40    }
41
42    fn from_unnamed_fields(fields: &FieldsUnnamed) -> Self {
43        let mut params_from = quote! {};
44        let mut into_usize = quote! { 0usize };
45        let mut length = quote! { 1usize };
46        for (i, field) in fields.unnamed.iter().enumerate() {
47            let ty = &field.ty;
48            let index_ident = Index::from(i);
49            let field_length = type_length(ty);
50
51            into_usize = quote! {
52                (#into_usize + #length * ::enum_map::Enum::into_usize(self.#index_ident))
53            };
54
55            params_from = quote! {
56                #params_from <#ty as ::enum_map::Enum>::from_usize(
57                    value / #length % #field_length
58                ),
59            };
60
61            length = quote! { (#length * #field_length) };
62        }
63
64        let from_usize = quote! { Self(#params_from) };
65        Self {
66            length,
67            from_usize,
68            into_usize,
69        }
70    }
71
72    fn from_named_fields(fields: &FieldsNamed) -> Self {
73        let mut params_from = quote! {};
74        let mut into_usize = quote! { 0usize };
75        let mut length = quote! { 1usize };
76        for field in fields.named.iter() {
77            let ty = &field.ty;
78            let ident = field.ident.as_ref().unwrap();
79            let field_length = type_length(ty);
80
81            into_usize = quote! {
82                (#into_usize + #length * ::enum_map::Enum::into_usize(self.#ident))
83            };
84
85            params_from = quote! {
86                #params_from #ident: <#ty as ::enum_map::Enum>::from_usize(
87                    value / #length % #field_length
88                ),
89            };
90
91            length = quote! { (#field_length * #length) };
92        }
93
94        let from_usize = quote! { Self { #params_from } };
95        Self {
96            length,
97            from_usize,
98            into_usize,
99        }
100    }
101
102    fn finish(&self, name: &Ident) -> TokenStream {
103        let length = &self.length;
104        let from_usize = &self.from_usize;
105        let into_usize = &self.into_usize;
106
107        quote! {
108            #[automatically_derived]
109            impl ::enum_map::Enum for #name {
110                const LENGTH: ::enum_map::usize = #length;
111
112                #[inline]
113                fn from_usize(value: ::enum_map::usize) -> Self {
114                    #from_usize
115                }
116
117                #[inline]
118                fn into_usize(self) -> ::enum_map::usize {
119                    #into_usize
120                }
121            }
122
123            #[automatically_derived]
124            impl<V> ::enum_map::EnumArray<V> for #name {
125                type Array = [V; #length];
126            }
127        }
128    }
129}