enum_map_derive/
derive_struct.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// SPDX-FileCopyrightText: 2021 - 2022 Kamila Borowska <kamila@borowska.pw>
// SPDX-FileCopyrightText: 2021 Bruno CorrĂȘa Zimmermann <brunoczim@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::type_length;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Fields, FieldsNamed, FieldsUnnamed, Ident, Index};

pub fn generate(name: Ident, data_struct: DataStruct) -> TokenStream {
    StructGenerator::from_fields(&data_struct.fields).finish(&name)
}

/// Total length is the product of each member's length. To represent a struct, one can
/// think of this as representing a little-endian number. First member is simply added, but
/// next members are multiplied before being added.
#[derive(Debug)]
struct StructGenerator {
    length: TokenStream,
    from_usize: TokenStream,
    into_usize: TokenStream,
}

impl StructGenerator {
    fn from_fields(fields: &Fields) -> Self {
        match fields {
            Fields::Unit => Self::from_unit_fields(),
            Fields::Unnamed(fields_data) => Self::from_unnamed_fields(fields_data),
            Fields::Named(fields_data) => Self::from_named_fields(fields_data),
        }
    }

    fn from_unit_fields() -> Self {
        Self {
            length: quote! { 1usize },
            from_usize: quote! { Self },
            into_usize: quote! { 0usize },
        }
    }

    fn from_unnamed_fields(fields: &FieldsUnnamed) -> Self {
        let mut params_from = quote! {};
        let mut into_usize = quote! { 0usize };
        let mut length = quote! { 1usize };
        for (i, field) in fields.unnamed.iter().enumerate() {
            let ty = &field.ty;
            let index_ident = Index::from(i);
            let field_length = type_length(ty);

            into_usize = quote! {
                (#into_usize + #length * ::enum_map::Enum::into_usize(self.#index_ident))
            };

            params_from = quote! {
                #params_from <#ty as ::enum_map::Enum>::from_usize(
                    value / #length % #field_length
                ),
            };

            length = quote! { (#length * #field_length) };
        }

        let from_usize = quote! { Self(#params_from) };
        Self {
            length,
            from_usize,
            into_usize,
        }
    }

    fn from_named_fields(fields: &FieldsNamed) -> Self {
        let mut params_from = quote! {};
        let mut into_usize = quote! { 0usize };
        let mut length = quote! { 1usize };
        for field in fields.named.iter() {
            let ty = &field.ty;
            let ident = field.ident.as_ref().unwrap();
            let field_length = type_length(ty);

            into_usize = quote! {
                (#into_usize + #length * ::enum_map::Enum::into_usize(self.#ident))
            };

            params_from = quote! {
                #params_from #ident: <#ty as ::enum_map::Enum>::from_usize(
                    value / #length % #field_length
                ),
            };

            length = quote! { (#field_length * #length) };
        }

        let from_usize = quote! { Self { #params_from } };
        Self {
            length,
            from_usize,
            into_usize,
        }
    }

    fn finish(&self, name: &Ident) -> TokenStream {
        let length = &self.length;
        let from_usize = &self.from_usize;
        let into_usize = &self.into_usize;

        quote! {
            #[automatically_derived]
            impl ::enum_map::Enum for #name {
                const LENGTH: ::enum_map::usize = #length;

                #[inline]
                fn from_usize(value: ::enum_map::usize) -> Self {
                    #from_usize
                }

                #[inline]
                fn into_usize(self) -> ::enum_map::usize {
                    #into_usize
                }
            }

            #[automatically_derived]
            impl<V> ::enum_map::EnumArray<V> for #name {
                type Array = [V; #length];
            }
        }
    }
}