1use proc_macro::TokenStream;
2use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
3use quote::{quote, ToTokens};
4use syn::*;
5
6#[proc_macro_derive(Io, attributes(io))]
7pub fn derive_io(input: TokenStream) -> TokenStream {
8 let DeriveInput { ident, data, .. } = parse_macro_input!(input);
9 derive_io_inner(ident, data).into()
10}
11
12fn derive_io_inner(ident: Ident, data: Data) -> TokenStream2 {
13 let (read, write, size) = match data {
14 Data::Struct(data) => (
15 read_fields(quote! { Self }, &data.fields),
16 write_struct_fields(&data.fields),
17 struct_field_sizes(&data.fields)
18 ),
19 Data::Enum(data) => {
20 let variant_type = match data.variants.len() {
22 0 => return Error::new(ident.span(), "Empty enums are not supported, use () instead.").to_compile_error(),
23 1..=255 => quote! { u8 },
24 256..=65535 => quote! { u16 },
25 _ => quote! { u32 }
26 };
27 let read_variants = data.variants.iter()
29 .enumerate()
30 .map(|(i, variant)| {
31 let i = Literal::usize_unsuffixed(i);
32 let ident = &variant.ident;
33 let read_fields = read_fields(quote! { Self::#ident }, &variant.fields);
34 quote! {
35 #i => #read_fields,
36 }
37 });
38 let match_variants = data.variants.iter()
40 .enumerate()
41 .map(|(i, variant)| {
42 let i = Literal::usize_unsuffixed(i);
44 let ident = &variant.ident;
45 let matcher = match &variant.fields {
46 Fields::Named(_) => quote! { {..} },
47 Fields::Unnamed(_) => quote! { (..) },
48 Fields::Unit => quote! {}
49 };
50 quote! {
51 Self::#ident #matcher => #i
52 }
53 });
54 let write_variants = data.variants.iter()
55 .filter_map(|variant| {
56 let ident = &variant.ident;
57 let matcher = enum_matcher(&variant.fields)?;
58 let writes = enum_field_idents(&variant.fields, "write", |func, ident| quote! {
59 #func(#ident, w)?;
60 });
61 Some(quote! {
62 Self::#ident #matcher => { #(#writes)* }
63 })
64 });
65 let size_variants = data.variants.iter()
66 .filter_map(|variant| {
67 let ident = &variant.ident;
68 let matcher = enum_matcher(&variant.fields)?;
69 let sizes = enum_field_idents(&variant.fields, "size", |func, ident| quote! {
70 #func(#ident)
71 });
72 Some(quote! {
73 Self::#ident #matcher => #(#sizes)+*
74 })
75 });
76 (quote! {
77 {
79 let variant = #variant_type::read(r)?;
80 match variant {
81 #(#read_variants)*
82 _ => return Err(derive_enum_variant_error(variant.into(), stringify!(#ident)))
83 }
84 }
85 }, quote! {
86 let variant: #variant_type = match self {
87 #(#match_variants),*
88 };
89 variant.write(w)?;
90 match self {
91 #(#write_variants,)*
92 _ => {} }
94 }, quote! {
95 1 + match self {
96 #(#size_variants,)*
97 _ => 0
98 }
99 })
100 },
101 Data::Union(_) => return Error::new(ident.span(), "Unions are not supported").to_compile_error()
102 };
103
104 quote! {
105 #[automatically_derived]
106 impl Io for #ident {
107 fn read(r: &mut Reader) -> ::std::io::Result<Self> {
108 Ok(#read)
109 }
110
111 fn write(&self, w: &mut Writer) -> ::std::io::Result<()> {
112 #write
113 Ok(())
114 }
115
116 fn size(&self) -> usize {
117 #size
118 }
119 }
120 }
121}
122
123enum FieldOptions {
124 None,
125 Ignored,
126 Len(Type)
127}
128
129impl FieldOptions {
130 fn is_some(&self) -> bool {
131 !matches!(self, Self::None)
132 }
133
134 fn is_ignored(&self) -> bool {
135 matches!(self, Self::Ignored)
136 }
137}
138
139fn field_options(field: &Field) -> FieldOptions {
140 let mut options = FieldOptions::None;
141 for attr in field.attrs.iter() {
142 if !attr.path().is_ident("io") {
144 continue;
145 }
146
147 if let Err(e) = attr.parse_nested_meta(|meta| {
149 if options.is_some() {
150 return Err(meta.error("duplicate io attributes used"));
151 }
152
153 if meta.path.is_ident("ignore") {
154 options = FieldOptions::Ignored;
155 return Ok(());
156 }
157
158 if meta.path.is_ident("len") {
159 let content;
160 parenthesized!(content in meta.input);
161 let len = content.parse()?;
162 options = FieldOptions::Len(len);
163 return Ok(());
164 }
165
166 Err(meta.error(format!("unknown io attribute '{}' used", meta.path.to_token_stream())))
167 }) {
168 panic!("Failed to parse attribute for field '{}': {e}", field.ident.as_ref().unwrap());
169 }
170 }
171
172 options
173}
174
175
176fn is_ignored(field: &Field) -> bool {
177 field_options(field).is_ignored()
178}
179
180fn read_fields(base: TokenStream2, fields: &Fields) -> TokenStream2 {
181 match fields {
182 Fields::Named(fields) => {
183 let read_fields = fields.named.iter()
184 .map(|field| {
185 let ident = &field.ident;
186 let read = read_field(&field);
187 quote! {
188 #ident: #read
189 }
190 });
191 quote! {
192 #base {
193 #(#read_fields),*
194 }
195 }
196 },
197 Fields::Unnamed(fields) => {
198 let read_fields = fields.unnamed.iter()
199 .map(|field| read_field(&field));
200 quote! {
201 #base(#(#read_fields),*)
202 }
203 },
204 Fields::Unit => base
205 }
206}
207
208fn read_field(field: &Field) -> TokenStream2 {
210 let ty = &field.ty;
211 let options = field_options(field);
212 match options {
213 FieldOptions::Ignored => {
214 quote! {
215 <#ty as ::std::default::Default>::default()
216 }
217 },
218 FieldOptions::None => {
219 quote! {
220 <#ty as Io>::read(r)?
221 }
222 },
223 FieldOptions::Len(len) => {
224 quote! {
225 <#ty as LenIo>::read::<#len>(r)?
226 }
227 }
228 }
229}
230
231fn write_struct_fields(fields: &Fields) -> TokenStream2 {
232 match fields {
233 Fields::Named(fields) => {
234 let fields = fields.named.iter()
235 .filter_map(|field| {
236 let options = field_options(field);
237 let ident = &field.ident;
238 Some(match options {
239 FieldOptions::Ignored => return None,
240 FieldOptions::None => quote! {
241 Io::write(&self.#ident, w)?;
242 },
243 FieldOptions::Len(len) => quote! {
244 LenIo::write::<#len>(&self.#ident, w)?;
245 }
246 })
247 });
248 quote! {
249 #(#fields)*
250 }
251 },
252 Fields::Unnamed(fields) => {
253 let fields = fields.unnamed.iter()
257 .enumerate()
258 .filter_map(|(i, field)| {
259 let options = field_options(field);
260 let i: Index = i.into();
261 Some(match options {
262 FieldOptions::Ignored => return None,
263 FieldOptions::None => quote! {
264 Io::write(&self.#i, w)?;
265 },
266 FieldOptions::Len(len) => quote! {
267 LenIo::write::<#len>(&self.#i, w)?;
268 }
269 })
270 });
271 quote! {
272 #(#fields)*
273 }
274 },
275 Fields::Unit => quote! {}
276 }
277}
278
279fn struct_field_sizes(fields: &Fields) -> TokenStream2 {
280 match fields {
281 Fields::Named(fields) => {
282 let fields = fields.named.iter()
283 .filter_map(|field| {
284 let options = field_options(field);
285 let ident = &field.ident;
286 Some(match options {
287 FieldOptions::Ignored => return None,
288 FieldOptions::None => quote! {
289 Io::size(&self.#ident)
290 },
291 FieldOptions::Len(len) => quote! {
292 LenIo::size::<#len>(&self.#ident)
293 }
294 })
295 });
296 quote! {
297 #(#fields)+*
298 }
299 },
300 Fields::Unnamed(fields) => {
301 let fields = fields.unnamed.iter()
302 .enumerate()
303 .filter_map(|(i, field)| {
304 let options = field_options(field);
305 let i: Index = i.into();
306 Some(match options {
307 FieldOptions::Ignored => return None,
308 FieldOptions::None => quote! {
309 Io::size(&self.#i)
310 },
311 FieldOptions::Len(len) => quote! {
312 LenIo::size::<#len>(&self.#i)
313 }
314 })
315 });
316 quote! {
317 #(#fields)+*
318 }
319 },
320 Fields::Unit => quote! {}
321 }
322}
323
324fn enum_matcher(fields: &Fields) -> Option<TokenStream2> {
325 Some(match fields {
326 Fields::Named(fields) => {
327 let fields = fields.named.iter()
328 .filter(|field| !is_ignored(field))
329 .filter_map(|field| field.ident.clone());
330 quote! {
331 { #(#fields),* }
332 }
333 },
334 Fields::Unnamed(fields) => {
335 let fields = fields.unnamed.iter()
336 .enumerate()
337 .filter(|(_, field)| !is_ignored(field))
338 .map(|(i, _)| name_unnamed_field(i));
339 quote! {
340 (#(#fields),*)
341 }
342 },
343 Fields::Unit => return None
344 })
345}
346
347fn enum_field_idents(fields: &Fields, name: &'static str, f: impl Fn(TokenStream2, Ident) -> TokenStream2) -> Vec<TokenStream2> {
348 let name = Ident::new(name, Span::call_site());
349 match fields {
350 Fields::Named(fields) => fields.named.iter()
351 .filter_map(|field| {
352 let ident = field.ident.clone().unwrap();
353 map_trait(&name, field, ident)
354 })
355 .map(|(a, b)| f(a, b))
356 .collect(),
357 Fields::Unnamed(fields) => fields.unnamed.iter()
358 .enumerate()
359 .filter_map(|(i, field)| {
360 let ident = name_unnamed_field(i);
361 map_trait(&name, field, ident)
362 })
363 .map(|(a, b)| f(a, b))
364 .collect(),
365 Fields::Unit => vec![]
366 }
367}
368
369fn map_trait(name: &Ident, field: &Field, ident: Ident) -> Option<(TokenStream2, Ident)> {
370 let options = field_options(field);
371 Some((match options {
372 FieldOptions::Ignored => return None,
373 FieldOptions::None => quote! {
374 Io::#name
375 },
376 FieldOptions::Len(len) => quote! {
377 LenIo::#name::<#len>
378 }
379 }, ident))
380}
381
382fn name_unnamed_field(i: usize) -> Ident {
384 let c = (b'a' + (i as u8)) as char;
385 let s = c.to_string();
386 Ident::new(&s, Span::call_site())
387}