@@ -276,6 +276,86 @@ fn impl_struct(
276276 }
277277}
278278
279+ fn 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+
287+ const VALID_PRIMITIVE_REPRS : & [ & ' static str ] = & [
288+ "i8" , "i16" , "i32" , "i64" , "i128" , "u8" , "u16" , "u32" , "u64" , "u128" ,
289+ ] ;
290+
291+ fn 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+
312+ fn 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+
353+ fn 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+
279359fn impl_try_from_ctx ( ast : & syn:: DeriveInput ) -> proc_macro2:: TokenStream {
280360 let name = & ast. ident ;
281361 let generics = & ast. generics ;
@@ -287,7 +367,11 @@ fn impl_try_from_ctx(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
287367 panic ! ( "Pread can not be derived for unit structs" )
288368 }
289369 } ,
290- _ => panic ! ( "Pread can only be derived for structs" ) ,
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" ) ,
291375 }
292376}
293377
@@ -451,6 +535,35 @@ fn impl_try_into_ctx(
451535 }
452536}
453537
538+ fn 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+
454567fn impl_pwrite ( ast : & syn:: DeriveInput ) -> proc_macro2:: TokenStream {
455568 let name = & ast. ident ;
456569 let generics = & ast. generics ;
@@ -462,7 +575,11 @@ fn impl_pwrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
462575 panic ! ( "Pwrite can not be derived for unit structs" )
463576 }
464577 } ,
465- _ => panic ! ( "Pwrite can only be derived for structs" ) ,
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" ) ,
466583 }
467584}
468585
0 commit comments