Skip to content

Commit 19e04c5

Browse files
committed
derive: allow primitive, fieldless enums to derive Pread/Pwrite; add example, add tests
1 parent 9536f0d commit 19e04c5

3 files changed

Lines changed: 197 additions & 2 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use scroll::Pread;
2+
use scroll_derive::{Pread, Pwrite};
3+
4+
const GOOD: u16 = 0x10;
5+
const BAD: u16 = 0x20;
6+
const UGLY: u16 = 0x30;
7+
8+
#[derive(Debug, PartialEq, Pread, Pwrite)]
9+
#[repr(u16)]
10+
enum Service {
11+
Good = GOOD,
12+
Bad = BAD,
13+
Ugly = UGLY,
14+
}
15+
16+
fn main() {
17+
let bytes = [0x10, 0x0, 0x30, 0x0, 0x20, 0x0];
18+
fn services(bytes: &[u8]) -> impl Iterator<Item = Service> {
19+
bytes
20+
.chunks(std::mem::size_of::<Service>())
21+
.filter_map(|chunk| chunk.pread(0).ok())
22+
}
23+
let services = services(&bytes);
24+
use Service::*;
25+
for (s1, s2) in services.zip([Good, Ugly, Bad].into_iter()) {
26+
println!("{s1:?},{s2:?}");
27+
assert_eq!(s1, s2);
28+
}
29+
}

scroll_derive/src/lib.rs

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
279359
fn 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+
454567
fn 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

scroll_derive/tests/tests.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,52 @@ fn test_pread_lifetime() {
302302
b2.pwrite(&data, 0).unwrap();
303303
assert_eq!(b2, bytes);
304304
}
305+
306+
#[derive(Pread, Debug, PartialEq, Eq)]
307+
#[repr(u8)]
308+
enum Foo {
309+
One = 1,
310+
Two,
311+
Three = 10,
312+
}
313+
314+
#[test]
315+
fn test_enum_u8() {
316+
let bytes = [10, 1, 2, 0];
317+
let res = bytes.pread::<Foo>(0).unwrap();
318+
assert_eq!(Foo::Three, res);
319+
let res = bytes.pread::<Foo>(1).unwrap();
320+
assert_eq!(Foo::One, res);
321+
let res = bytes.pread::<Foo>(2).unwrap();
322+
assert_eq!(Foo::Two, res);
323+
}
324+
325+
#[test]
326+
fn test_roundtrip_enum_u32() {
327+
#[derive(Pread, Pwrite, Debug, PartialEq, Eq)]
328+
#[repr(u32)]
329+
enum Foo {
330+
One = 1,
331+
Two,
332+
Three = 10,
333+
}
334+
335+
let mut bytes = [10, 0, 0, 0];
336+
let foo_three = bytes.pread::<Foo>(0).unwrap();
337+
assert_eq!(Foo::Three, foo_three);
338+
bytes[0] = 1;
339+
let foo_one = bytes.pread::<Foo>(0).unwrap();
340+
assert_eq!(Foo::One, foo_one);
341+
bytes[0] = 2;
342+
let foo_two = bytes.pread::<Foo>(0).unwrap();
343+
assert_eq!(Foo::Two, foo_two);
344+
let error = bytes.pread::<Foo>(1);
345+
assert!(error.is_err());
346+
assert_eq!(bytes.pwrite(foo_two, 0).unwrap(), 4);
347+
assert_eq!(bytes, [2, 0, 0, 0]);
348+
assert_eq!(bytes.pwrite(foo_one, 0).unwrap(), 4);
349+
assert_eq!(bytes, [1, 0, 0, 0]);
350+
assert_eq!(bytes.pwrite(foo_three, 0).unwrap(), 4);
351+
assert_eq!(bytes, [10, 0, 0, 0]);
352+
assert!(bytes.pwrite(Foo::Three, 1).is_err());
353+
}

0 commit comments

Comments
 (0)