Skip to main content

scroll_derive/
lib.rs

1#![recursion_limit = "1024"]
2
3extern crate proc_macro;
4
5use proc_macro2::Span;
6use quote::{ToTokens, format_ident, quote};
7use syn::Ident;
8
9fn extract_idents_and_offset(
10    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
11) -> (Vec<(proc_macro2::TokenStream, &syn::Field)>, syn::Ident) {
12    // first iterate idents
13    let idents: Vec<_> = fields
14        .into_iter()
15        .enumerate()
16        .map(|(i, f)| {
17            let ident = f.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
18                let t = proc_macro2::Literal::usize_unsuffixed(i);
19                quote! {#t}
20            });
21            (ident, f)
22        })
23        .collect();
24    // iterate until we have no field that matches our offset
25    let offset = fresh_name(
26        &fields,
27        proc_macro2::Ident::new("offset", Span::call_site()),
28    );
29
30    (idents, offset)
31}
32
33/// Generates a fresh name that will not clash with any field named the same
34/// NB: there is probably a more efficient algorithm than this worst case O^2 runtime, but even for
35/// a struct with hundreds of fields all clashing with increasing _ prefixes, which is a highly
36/// degenerate example input, it is fine.
37fn fresh_name(
38    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
39    mut target: proc_macro2::Ident,
40) -> Ident {
41    while fields.iter().any(|f| {
42        f.ident
43            .as_ref()
44            .map(|ident| ident == &target)
45            .unwrap_or(false)
46    }) {
47        target = format_ident!("_{target}");
48    }
49    target
50}
51
52fn extract_lifetime(
53    gp: &syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>,
54) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
55    let mut lifetimes = gp
56        .iter()
57        .filter_map(|param: &syn::GenericParam| match param {
58            syn::GenericParam::Lifetime(lifetime) => Some(lifetime.lifetime.clone()),
59            _ => None,
60        })
61        .collect::<Vec<_>>();
62    if lifetimes.len() > 1 {
63        panic!("Pread cannot be derived for multiple lifetimes")
64    }
65    let lifetime = lifetimes
66        .pop()
67        .unwrap_or(syn::Lifetime::new("'a", Span::call_site()));
68    // alpha rename/make the thing fresh
69    let alpha = format!("'{}_fresh", lifetime.ident.to_string());
70    (
71        lifetime.to_token_stream(),
72        syn::Lifetime::new(&alpha.to_string(), lifetime.span()).to_token_stream(),
73    )
74}
75
76fn impl_field(
77    ident: &proc_macro2::TokenStream,
78    ty: &syn::Type,
79    src: &proc_macro2::Ident,
80    default_ctx: &proc_macro2::TokenStream,
81    custom_ctx: Option<&proc_macro2::TokenStream>,
82    offset: &Ident,
83    noctx: bool,
84) -> proc_macro2::TokenStream {
85    let ctx = custom_ctx.unwrap_or(default_ctx);
86    match ty {
87        syn::Type::Group(group) => impl_field(
88            ident,
89            &group.elem,
90            src,
91            default_ctx,
92            custom_ctx,
93            offset,
94            noctx,
95        ),
96        _ => {
97            if noctx {
98                quote! {
99                    let #ident = #src.gread::<#ty>(#offset)?;
100                }
101            } else {
102                quote! {
103                    let #ident = #src.gread_with::<#ty>(#offset, #ctx)?;
104                }
105            }
106        }
107    }
108}
109
110/// Retrieve the field attribute with given ident e.g:
111/// ```ignore
112/// #[attr_ident(..)]
113/// field: T,
114/// ```
115fn get_attr<'a>(attr_ident: &str, field: &'a syn::Field) -> Option<&'a syn::Attribute> {
116    field
117        .attrs
118        .iter()
119        .find(|attr| attr.path().is_ident(attr_ident))
120}
121
122/// Gets the `TokenStream` for the custom ctx set in the `ctx` attribute. e.g. `expr` in the following
123/// ```ignore
124/// #[scroll(ctx = expr)]
125/// field: T,
126/// ```
127fn custom_ctx(field: &syn::Field, noctx: &mut bool) -> Option<proc_macro2::TokenStream> {
128    get_attr("scroll", field).and_then(|x| {
129        // parsed #[scroll..]
130        // `expr` is `None` if the `ctx` key is not used.
131        let mut attrib = None;
132        let res = x.parse_nested_meta(|meta| {
133            // parsed #[scroll(..)]
134            if meta.path.is_ident("ctx") {
135                // parsed #[scroll(ctx..)]
136                let value = meta.value()?; // parsed #[scroll(ctx = ..)]
137                attrib = Some(value.parse::<syn::Expr>()?.into_token_stream()); // parsed #[scroll(ctx = expr)]
138                return Ok(());
139            }
140            if meta.path.is_ident("noctx") {
141                // parsed #[scroll(noctx)]
142                *noctx = true;
143                return Ok(());
144            }
145            Err(meta.error(match meta.path.get_ident() {
146                Some(ident) => format!("unrecognized attribute: {ident}"),
147                None => "unrecognized and invalid attribute".to_owned(),
148            }))
149        });
150        match res {
151            Ok(()) => attrib,
152            Err(e) => Some(e.into_compile_error()),
153        }
154    })
155}
156
157fn impl_struct(
158    name: &syn::Ident,
159    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
160    generics: &syn::Generics,
161    unnamed: bool,
162) -> proc_macro2::TokenStream {
163    let offset = fresh_name(
164        fields,
165        syn::Ident::new("offset", proc_macro2::Span::call_site()),
166    );
167    let src = fresh_name(
168        fields,
169        syn::Ident::new("src", proc_macro2::Span::call_site()),
170    );
171    let ctx = fresh_name(
172        fields,
173        syn::Ident::new("ctx", proc_macro2::Span::call_site()),
174    )
175    .to_token_stream();
176    let (items, item_assignments) = fields
177        .iter()
178        .enumerate()
179        .map(|(i, f)| {
180            let (ident, prefixed_ident) = &f
181                .ident
182                .as_ref()
183                .map(|i| (quote! {#i}, quote! {#i}))
184                .unwrap_or({
185                    let t = proc_macro2::Literal::usize_unsuffixed(i);
186                    let suf = if unnamed {
187                        syn::Ident::new(&format!("_{t}"), proc_macro2::Span::call_site())
188                            .into_token_stream()
189                    } else {
190                        t.clone().to_token_stream()
191                    };
192                    (quote! {#t}, suf)
193                });
194            let ty = &f.ty;
195            // parse the `expr` out of #[scroll(ctx = expr)]
196            let mut noctx = false;
197            let custom_ctx = custom_ctx(f, &mut noctx);
198            (
199                impl_field(
200                    &prefixed_ident,
201                    ty,
202                    &src,
203                    &ctx,
204                    custom_ctx.as_ref(),
205                    &offset,
206                    noctx,
207                ),
208                quote! { #ident: #prefixed_ident },
209            )
210        })
211        .collect::<(Vec<_>, Vec<_>)>();
212
213    let gl = &generics.lt_token;
214    let gp = &generics.params;
215    let gg = &generics.gt_token;
216    let gn = gp.iter().map(|param: &syn::GenericParam| match param {
217        syn::GenericParam::Type(t) => {
218            let ident = &t.ident;
219            quote! { #ident }
220        }
221        p => quote! { #p },
222    });
223
224    let (lifetime, _fresh_lifetime) = extract_lifetime(gp);
225    let gn = quote! { #gl #( #gn ),* #gg };
226    // drop the lifetime from our generic params, since we already grabbed it
227    let initial_generic_params = gp
228        .iter()
229        .filter_map(|param: &syn::GenericParam| match param {
230            syn::GenericParam::Lifetime(_) => None,
231            p => Some(p),
232        })
233        .collect::<Vec<_>>();
234    let lhs_gp = if !initial_generic_params.is_empty() {
235        quote! { #( #initial_generic_params ),* }
236    } else {
237        quote! {}
238    };
239
240    let gw = if !gp.is_empty() {
241        let gi = gp.iter().filter_map(|param: &syn::GenericParam| match param {
242            syn::GenericParam::Type(t) => Some({
243                let ident = &t.ident;
244                quote! {
245                    #ident : ::scroll::ctx::TryFromCtx<#lifetime, ::scroll::Endian, Error = ::scroll::Error>,
246                    ::scroll::Error : ::std::convert::From<< #ident as ::scroll::ctx::TryFromCtx<#lifetime, ::scroll::Endian>>::Error>,
247                    < #ident as ::scroll::ctx::TryFromCtx<#lifetime, ::scroll::Endian>>::Error : ::std::convert::From<scroll::Error>
248                }
249            }),
250            syn::GenericParam::Lifetime(_) => None,
251            p => Some(quote! { #p })
252        }).collect::<Vec<_>>();
253        if !gi.is_empty() {
254            // NB: that extra comma after * is very important
255            quote! { #( #gi ),*,  }
256        } else {
257            quote! {}
258        }
259    } else {
260        quote! {}
261    };
262
263    quote! {
264     impl<#lifetime, #lhs_gp > ::scroll::ctx::TryFromCtx<#lifetime, ::scroll::Endian> for #name #gn
265         where #gw #name #gn : #lifetime {
266            // TODO: allow passing user error here
267            type Error = ::scroll::Error;
268            #[inline]
269            fn try_from_ctx(#src: &#lifetime [u8], #ctx: ::scroll::Endian) -> ::scroll::export::result::Result<(Self, usize), Self::Error> {
270              use ::scroll::Pread;
271              let #offset = &mut 0;
272              #(#items)*
273              Ok((Self { #(#item_assignments,)* }, *#offset))
274            }
275        }
276    }
277}
278
279fn ensure_fieldless(variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>) {
280    for variant in variants {
281        if !variant.fields.is_empty() {
282            panic!("Deriving enums in scroll must be primitive, fieldless enums");
283        }
284    }
285}
286
287const VALID_PRIMITIVE_REPRS: &[&'static str] = &[
288    "i8", "i16", "i32", "i64", "i128", "u8", "u16", "u32", "u64", "u128",
289];
290
291fn extract_repr_type(ast: &syn::DeriveInput) -> syn::Ident {
292    let mut repr_type: Option<syn::Ident> = None;
293    for attr in &ast.attrs {
294        if attr.path().is_ident("repr") {
295            let _ = attr.parse_nested_meta(|meta| {
296                for prim in VALID_PRIMITIVE_REPRS {
297                    if meta.path.is_ident(prim) {
298                        repr_type = meta.path.get_ident().cloned();
299                        return Ok(());
300                    }
301                }
302                Ok(())
303            });
304        };
305    }
306    let Some(repr_type) = repr_type else {
307        panic!("Deriving pread on enum requires repr with one of: {VALID_PRIMITIVE_REPRS:?}");
308    };
309    repr_type
310}
311
312fn impl_try_from_ctx_enum(
313    name: &syn::Ident,
314    repr_type: syn::Ident,
315    variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
316) -> proc_macro2::TokenStream {
317    let variant_consts = variants.iter().map(|variant| {
318        let ident = &variant.ident;
319        let const_name = format_ident!("_{}", ident.to_string().to_uppercase());
320        quote! {
321            const #const_name: #repr_type = #name::#ident as #repr_type;
322        }
323    });
324    let variant_cases = variants.iter().map(|variant| {
325        let ident = &variant.ident;
326        let const_name = format_ident!("_{}", ident.to_string().to_uppercase());
327        quote! {
328            #const_name => #name::#ident,
329        }
330    });
331    let static_msg = format!(
332        "No variants matched a discriminant of type {}",
333        repr_type.to_string()
334    );
335    quote! {
336     impl<'a> ::scroll::ctx::TryFromCtx<'a, ::scroll::Endian> for #name {
337            type Error = ::scroll::Error;
338            #[inline]
339            fn try_from_ctx(src: &'a [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<(Self, usize), Self::Error> {
340              use ::scroll::Pread;
341              #(#variant_consts)*
342              let offset = &mut 0;
343              let val = match src.gread_with::<#repr_type>(offset, ctx)? {
344                  #(#variant_cases)*
345                  _ => return Err(::scroll::Error::BadInput { size: *offset, msg: #static_msg})
346              };
347              Ok((val, *offset))
348            }
349        }
350    }
351}
352
353fn validate_enum(ast: &syn::DeriveInput, data: &syn::DataEnum) -> Ident {
354    let repr_type = extract_repr_type(ast);
355    ensure_fieldless(&data.variants);
356    repr_type
357}
358
359fn impl_try_from_ctx(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
360    let name = &ast.ident;
361    let generics = &ast.generics;
362    match &ast.data {
363        syn::Data::Struct(data) => match &data.fields {
364            syn::Fields::Named(fields) => impl_struct(name, &fields.named, generics, false),
365            syn::Fields::Unnamed(fields) => impl_struct(name, &fields.unnamed, generics, true),
366            _ => {
367                panic!("Pread can not be derived for unit structs")
368            }
369        },
370        syn::Data::Enum(data) => {
371            let repr_type = validate_enum(ast, data);
372            impl_try_from_ctx_enum(&ast.ident, repr_type, &data.variants)
373        }
374        _ => panic!("Pread can only be derived for structs and primitive enums"),
375    }
376}
377
378#[proc_macro_derive(Pread, attributes(scroll))]
379pub fn derive_pread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
380    let ast: syn::DeriveInput = syn::parse(input).unwrap();
381    let generated = impl_try_from_ctx(&ast);
382    generated.into()
383}
384
385fn impl_pwrite_field(
386    ident: &proc_macro2::TokenStream,
387    ty: &syn::Type,
388    default_ctx: &proc_macro2::TokenStream,
389    custom_ctx: Option<&proc_macro2::TokenStream>,
390    offset: &proc_macro2::Ident,
391    noctx: bool,
392) -> proc_macro2::TokenStream {
393    let ctx = custom_ctx.unwrap_or(default_ctx);
394    match ty {
395        syn::Type::Array(array) => match &array.len {
396            syn::Expr::Lit(syn::ExprLit {
397                lit: syn::Lit::Int(int),
398                ..
399            }) => {
400                let size = int.base10_parse::<usize>().unwrap();
401                quote! {
402                    for i in 0..#size {
403                        dst.gwrite_with(&self.#ident[i], #offset, #ctx)?;
404                    }
405                }
406            }
407            _ => panic!("Pwrite derive with bad array constexpr"),
408        },
409        syn::Type::Group(group) => {
410            impl_pwrite_field(ident, &group.elem, default_ctx, custom_ctx, offset, noctx)
411        }
412        syn::Type::Reference(reference) => match *reference.elem {
413            syn::Type::Slice(_) => {
414                quote! {
415                    dst.gwrite_with(self.#ident, #offset, ())?
416                }
417            }
418            syn::Type::Path(ref path) => {
419                if path.path.get_ident().unwrap().to_string().as_str() == "str" {
420                    quote! {
421                        dst.gwrite(self.#ident, #offset)?
422                    }
423                } else {
424                    quote! {
425                        dst.gwrite_with(self.#ident, #offset, #ctx)?
426                    }
427                }
428            }
429            _ => {
430                quote! {
431                    dst.gwrite_with(self.#ident, #offset, #ctx)?
432                }
433            }
434        },
435        _ => {
436            quote! {
437                dst.gwrite_with(&self.#ident, #offset, #ctx)?
438            }
439        }
440    }
441}
442
443fn impl_try_into_ctx(
444    name: &syn::Ident,
445    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
446    generics: &syn::Generics,
447) -> proc_macro2::TokenStream {
448    let (idents, offset) = extract_idents_and_offset(fields);
449    let ctx = fresh_name(
450        fields,
451        syn::Ident::new("ctx", proc_macro2::Span::call_site()),
452    )
453    .to_token_stream();
454    let items: Vec<_> = idents
455        .iter()
456        .map(|(ident, f)| {
457            let ty = &f.ty;
458            let mut noctx = false;
459            let custom_ctx = custom_ctx(f, &mut noctx);
460            impl_pwrite_field(ident, ty, &ctx, custom_ctx.as_ref(), &offset, noctx)
461        })
462        .collect();
463
464    let gl = &generics.lt_token;
465    let gp = &generics.params;
466    let gg = &generics.gt_token;
467    let gn = gp.iter().map(|param: &syn::GenericParam| match param {
468        syn::GenericParam::Type(t) => {
469            let ident = &t.ident;
470            quote! { #ident }
471        }
472        p => quote! { #p },
473    });
474    let gn = quote! { #gl #( #gn ),* #gg };
475    // it's always important to keep it _fresh_ when we pwrite
476    let (_lifetime, fresh_lifetime) = extract_lifetime(gp);
477    let gwref = if !gp.is_empty() {
478        let gi: Vec<_> = gp.iter().filter_map(|param: &syn::GenericParam| match param {
479            syn::GenericParam::Type(t) => {
480                let ident = &t.ident;
481                Some(quote! {
482                    &#fresh_lifetime #ident : ::scroll::ctx::TryIntoCtx<::scroll::Endian>,
483                    ::scroll::Error: ::std::convert::From<<&#fresh_lifetime #ident as ::scroll::ctx::TryIntoCtx<::scroll::Endian>>::Error>,
484                    <&#fresh_lifetime #ident as ::scroll::ctx::TryIntoCtx<::scroll::Endian>>::Error: ::std::convert::From<scroll::Error>
485                })
486            },
487            syn::GenericParam::Lifetime(_) => None,
488            p => Some(quote! { #p }),
489        }).collect();
490        if !gi.is_empty() {
491            quote! { where #( #gi ),* }
492        } else {
493            quote! {}
494        }
495    } else {
496        quote! {}
497    };
498    let gw = if !gp.is_empty() {
499        let gi = gp.iter().filter_map(|param: &syn::GenericParam| match param {
500            syn::GenericParam::Type(t) => {
501                let ident = &t.ident;
502                Some(quote! {
503                    #ident : ::scroll::ctx::TryIntoCtx<::scroll::Endian>,
504                    ::scroll::Error: ::std::convert::From<<#ident as ::scroll::ctx::TryIntoCtx<::scroll::Endian>>::Error>,
505                    <#ident as ::scroll::ctx::TryIntoCtx<::scroll::Endian>>::Error: ::std::convert::From<scroll::Error>
506                })
507            },
508            syn::GenericParam::Lifetime(_) => None,
509            p => Some(quote! { #p }),
510        });
511        quote! { where Self: ::std::marker::Copy, #( #gi ),* }
512    } else {
513        quote! {}
514    };
515
516    quote! {
517        impl<#fresh_lifetime, #gp > ::scroll::ctx::TryIntoCtx<::scroll::Endian> for &#fresh_lifetime #name #gn #gwref {
518            type Error = ::scroll::Error;
519            #[inline]
520            fn try_into_ctx(self, dst: &mut [u8], #ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
521                use ::scroll::Pwrite;
522                let #offset = &mut 0;
523                #(#items;)*
524                Ok(*#offset)
525            }
526        }
527
528        impl #gl #gp #gg ::scroll::ctx::TryIntoCtx<::scroll::Endian> for #name #gn #gw {
529            type Error = ::scroll::Error;
530            #[inline]
531            fn try_into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
532                (&self).try_into_ctx(dst, ctx)
533            }
534        }
535    }
536}
537
538fn impl_try_into_ctx_primitive_enum(
539    name: &Ident,
540    repr_type: Ident,
541    _variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
542) -> proc_macro2::TokenStream {
543    quote! {
544        impl ::scroll::ctx::TryIntoCtx<::scroll::Endian> for &'_ #name {
545            type Error = ::scroll::Error;
546            #[inline]
547            fn try_into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
548                use ::scroll::Pwrite;
549                // SAFETY: https://doc.rust-lang.org/std/mem/fn.discriminant.html#accessing-the-numeric-value-of-the-discriminant
550                // > If an enum has opted-in to having a primitive representation for its discriminant,
551                // > then it’s possible to use pointers to read the memory location storing the discriminant.
552                // NB: the derive macro ensures that we are a primitive (and also fieldless) enum
553                dst.pwrite_with(unsafe { *<*const _>::from(self).cast::<#repr_type>() }, 0, ctx)
554            }
555        }
556
557        impl ::scroll::ctx::TryIntoCtx<::scroll::Endian> for #name {
558            type Error = ::scroll::Error;
559            #[inline]
560            fn try_into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) -> ::scroll::export::result::Result<usize, Self::Error> {
561                (&self).try_into_ctx(dst, ctx)
562            }
563        }
564    }
565}
566
567fn impl_pwrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
568    let name = &ast.ident;
569    let generics = &ast.generics;
570    match &ast.data {
571        syn::Data::Struct(data) => match &data.fields {
572            syn::Fields::Named(fields) => impl_try_into_ctx(name, &fields.named, generics),
573            syn::Fields::Unnamed(fields) => impl_try_into_ctx(name, &fields.unnamed, generics),
574            _ => {
575                panic!("Pwrite can not be derived for unit structs")
576            }
577        },
578        syn::Data::Enum(data) => {
579            let repr_type = validate_enum(ast, data);
580            impl_try_into_ctx_primitive_enum(&ast.ident, repr_type, &data.variants)
581        }
582        _ => panic!("Pwrite can only be derived for structs and primitive enums"),
583    }
584}
585
586#[proc_macro_derive(Pwrite, attributes(scroll))]
587pub fn derive_pwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
588    let ast: syn::DeriveInput = syn::parse(input).unwrap();
589    let generated = impl_pwrite(&ast);
590    generated.into()
591}
592
593fn size_with(
594    name: &syn::Ident,
595    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
596    generics: &syn::Generics,
597) -> proc_macro2::TokenStream {
598    let items: Vec<_> = fields
599        .iter()
600        .map(|f| {
601            let ty = &f.ty;
602            let mut noctx = false;
603            let custom_ctx = custom_ctx(f, &mut noctx).map(|x| quote! {&#x});
604            let default_ctx =
605                syn::Ident::new("ctx", proc_macro2::Span::call_site()).into_token_stream();
606            let ctx = custom_ctx.unwrap_or(default_ctx);
607            match ty {
608                syn::Type::Reference(_) => {
609                    panic!("SizeWith cannot be derived for references")
610                }
611                syn::Type::Array(array) => {
612                    let elem = &array.elem;
613                    match &array.len {
614                        syn::Expr::Lit(syn::ExprLit {
615                            lit: syn::Lit::Int(int),
616                            ..
617                        }) => {
618                            let size = int.base10_parse::<usize>().unwrap();
619                            quote! {
620                                (#size * <#elem>::size_with(#ctx))
621                            }
622                        }
623                        _ => panic!("SizeWith derive has bad array constexpr"),
624                    }
625                }
626                _ => {
627                    quote! {
628                        <#ty>::size_with(#ctx)
629                    }
630                }
631            }
632        })
633        .collect();
634
635    let gl = &generics.lt_token;
636    let gp = &generics.params;
637    let gg = &generics.gt_token;
638    let gn = gp.iter().map(|param: &syn::GenericParam| match param {
639        syn::GenericParam::Type(t) => {
640            let ident = &t.ident;
641            quote! { #ident }
642        }
643        p => quote! { #p },
644    });
645    let gn = quote! { #gl #( #gn ),* #gg };
646    let gw = if !gp.is_empty() {
647        let gi = gp
648            .iter()
649            .filter_map(|param: &syn::GenericParam| match param {
650                syn::GenericParam::Type(t) => {
651                    let ident = &t.ident;
652                    Some(quote! {
653                        #ident : ::scroll::ctx::SizeWith<::scroll::Endian>
654                    })
655                }
656                syn::GenericParam::Lifetime(_) => None,
657                p => Some(quote! { #p }),
658            });
659        quote! { where #( #gi ),* }
660    } else {
661        quote! {}
662    };
663
664    quote! {
665        impl #gl #gp #gg ::scroll::ctx::SizeWith<::scroll::Endian> for #name #gn #gw {
666            #[inline]
667            fn size_with(ctx: &::scroll::Endian) -> usize {
668                0 #(+ #items)*
669            }
670        }
671    }
672}
673
674fn impl_size_with(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
675    let name = &ast.ident;
676    let generics = &ast.generics;
677    match &ast.data {
678        syn::Data::Struct(data) => match &data.fields {
679            syn::Fields::Named(fields) => size_with(name, &fields.named, generics),
680            syn::Fields::Unnamed(fields) => size_with(name, &fields.unnamed, generics),
681            _ => {
682                panic!("SizeWith can not be derived for unit structs")
683            }
684        },
685        _ => panic!("SizeWith can only be derived for structs"),
686    }
687}
688
689#[proc_macro_derive(SizeWith, attributes(scroll))]
690pub fn derive_sizewith(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
691    let ast: syn::DeriveInput = syn::parse(input).unwrap();
692    let generated = impl_size_with(&ast);
693    generated.into()
694}
695
696fn impl_cread_struct(
697    name: &syn::Ident,
698    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
699    generics: &syn::Generics,
700) -> proc_macro2::TokenStream {
701    let items: Vec<_> = fields.iter().enumerate().map(|(i, f)| {
702        let ident = &f.ident.as_ref().map(|i|quote!{#i}).unwrap_or({let t = proc_macro2::Literal::usize_unsuffixed(i); quote!{#t}});
703        let ty = &f.ty;
704        let mut noctx = false;
705        let custom_ctx = custom_ctx(f, &mut noctx);
706        let default_ctx =
707            syn::Ident::new("ctx", proc_macro2::Span::call_site()).into_token_stream();
708        let ctx = custom_ctx.unwrap_or(default_ctx);
709        match ty {
710            syn::Type::Reference(_) => {
711                panic!("IOread cannot be derived for references, because SizeWith cannot be derived for references")
712            }
713            syn::Type::Array(array) => {
714                let arrty = &array.elem;
715                match &array.len {
716                    syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int), ..}) => {
717                        let size = int.base10_parse::<usize>().unwrap();
718                        let incr = quote! { ::scroll::export::mem::size_of::<#arrty>() };
719                        quote! {
720                            #ident: {
721                                let mut __tmp: #ty = [0u8.into(); #size];
722                                for i in 0..__tmp.len() {
723                                    __tmp[i] = src.cread_with(*offset, #ctx);
724                                    *offset += #incr;
725                                }
726                                __tmp
727                            }
728                        }
729                    },
730                    _ => panic!("IOread derive with bad array constexpr")
731                }
732            },
733            _ => {
734                let size = quote! { ::scroll::export::mem::size_of::<#ty>() };
735                quote! {
736                    #ident: { let res = src.cread_with::<#ty>(*offset, #ctx); *offset += #size; res }
737                }
738            }
739        }
740    }).collect();
741
742    let gl = &generics.lt_token;
743    let gp = &generics.params;
744    let gg = &generics.gt_token;
745    let gn = gp.iter().map(|param: &syn::GenericParam| match param {
746        syn::GenericParam::Type(t) => {
747            let ident = &t.ident;
748            quote! { #ident }
749        }
750        p => quote! { #p },
751    });
752    let gn = quote! { #gl #( #gn ),* #gg };
753    let gw = if !gp.is_empty() {
754        let gi = gp.iter().map(|param: &syn::GenericParam| match param {
755            syn::GenericParam::Type(t) => {
756                let ident = &t.ident;
757                quote! {
758                    #ident : ::scroll::ctx::FromCtx<::scroll::Endian> + ::std::convert::From<u8> + ::std::marker::Copy
759                }
760            },
761            p => quote! { #p }
762        });
763        quote! { where #( #gi ),* , }
764    } else {
765        quote! {}
766    };
767
768    quote! {
769        impl #gl #gp #gg ::scroll::ctx::FromCtx<::scroll::Endian> for #name #gn #gw {
770            #[inline]
771            fn from_ctx(src: &[u8], ctx: ::scroll::Endian) -> Self {
772                use ::scroll::Cread;
773                let offset = &mut 0;
774                let data = Self { #(#items,)* };
775                data
776            }
777        }
778    }
779}
780
781fn impl_from_ctx(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
782    let name = &ast.ident;
783    let generics = &ast.generics;
784    match &ast.data {
785        syn::Data::Struct(data) => match &data.fields {
786            syn::Fields::Named(fields) => impl_cread_struct(name, &fields.named, generics),
787            syn::Fields::Unnamed(fields) => impl_cread_struct(name, &fields.unnamed, generics),
788            _ => {
789                panic!("IOread can not be derived for unit structs")
790            }
791        },
792        _ => panic!("IOread can only be derived for structs"),
793    }
794}
795
796#[proc_macro_derive(IOread, attributes(scroll))]
797pub fn derive_ioread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
798    let ast: syn::DeriveInput = syn::parse(input).unwrap();
799    let generated = impl_from_ctx(&ast);
800    generated.into()
801}
802
803fn impl_into_ctx(
804    name: &syn::Ident,
805    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
806    generics: &syn::Generics,
807) -> proc_macro2::TokenStream {
808    let items: Vec<_> = fields
809        .iter()
810        .enumerate()
811        .map(|(i, f)| {
812            let ident = &f.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
813                let t = proc_macro2::Literal::usize_unsuffixed(i);
814                quote! {#t}
815            });
816            let ty = &f.ty;
817            let mut noctx = false;
818            let size = quote! { ::scroll::export::mem::size_of::<#ty>() };
819            let custom_ctx = custom_ctx(f, &mut noctx);
820            let default_ctx =
821                syn::Ident::new("ctx", proc_macro2::Span::call_site()).into_token_stream();
822            let ctx = custom_ctx.unwrap_or(default_ctx);
823            match ty {
824                syn::Type::Reference(_) => {
825                    panic!("IOwrite cannot be derived for references, because SizeWith cannot be derived for references")
826                }
827                syn::Type::Array(array) => {
828                    let arrty = &array.elem;
829                    quote! {
830                        let size = ::scroll::export::mem::size_of::<#arrty>();
831                        for i in 0..self.#ident.len() {
832                            dst.cwrite_with(self.#ident[i], *offset, #ctx);
833                            *offset += size;
834                        }
835                    }
836                }
837                _ => {
838                    quote! {
839                        dst.cwrite_with(self.#ident, *offset, #ctx);
840                        *offset += #size;
841                    }
842                }
843            }
844        })
845        .collect();
846
847    let gl = &generics.lt_token;
848    let gp = &generics.params;
849    let gg = &generics.gt_token;
850    let gn = gp.iter().map(|param: &syn::GenericParam| match param {
851        syn::GenericParam::Type(t) => {
852            let ident = &t.ident;
853            quote! { #ident }
854        }
855        p => quote! { #p },
856    });
857    let gw = if !gp.is_empty() {
858        let gi = gp.iter().map(|param: &syn::GenericParam| match param {
859            syn::GenericParam::Type(t) => {
860                let ident = &t.ident;
861                quote! {
862                    #ident : ::scroll::ctx::IntoCtx<::scroll::Endian> + ::std::marker::Copy
863                }
864            }
865            p => quote! { #p },
866        });
867        quote! { where #( #gi ),* }
868    } else {
869        quote! {}
870    };
871    let gn = quote! { #gl #( #gn ),* #gg };
872
873    quote! {
874        impl<'a, #gp > ::scroll::ctx::IntoCtx<::scroll::Endian> for &'a #name #gn #gw {
875            #[inline]
876            fn into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) {
877                use ::scroll::Cwrite;
878                let offset = &mut 0;
879                #(#items;)*;
880            }
881        }
882
883        impl #gl #gp #gg ::scroll::ctx::IntoCtx<::scroll::Endian> for #name #gn #gw {
884            #[inline]
885            fn into_ctx(self, dst: &mut [u8], ctx: ::scroll::Endian) {
886                (&self).into_ctx(dst, ctx)
887            }
888        }
889    }
890}
891
892fn impl_iowrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
893    let name = &ast.ident;
894    let generics = &ast.generics;
895    match &ast.data {
896        syn::Data::Struct(data) => match &data.fields {
897            syn::Fields::Named(fields) => impl_into_ctx(name, &fields.named, generics),
898            syn::Fields::Unnamed(fields) => impl_into_ctx(name, &fields.unnamed, generics),
899            _ => {
900                panic!("IOwrite can not be derived for unit structs")
901            }
902        },
903        _ => panic!("IOwrite can only be derived for structs"),
904    }
905}
906
907#[proc_macro_derive(IOwrite, attributes(scroll))]
908pub fn derive_iowrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
909    let ast: syn::DeriveInput = syn::parse(input).unwrap();
910    let generated = impl_iowrite(&ast);
911    generated.into()
912}