simdnbt_derive/
lib.rs

1mod attrs;
2
3use attrs::{parse_field_attrs, parse_unit_attrs};
4use quote::quote;
5use syn::{parse_macro_input, DeriveInput};
6
7#[proc_macro_derive(Deserialize, attributes(simdnbt))]
8pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9    let input = parse_macro_input!(input as DeriveInput);
10
11    let ident = input.ident;
12
13    let field_deserializer;
14
15    match input.data {
16        syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
17            syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
18                let mut field_deserializers = Vec::<proc_macro2::TokenStream>::new();
19                for field in named {
20                    let struct_field_name = field.ident.unwrap();
21
22                    let mut field_attrs = parse_field_attrs(&field.attrs);
23
24                    let field_name = field_attrs
25                        .rename
26                        .take()
27                        .unwrap_or_else(|| struct_field_name.to_string());
28
29                    if field_attrs.flatten {
30                        field_deserializers.push(quote! {
31                            #struct_field_name: simdnbt::Deserialize::from_compound(nbt)?,
32                        })
33                    } else {
34                        let debug_ident = format!("{ident}::{struct_field_name}");
35
36                        field_deserializers.push(quote! {
37                            #struct_field_name: simdnbt::FromNbtTag::from_optional_nbt_tag(
38                                nbt.get(#field_name)
39                            )?.ok_or(simdnbt::DeserializeError::MismatchedFieldType(#debug_ident.to_owned()))?
40                        });
41                    }
42                }
43                field_deserializer = quote! {
44                    Self {
45                        #(#field_deserializers),*
46                    };
47                }
48            }
49            syn::Fields::Unnamed(unnamed) => {
50                assert!(
51                    unnamed.unnamed.len() == 1,
52                    "Unnamed structs must only have one field",
53                );
54
55                field_deserializer = quote! {
56                    Self(simdnbt::Deserialize::from_compound(nbt)?)
57                };
58            }
59            syn::Fields::Unit => todo!(),
60        },
61        syn::Data::Enum(_) => todo!(),
62        syn::Data::Union(_) => todo!(),
63    }
64
65    let generics = input.generics;
66    let where_clause = &generics.where_clause;
67
68    let struct_attrs = attrs::parse_struct_attrs(&input.attrs);
69
70    let extra_checks = if struct_attrs.deny_unknown_fields {
71        quote! {
72            if !nbt.is_empty() {
73                return Err(simdnbt::DeserializeError::UnknownField(nbt.keys().next().unwrap().to_string()));
74            }
75        }
76    } else {
77        quote! {}
78    };
79
80    let output = quote! {
81        impl #generics simdnbt::Deserialize for #ident #generics #where_clause {
82            fn from_compound(mut nbt: simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
83                let value = #field_deserializer;
84                #extra_checks
85                Ok(value)
86            }
87        }
88    };
89
90    output.into()
91}
92
93#[proc_macro_derive(Serialize, attributes(simdnbt))]
94pub fn serialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
95    let input = parse_macro_input!(input as DeriveInput);
96
97    let ident = input.ident;
98
99    let mut field_serializers = Vec::<proc_macro2::TokenStream>::new();
100
101    match input.data {
102        syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
103            syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
104                for field in named {
105                    let struct_field_name = field.ident.unwrap();
106
107                    let mut field_attrs = parse_field_attrs(&field.attrs);
108
109                    let field_name = field_attrs
110                        .rename
111                        .take()
112                        .unwrap_or_else(|| struct_field_name.to_string());
113
114                    field_serializers.push(quote! {
115                        if let Some(item) = simdnbt::ToNbtTag::to_optional_nbt_tag(self.#struct_field_name) {
116                            nbt.insert(#field_name, item);
117                        }
118                    });
119                }
120            }
121            syn::Fields::Unnamed(_) => todo!(),
122            syn::Fields::Unit => todo!(),
123        },
124        syn::Data::Enum(_) => todo!(),
125        syn::Data::Union(_) => todo!(),
126    }
127
128    let generics = input.generics;
129    let where_clause = &generics.where_clause;
130
131    let output = quote! {
132        impl #generics simdnbt::Serialize for #ident #generics #where_clause {
133            fn to_compound(self) -> simdnbt::owned::NbtCompound {
134                let mut nbt = simdnbt::owned::NbtCompound::new();
135                #(#field_serializers)*
136                nbt
137            }
138        }
139    };
140
141    output.into()
142}
143
144#[proc_macro_derive(FromNbtTag, attributes(simdnbt))]
145pub fn from_nbt_tag_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
146    let input = parse_macro_input!(input as DeriveInput);
147
148    let ident = input.ident;
149
150    let mut matchers = Vec::<proc_macro2::TokenStream>::new();
151
152    match input.data {
153        syn::Data::Struct(_) => panic!("Use #[derive(Deserialize)] instead"),
154        syn::Data::Enum(syn::DataEnum { variants, .. }) => {
155            for variant in variants {
156                match variant.fields {
157                    syn::Fields::Named(_) => todo!(),
158                    syn::Fields::Unnamed(_) => todo!(),
159                    syn::Fields::Unit => {
160                        let enum_variant_name = variant.ident;
161
162                        let mut unit_attrs = parse_unit_attrs(&variant.attrs);
163
164                        let variant_name = unit_attrs
165                            .rename
166                            .take()
167                            .unwrap_or_else(|| enum_variant_name.to_string());
168
169                        matchers.push(quote! {
170                            #variant_name => Some(Self::#enum_variant_name),
171                        });
172                    }
173                }
174            }
175        }
176        syn::Data::Union(_) => todo!(),
177    }
178
179    let generics = input.generics;
180    let where_clause = &generics.where_clause;
181
182    let output = quote! {
183        impl #generics simdnbt::FromNbtTag for #ident #generics #where_clause {
184            fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
185                match tag.string()?.to_str().as_ref() {
186                    #(#matchers)*
187                    _ => None,
188                }
189            }
190        }
191    };
192
193    output.into()
194}
195
196#[proc_macro_derive(ToNbtTag, attributes(simdnbt))]
197pub fn to_nbt_tag_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
198    let input = parse_macro_input!(input as DeriveInput);
199
200    let ident = input.ident;
201
202    let mut field_matchers = Vec::<proc_macro2::TokenStream>::new();
203
204    match input.data {
205        syn::Data::Struct(_) => panic!("Use #[derive(Serialize)] instead"),
206        syn::Data::Enum(syn::DataEnum { variants, .. }) => {
207            for variant in variants {
208                match variant.fields {
209                    syn::Fields::Named(_) => todo!(),
210                    syn::Fields::Unnamed(_) => todo!(),
211                    syn::Fields::Unit => {
212                        let enum_variant_name = variant.ident;
213
214                        let mut unit_attrs = parse_unit_attrs(&variant.attrs);
215
216                        let variant_name = unit_attrs
217                            .rename
218                            .take()
219                            .unwrap_or_else(|| enum_variant_name.to_string());
220
221                        field_matchers.push(quote! {
222                            Self::#enum_variant_name => simdnbt::owned::NbtTag::String(#variant_name.into()),
223                        });
224                    }
225                }
226            }
227        }
228        syn::Data::Union(_) => todo!(),
229    }
230
231    let generics = input.generics;
232    let where_clause = &generics.where_clause;
233
234    let output = quote! {
235        impl #generics simdnbt::ToNbtTag for #ident #generics #where_clause {
236            fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
237                match self {
238                    #(#field_matchers)*
239                }
240            }
241        }
242    };
243
244    output.into()
245}