1use proc_macro_crate::{FoundCrate, crate_name};
2use proc_macro2::Span;
3use quote::{format_ident, quote};
4use syn::{Data, DeriveInput, Fields, Ident, Meta, Type, parse_macro_input};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7enum AttributeType {
8 LittleEndian,
9 BigEndian,
10 TryTransparent,
11 IoOnly,
12 None,
13}
14
15fn parse_byteable_attr(attrs: &[syn::Attribute]) -> AttributeType {
16 for attr in attrs {
17 if attr.path().is_ident("byteable") {
18 if let Meta::List(meta_list) = &attr.meta {
19 return match meta_list.tokens.to_string().as_str() {
20 "little_endian" => AttributeType::LittleEndian,
21 "big_endian" => AttributeType::BigEndian,
22 "transparent" => AttributeType::None,
23 "try_transparent" => AttributeType::TryTransparent,
24 "io_only" => AttributeType::IoOnly,
25 other => panic!(
26 "Unknown byteable attribute: {other}. \
27 Valid attributes are: little_endian, big_endian, try_transparent, io_only"
28 ),
29 };
30 }
31 panic!(
32 "Unknown byteable attribute. \
33 Valid attributes are: little_endian, big_endian, try_transparent, io_only"
34 );
35 }
36 }
37 AttributeType::None
38}
39
40fn byteable_crate_path() -> proc_macro2::TokenStream {
42 match crate_name("byteable").expect("byteable is present in `Cargo.toml`") {
43 FoundCrate::Itself => quote!(::byteable),
44 FoundCrate::Name(name) => {
45 let ident = Ident::new(&name, Span::call_site());
46 quote!(#ident)
47 }
48 }
49}
50
51#[proc_macro_derive(Byteable, attributes(byteable))]
188pub fn byteable_derive_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
189 let input: DeriveInput = parse_macro_input!(input);
190 match input.data {
191 Data::Struct(_) => return struct_derive(input),
192 Data::Enum(_) => return enum_derive(input),
193 Data::Union(_) => panic!("union structs are unsupported"),
194 }
195}
196
197fn struct_derive(input: DeriveInput) -> proc_macro::TokenStream {
198 if parse_byteable_attr(&input.attrs) == AttributeType::IoOnly {
199 return io_struct_derive(input);
200 }
201 fixed_struct_derived(input)
202}
203
204fn gen_struct_field_write(
205 field_access: &proc_macro2::TokenStream,
206 field_type: &Type,
207 attrs: &[syn::Attribute],
208 bc: &proc_macro2::TokenStream,
209) -> proc_macro2::TokenStream {
210 match parse_byteable_attr(attrs) {
211 AttributeType::LittleEndian => quote! {
212 writer.write_value(&<#field_type as #bc::HasEndianRepr>::to_little_endian(#field_access))?;
213 },
214 AttributeType::BigEndian => quote! {
215 writer.write_value(&<#field_type as #bc::HasEndianRepr>::to_big_endian(#field_access))?;
216 },
217 AttributeType::None => quote! { writer.write_value(&#field_access)?; },
218 AttributeType::IoOnly => {
219 panic!("#[byteable(io_only)] is a struct-level attribute and cannot be used on a field")
220 }
221 AttributeType::TryTransparent => panic!(
222 "#[byteable(try_transparent)] is not applicable in \
223 io_only mode; remove the annotation or use a plain field"
224 ),
225 }
226}
227
228fn gen_field_read(
229 field_ident: &Ident,
230 field_ty: &syn::Type,
231 attrs: &[syn::Attribute],
232 bc: &proc_macro2::TokenStream,
233) -> proc_macro2::TokenStream {
234 match parse_byteable_attr(attrs) {
235 AttributeType::LittleEndian => {
236 quote! { let #field_ident: #field_ty = reader.read_value::<<#field_ty as #bc::HasEndianRepr>::LE>()?.get(); }
237 }
238 AttributeType::BigEndian => {
239 quote! { let #field_ident: #field_ty = reader.read_value::<<#field_ty as #bc::HasEndianRepr>::BE>()?.get(); }
240 }
241 AttributeType::None => quote! { let #field_ident: #field_ty = reader.read_value()?; },
242 other => panic!(
243 "unsupported #[byteable] attribute `{other:?}` on field `{field_ident}`; \
244 only little_endian and big_endian are supported here"
245 ),
246 }
247}
248
249fn io_struct_derive(input: DeriveInput) -> proc_macro::TokenStream {
250 let bc = byteable_crate_path();
251 let name = &input.ident;
252
253 let fields_data = match &input.data {
254 Data::Struct(data) => &data.fields,
255 _ => unreachable!(),
256 };
257
258 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
259
260 if let Fields::Unit = fields_data {
261 let vis = &input.vis;
262 let raw_name = format_ident!("__byteable_raw_{}", name);
263 return quote! {
264 #[derive(Clone, Copy)]
265 #[doc(hidden)]
266 #[allow(non_camel_case_types)]
267 #vis struct #raw_name;
268
269 unsafe impl #bc::PlainOldData for #raw_name {}
270
271 impl #bc::RawRepr for #name {
272 type Raw = #raw_name;
273
274 #[inline]
275 fn to_raw(&self) -> #raw_name {
276 #raw_name
277 }
278 }
279
280 impl #bc::FromRawRepr for #name {
281 #[inline]
282 fn from_raw(value: #raw_name) -> Self {
283 Self
284 }
285 }
286
287 impl #bc::TryFromRawRepr for #name {
288 #[inline]
289 fn try_from_raw(value: #raw_name) -> Result<Self, #bc::DecodeError> {
290 Ok(Self)
291 }
292 }
293
294 impl #bc::IntoByteArray for #raw_name
295 where #raw_name : #bc::PlainOldData
296 {
297 type ByteArray = [u8; ::core::mem::size_of::<Self>()];
298 fn into_byte_array(&self) -> Self::ByteArray {
299 #[allow(unnecessary_transmutes)]
300 unsafe { ::core::mem::transmute(*self) }
301 }
302 }
303
304 impl #bc::FromByteArray for #raw_name
305 where #raw_name : #bc::PlainOldData
306 {
307 fn from_byte_array(byte_array: <Self as #bc::IntoByteArray>::ByteArray) -> Self {
308 #[allow(unnecessary_transmutes)]
309 unsafe { ::core::mem::transmute(byte_array) }
310
311 }
312 }
313
314 impl #bc::IntoByteArray for #name
315 where
316 #name: #bc::RawRepr,
317 <#name as #bc::RawRepr>::Raw: #bc::IntoByteArray,
318 {
319 type ByteArray = <<Self as #bc::RawRepr>::Raw as #bc::IntoByteArray>::ByteArray;
320
321 fn into_byte_array(&self) -> Self::ByteArray {
322 <Self as #bc::RawRepr>::to_raw(self).into_byte_array()
323 }
324 }
325
326 impl #bc::FromByteArray for #name
327 where
328 #name: #bc::FromRawRepr,
329 <#name as #bc::RawRepr>::Raw: #bc::FromByteArray,
330 {
331 fn from_byte_array(byte_array: Self::ByteArray) -> Self {
332 let raw = <<Self as #bc::RawRepr>::Raw as #bc::FromByteArray>::from_byte_array(byte_array);
333 <Self as #bc::FromRawRepr>::from_raw(raw)
334 }
335 }
336 }
337 .into();
338 }
339
340 let (fields, is_tuple) = match fields_data {
341 syn::Fields::Named(f) => (&f.named, false),
342 syn::Fields::Unnamed(f) => (&f.unnamed, true),
343 syn::Fields::Unit => unreachable!(),
344 };
345
346 let write_stmts: Vec<_> = fields
347 .iter()
348 .enumerate()
349 .map(|(i, field)| {
350 let field_access = if is_tuple {
351 let idx = syn::Index::from(i);
352 quote! { self.#idx }
353 } else {
354 let fname = field.ident.as_ref().unwrap();
355 quote! { self.#fname }
356 };
357 gen_struct_field_write(&field_access, &field.ty, &field.attrs, &bc)
358 })
359 .collect();
360
361 let (read_bindings, construct_expr): (Vec<_>, proc_macro2::TokenStream) = if is_tuple {
362 let idents: Vec<_> = (0..fields.len())
363 .map(|i| syn::Ident::new(&format!("__field_{i}"), name.span()))
364 .collect();
365 let bindings = fields
366 .iter()
367 .zip(&idents)
368 .map(|(f, id)| gen_field_read(id, &f.ty, &f.attrs, &bc))
369 .collect();
370 (bindings, quote! { Ok(Self(#(#idents),*)) })
371 } else {
372 let field_idents: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
373 let bindings = fields
374 .iter()
375 .map(|f| gen_field_read(f.ident.as_ref().unwrap(), &f.ty, &f.attrs, &bc))
376 .collect();
377 (bindings, quote! { Ok(Self { #(#field_idents),* }) })
378 };
379
380 quote! {
381 impl #impl_generics #bc::Readable for #name #type_generics #where_clause {
382 fn read_from(mut reader: &mut (impl ::std::io::Read + ?Sized)) -> Result<Self, #bc::ReadableError> {
383 use #bc::ReadValue;
384 #( #read_bindings )*
385 #construct_expr
386 }
387 }
388 impl #impl_generics #bc::Writable for #name #type_generics #where_clause {
389 fn write_to(&self, mut writer: &mut (impl ::std::io::Write + ?Sized)) -> ::std::io::Result<()> {
390 use #bc::WriteValue;
391 #( #write_stmts )*
392 Ok(())
393 }
394 }
395 }.into()
396}
397
398fn fixed_struct_derived(input: DeriveInput) -> proc_macro::TokenStream {
399 let bc = byteable_crate_path();
400 let original_name = &input.ident;
401
402 let fields_data = match &input.data {
405 Data::Struct(data) => &data.fields,
406 _ => unreachable!(),
407 };
408
409 let vis = &input.vis;
410 let raw_name = format_ident!("__byteable_raw_{}", original_name);
411
412 if let Fields::Unit = fields_data {
413 return quote! {
414 #[derive(Clone, Copy)]
415 #[doc(hidden)]
416 #[allow(non_camel_case_types)]
417 #vis struct #raw_name;
418
419 unsafe impl #bc::PlainOldData for #raw_name {}
420
421 impl #bc::RawRepr for #original_name {
422 type Raw = #raw_name;
423
424 #[inline]
425 fn to_raw(&self) -> #raw_name {
426 #raw_name
427 }
428 }
429
430 impl #bc::FromRawRepr for #original_name {
431 #[inline]
432 fn from_raw(value: #raw_name) -> Self {
433 Self
434 }
435 }
436
437 impl #bc::TryFromRawRepr for #original_name {
438 #[inline]
439 fn try_from_raw(value: #raw_name) -> Result<Self, #bc::DecodeError> {
440 Ok(Self)
441 }
442 }
443
444 impl #bc::IntoByteArray for #raw_name
445 where #raw_name : #bc::PlainOldData
446 {
447 type ByteArray = [u8; ::core::mem::size_of::<Self>()];
448 fn into_byte_array(&self) -> Self::ByteArray {
449 #[allow(unnecessary_transmutes)]
450 unsafe { ::core::mem::transmute(*self) }
451 }
452 }
453
454 impl #bc::FromByteArray for #raw_name
455 where #raw_name : #bc::PlainOldData
456 {
457 fn from_byte_array(byte_array: <Self as #bc::IntoByteArray>::ByteArray) -> Self {
458 #[allow(unnecessary_transmutes)]
459 unsafe { ::core::mem::transmute(byte_array) }
460
461 }
462 }
463
464 impl #bc::IntoByteArray for #original_name
465 where
466 #original_name: #bc::RawRepr,
467 <#original_name as #bc::RawRepr>::Raw: #bc::IntoByteArray,
468 {
469 type ByteArray = [u8; ::core::mem::size_of::<<Self as #bc::RawRepr>::Raw>()];
470 fn into_byte_array(&self) -> Self::ByteArray {
471 <Self as #bc::RawRepr>::to_raw(self).into_byte_array()
472 }
473 }
474
475 impl #bc::FromByteArray for #original_name
476 where
477 #original_name: #bc::FromRawRepr,
478 <#original_name as #bc::RawRepr>::Raw: #bc::FromByteArray,
479 {
480 fn from_byte_array(byte_array: Self::ByteArray) -> Self {
481 let raw = <<Self as #bc::RawRepr>::Raw as #bc::FromByteArray>::from_byte_array(byte_array);
482 <Self as #bc::FromRawRepr>::from_raw(raw)
483 }
484 }
485 }
486 .into();
487 }
488
489 let (fields, is_tuple) = match fields_data {
490 Fields::Named(f) => (&f.named, false),
491 Fields::Unnamed(f) => (&f.unnamed, true),
492 Fields::Unit => unreachable!(),
493 };
494
495 struct FieldInfo {
496 raw_field_def: proc_macro2::TokenStream,
497 to_raw_expr: proc_macro2::TokenStream,
498 from_raw_expr: proc_macro2::TokenStream,
499 }
500
501 let mut field_infos = Vec::new();
503 let mut has_try = false;
504
505 for (i, field) in fields.iter().enumerate() {
506 let field_type = &field.ty;
507 let attr = parse_byteable_attr(&field.attrs);
508 if attr == AttributeType::TryTransparent {
509 has_try = true;
510 }
511
512 let field_info = if is_tuple {
513 let idx = syn::Index::from(i);
514 match attr {
515 AttributeType::LittleEndian => FieldInfo {
516 raw_field_def: quote! { #vis <#field_type as #bc::HasEndianRepr>::LE },
517 to_raw_expr: quote! { <#field_type as #bc::HasEndianRepr>::to_little_endian(self.#idx) },
518 from_raw_expr: quote! { <#field_type as #bc::FromEndianRepr>::from_little_endian(value.#idx) },
519 },
520 AttributeType::BigEndian => FieldInfo {
521 raw_field_def: quote! { #vis <#field_type as #bc::HasEndianRepr>::BE },
522 to_raw_expr: quote! { <#field_type as #bc::HasEndianRepr>::to_big_endian(self.#idx) },
523 from_raw_expr: quote! { <#field_type as #bc::FromEndianRepr>::from_big_endian(value.#idx) },
524 },
525 AttributeType::TryTransparent => FieldInfo {
526 raw_field_def: quote! { #vis <#field_type as #bc::RawRepr>::Raw },
527 to_raw_expr: quote! { <#field_type as #bc::RawRepr>::to_raw(&self.#idx) },
528 from_raw_expr: quote! { <#field_type as #bc::TryFromRawRepr>::try_from_raw(value.#idx)? },
529 },
530 AttributeType::IoOnly => panic!(
531 "#[byteable(io_only)] is a struct-level attribute and cannot be used on individual fields"
532 ),
533 AttributeType::None => FieldInfo {
534 raw_field_def: quote! { #vis <#field_type as #bc::RawRepr>::Raw },
535 to_raw_expr: quote! { <#field_type as #bc::RawRepr>::to_raw(&self.#idx) },
536 from_raw_expr: quote! { <#field_type as #bc::FromRawRepr>::from_raw(value.#idx) },
537 },
538 }
539 } else {
540 let name = field.ident.as_ref().unwrap();
541 match attr {
542 AttributeType::LittleEndian => FieldInfo {
543 raw_field_def: quote! { #vis #name: <#field_type as #bc::HasEndianRepr>::LE },
544 to_raw_expr: quote! { #name: <#field_type as #bc::HasEndianRepr>::to_little_endian(self.#name) },
545 from_raw_expr: quote! { #name: <#field_type as #bc::FromEndianRepr>::from_little_endian(value.#name) },
546 },
547 AttributeType::BigEndian => FieldInfo {
548 raw_field_def: quote! { #vis #name: <#field_type as #bc::HasEndianRepr>::BE },
549 to_raw_expr: quote! { #name: <#field_type as #bc::HasEndianRepr>::to_big_endian(self.#name) },
550 from_raw_expr: quote! { #name: <#field_type as #bc::FromEndianRepr>::from_big_endian(value.#name) },
551 },
552 AttributeType::TryTransparent => FieldInfo {
553 raw_field_def: quote! { #vis #name: <#field_type as #bc::RawRepr>::Raw },
554 to_raw_expr: quote! { #name: <#field_type as #bc::RawRepr>::to_raw(&self.#name) },
555 from_raw_expr: quote! { #name: <#field_type as #bc::TryFromRawRepr>::try_from_raw(value.#name)? },
556 },
557 AttributeType::IoOnly => panic!(
558 "#[byteable(io_only)] is a struct-level attribute and cannot be used on individual fields"
559 ),
560 AttributeType::None => FieldInfo {
561 raw_field_def: quote! { #vis #name: <#field_type as #bc::RawRepr>::Raw },
562 to_raw_expr: quote! { #name: <#field_type as #bc::RawRepr>::to_raw(&self.#name) },
563 from_raw_expr: quote! { #name: <#field_type as #bc::FromRawRepr>::from_raw(value.#name) },
564 },
565 }
566 };
567 field_infos.push(field_info);
568 }
569
570 let raw_struct_def = {
571 let field_defs = field_infos.iter().map(|v| &v.raw_field_def);
572 if is_tuple {
573 quote! {
574 #[derive(Clone, Copy)]
575 #[repr(C, packed)]
576 #[doc(hidden)]
577 #[allow(non_camel_case_types)]
578 #vis struct #raw_name( #(#field_defs),* );
579 }
580 } else {
581 quote! {
582 #[derive(Clone, Copy)]
583 #[repr(C, packed)]
584 #[doc(hidden)]
585 #[allow(non_camel_case_types)]
586 #vis struct #raw_name { #(#field_defs),* }
587 }
588 }
589 };
590
591 let raw_impls = {
592 quote! {
593 unsafe impl #bc::PlainOldData for #raw_name {}
594
595 impl #bc::IntoByteArray for #raw_name
596 where #raw_name : #bc::PlainOldData
597 {
598 type ByteArray = [u8; ::core::mem::size_of::<Self>()];
599 fn into_byte_array(&self) -> Self::ByteArray {
600 #[allow(unnecessary_transmutes)]
601 unsafe { ::core::mem::transmute(*self) }
602 }
603 }
604
605 impl #bc::FromByteArray for #raw_name
606 where #raw_name : #bc::PlainOldData
607 {
608 fn from_byte_array(byte_array: <Self as #bc::IntoByteArray>::ByteArray) -> Self {
609 #[allow(unnecessary_transmutes)]
610 unsafe { ::core::mem::transmute(byte_array) }
611
612 }
613 }
614 }
615 };
616
617 let from_raw_body = {
618 let from_raw_exprs = field_infos.iter().map(|v| &v.from_raw_expr);
619 if is_tuple {
620 quote! { Self(#(#from_raw_exprs),*) }
621 } else {
622 quote! { Self { #(#from_raw_exprs),* } }
623 }
624 };
625
626 let raw_repr = {
627 let to_raw_exprs = field_infos.iter().map(|v| &v.to_raw_expr);
628 if is_tuple {
629 quote! {
630 impl #bc::RawRepr for #original_name {
631 type Raw = #raw_name;
632
633 #[inline]
634 fn to_raw(&self) -> Self::Raw {
635 #raw_name (#(#to_raw_exprs),*)
636 }
637 }
638
639 impl #bc::IntoByteArray for #original_name
640 where
641 #original_name: #bc::RawRepr,
642 <#original_name as #bc::RawRepr>::Raw: #bc::IntoByteArray,
643 {
644 type ByteArray = <<Self as #bc::RawRepr>::Raw as #bc::IntoByteArray>::ByteArray;
645 fn into_byte_array(&self) -> Self::ByteArray {
646 <Self as #bc::RawRepr>::to_raw(self).into_byte_array()
647 }
648 }
649
650 }
651 } else {
652 quote! {
653 impl #bc::RawRepr for #original_name {
654 type Raw = #raw_name;
655
656 #[inline]
657 fn to_raw(&self) -> Self::Raw {
658 #raw_name { #(#to_raw_exprs),* }
659 }
660 }
661
662 impl #bc::IntoByteArray for #original_name
663 where
664 #original_name: #bc::RawRepr,
665 <#original_name as #bc::RawRepr>::Raw: #bc::IntoByteArray,
666 {
667 type ByteArray = <<Self as #bc::RawRepr>::Raw as #bc::IntoByteArray>::ByteArray;
668 fn into_byte_array(&self) -> Self::ByteArray {
669 <Self as #bc::RawRepr>::to_raw(self).into_byte_array()
670 }
671 }
672 }
673 }
674 };
675
676 let original_impls = if has_try {
677 quote! {
678 impl #bc::TryFromRawRepr for #original_name {
679 #[inline]
680 fn try_from_raw(value: #raw_name) -> Result<Self, #bc::DecodeError> { Ok(#from_raw_body) }
681 }
682
683 impl #bc::TryFromByteArray for #original_name
684 where
685 #original_name: #bc::TryFromRawRepr,
686 <#original_name as #bc::RawRepr>::Raw: #bc::FromByteArray,
687 {
688 fn try_from_byte_array(byte_array: Self::ByteArray) -> Result<Self, #bc::DecodeError> {
689 let raw = <<Self as #bc::RawRepr>::Raw as #bc::FromByteArray>::from_byte_array(byte_array);
690 <Self as #bc::TryFromRawRepr>::try_from_raw(raw)
691 }
692 }
693
694 }
695 } else {
696 quote! {
697 impl #bc::FromRawRepr for #original_name {
698 #[inline]
699 fn from_raw(value: #raw_name) -> Self { #from_raw_body }
700 }
701
702 impl #bc::TryFromRawRepr for #original_name {
703 #[inline]
704 fn try_from_raw(value: #raw_name) -> Result<Self, #bc::DecodeError> { Ok(<Self as #bc::FromRawRepr>::from_raw(value)) }
705 }
706
707
708 impl #bc::FromByteArray for #original_name
709 where
710 #original_name: #bc::FromRawRepr,
711 <#original_name as #bc::RawRepr>::Raw: #bc::FromByteArray,
712 {
713 fn from_byte_array(byte_array: Self::ByteArray) -> Self {
714 let raw = <<Self as #bc::RawRepr>::Raw as #bc::FromByteArray>::from_byte_array(byte_array);
715 <Self as #bc::FromRawRepr>::from_raw(raw)
716 }
717 }
718 }
719 };
720
721 quote! {
722 #raw_struct_def
723 #raw_impls
724 #raw_repr
725 #original_impls
726 }
727 .into()
728}
729
730fn extract_repr_type(attrs: &[syn::Attribute]) -> Option<syn::Ident> {
731 for attr in attrs {
732 if attr.path().is_ident("repr") {
733 if let Meta::List(meta_list) = &attr.meta {
734 if let Ok(ident) = syn::parse2::<syn::Ident>(meta_list.tokens.clone()) {
735 if matches!(
736 ident.to_string().as_str(),
737 "u8" | "i8"
738 | "u16"
739 | "i16"
740 | "u32"
741 | "i32"
742 | "u64"
743 | "i64"
744 | "u128"
745 | "i128"
746 ) {
747 return Some(ident);
748 }
749 }
750 }
751 }
752 }
753 None
754}
755
756fn gen_enum_field_write(
757 field_ident: &Ident,
758 field_type: &Type,
759 attrs: &[syn::Attribute],
760 bc: &proc_macro2::TokenStream,
761) -> proc_macro2::TokenStream {
762 match parse_byteable_attr(attrs) {
763 AttributeType::LittleEndian => quote! {
764 writer.write_value(&<#field_type as #bc::HasEndianRepr>::to_little_endian(*#field_ident))?;
765 },
766 AttributeType::BigEndian => quote! {
767 writer.write_value(&<#field_type as #bc::HasEndianRepr>::to_big_endian(*#field_ident))?;
768 },
769 AttributeType::None => quote! {
770 writer.write_value(#field_ident)?;
771 },
772 other => panic!(
773 "unsupported #[byteable] attribute `{other:?}` on field `{field_ident}`; \
774 only little_endian and big_endian are supported here"
775 ),
776 }
777}
778
779fn enum_derive(input: DeriveInput) -> proc_macro::TokenStream {
780 let Data::Enum(enum_data) = &input.data else {
781 unreachable!();
782 };
783 let has_field_variants = enum_data
784 .variants
785 .iter()
786 .any(|v| !matches!(v.fields, Fields::Unit));
787 if !has_field_variants {
788 return unit_enum_derive(input);
789 }
790 let name = input.ident;
791 let bc = byteable_crate_path();
792
793 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
796
797 let repr_ty = extract_repr_type(&input.attrs).unwrap_or_else(|| {
799 let n = enum_data.variants.len();
800 let ty_str = if n <= 256 {
801 "u8"
802 } else if n <= 65_536 {
803 "u16"
804 } else if n as u64 <= u32::MAX as u64 + 1 {
805 "u32"
806 } else {
807 "u64"
808 };
809 Ident::new(ty_str, name.span())
810 });
811
812 let endian_attr = parse_byteable_attr(&input.attrs);
813 let discriminants = compute_discriminants(&enum_data.variants);
814
815 let read_disc = match endian_attr {
816 AttributeType::LittleEndian => quote! {
817 let disc: #repr_ty = <#repr_ty as #bc::FromEndianRepr>::from_little_endian(
818 reader.read_value::<<#repr_ty as #bc::HasEndianRepr>::LE>()?
819 );
820 },
821 AttributeType::BigEndian => quote! {
822 let disc: #repr_ty = <#repr_ty as #bc::FromEndianRepr>::from_big_endian(
823 reader.read_value::<<#repr_ty as #bc::HasEndianRepr>::BE>()?
824 );
825 },
826 _ => quote! {
827 let disc: #repr_ty = reader.read_value()?;
828 },
829 };
830
831 let write_arms = enum_data
832 .variants
833 .iter()
834 .zip(&discriminants)
835 .map(|(variant, disc_tokens)| {
836 let variant_name = &variant.ident;
837 let write_disc = match endian_attr {
838 AttributeType::LittleEndian => quote! {
839 let disc_val: #repr_ty = #disc_tokens;
840 writer.write_value(&<#repr_ty as #bc::HasEndianRepr>::to_little_endian(disc_val))?;
841 },
842 AttributeType::BigEndian => quote! {
843 let disc_val: #repr_ty = #disc_tokens;
844 writer.write_value(&<#repr_ty as #bc::HasEndianRepr>::to_big_endian(disc_val))?;
845 },
846 _ => quote! {
847 let disc_val: #repr_ty = #disc_tokens;
848 writer.write_value(&disc_val)?;
849 },
850 };
851 match &variant.fields {
852 Fields::Unit => quote! {
853 #name::#variant_name => { #write_disc }
854 },
855 Fields::Named(named) => {
856 let field_names: Vec<_> = named
857 .named
858 .iter()
859 .map(|f| f.ident.as_ref().unwrap())
860 .collect();
861 let field_writes: Vec<_> = named
862 .named
863 .iter()
864 .map(|f| {
865 gen_enum_field_write(f.ident.as_ref().unwrap(), &f.ty, &f.attrs, &bc)
866 })
867 .collect();
868 quote! {
869 #name::#variant_name { #(#field_names),* } => {
870 #write_disc
871 #( #field_writes )*
872 }
873 }
874 }
875 Fields::Unnamed(unnamed) => {
876 let field_idents: Vec<_> = (0..unnamed.unnamed.len())
877 .map(|i| Ident::new(&format!("__field_{i}"), name.span()))
878 .collect();
879 let field_writes: Vec<_> = unnamed
880 .unnamed
881 .iter()
882 .zip(&field_idents)
883 .map(|(f, ident)| gen_enum_field_write(ident, &f.ty, &f.attrs, &bc))
884 .collect();
885 quote! {
886 #name::#variant_name(#(#field_idents),*) => {
887 #write_disc
888 #( #field_writes )*
889 }
890 }
891 }
892 }
893 });
894 let read_arms = enum_data
895 .variants
896 .iter()
897 .zip(&discriminants)
898 .map(|(variant, disc_tokens)| {
899 let variant_name = &variant.ident;
900
901 match &variant.fields {
902 Fields::Unit => quote! {
903 #disc_tokens => Ok(#name::#variant_name),
904 },
905 Fields::Named(named) => {
906 let field_idents: Vec<_> = named
907 .named
908 .iter()
909 .map(|f| f.ident.as_ref().unwrap())
910 .collect();
911 let field_reads: Vec<_> = named
912 .named
913 .iter()
914 .map(|f| gen_field_read(f.ident.as_ref().unwrap(), &f.ty, &f.attrs, &bc))
915 .collect();
916 quote! {
917 #disc_tokens => {
918 #( #field_reads )*
919 Ok(#name::#variant_name { #(#field_idents),* })
920 }
921 }
922 }
923 Fields::Unnamed(unnamed) => {
924 let field_idents: Vec<_> = (0..unnamed.unnamed.len())
925 .map(|i| Ident::new(&format!("__field_{i}"), name.span()))
926 .collect();
927 let field_reads: Vec<_> = unnamed
928 .unnamed
929 .iter()
930 .zip(&field_idents)
931 .map(|(f, ident)| gen_field_read(ident, &f.ty, &f.attrs, &bc))
932 .collect();
933 quote! {
934 #disc_tokens => {
935 #( #field_reads )*
936 Ok(#name::#variant_name(#(#field_idents),*))
937 }
938 }
939 }
940 }
941 });
942 quote! {
943 impl #impl_generics #bc::Writable for #name #type_generics #where_clause {
944 fn write_to(&self, mut writer: &mut (impl ::std::io::Write + ?Sized)) -> ::std::io::Result<()> {
945 use #bc::WriteValue;
946 match self {
947 #(#write_arms)*
948 }
949 Ok(())
950 }
951 }
952
953
954 impl #impl_generics #bc::Readable for #name #type_generics #where_clause {
955 fn read_from(mut reader: &mut (impl ::std::io::Read + ?Sized)) -> Result<Self, #bc::ReadableError> {
956 use #bc::ReadValue;
957 #read_disc
958 match disc {
959 #(#read_arms)*
960 _ => Err(#bc::ReadableError::DecodeError(#bc::DecodeError::InvalidDiscriminant { raw: disc as u64, type_name: ::core::stringify!(#name) })),
961 }
962 }
963 }
964 }.into()
965}
966
967fn try_eval_int_expr(expr: &syn::Expr) -> Option<u128> {
968 match expr {
969 syn::Expr::Lit(el) => {
970 if let syn::Lit::Int(li) = &el.lit {
971 if let Ok(v) = li.base10_parse::<u128>() {
973 return Some(v);
974 }
975 let s = li.to_string();
977 let (prefix, rest) =
978 if let Some(r) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
979 (16u32, r)
980 } else if let Some(r) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
981 (2, r)
982 } else if let Some(r) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) {
983 (8, r)
984 } else {
985 return None;
986 };
987 let digits: String = rest
989 .chars()
990 .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
991 .filter(|c| *c != '_' && !c.is_alphabetic())
992 .collect();
993 u128::from_str_radix(&digits, prefix).ok()
994 } else {
995 None
996 }
997 }
998 _ => None,
999 }
1000}
1001
1002fn compute_discriminants(
1009 variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
1010) -> Vec<proc_macro2::TokenStream> {
1011 let mut next: u128 = 0;
1012 variants
1013 .iter()
1014 .map(|v| {
1015 if let Some((_, expr)) = &v.discriminant {
1016 if let Some(val) = try_eval_int_expr(expr) {
1018 next = val + 1;
1019 } else {
1020 next += 1;
1021 }
1022 quote! { #expr }
1023 } else {
1024 let val = next;
1025 next += 1;
1026 let lit = proc_macro2::Literal::u128_unsuffixed(val);
1027 quote! { #lit }
1028 }
1029 })
1030 .collect()
1031}
1032
1033fn unit_enum_derive(input: DeriveInput) -> proc_macro::TokenStream {
1034 let bc = byteable_crate_path();
1035 let Data::Enum(enum_data) = &input.data else {
1036 unreachable!();
1037 };
1038 let enum_name = &input.ident;
1039
1040 let repr_ty = extract_repr_type(&input.attrs).unwrap_or_else(|| {
1041 let n = enum_data.variants.len();
1042 let ty_str = if n <= 256 {
1043 "u8"
1044 } else if n <= 65_536 {
1045 "u16"
1046 } else if n as u64 <= u32::MAX as u64 + 1 {
1047 "u32"
1048 } else {
1049 "u64"
1050 };
1051 Ident::new(ty_str, enum_name.span())
1052 });
1053
1054 let endian_attr = parse_byteable_attr(&input.attrs);
1055 let discriminants = compute_discriminants(&enum_data.variants);
1056
1057 let from_discriminant_arms =
1058 enum_data
1059 .variants
1060 .iter()
1061 .zip(&discriminants)
1062 .map(|(variant, disc)| {
1063 let variant_name = &variant.ident;
1064 quote! { #disc => Ok(#enum_name::#variant_name), }
1065 });
1066
1067 let into_byte_array_body = match endian_attr {
1068 AttributeType::LittleEndian => quote! {
1069 let v: #repr_ty = *self as _;
1070 <#repr_ty as #bc::HasEndianRepr>::to_little_endian(v).into_byte_array()
1071 },
1072 AttributeType::BigEndian => quote! {
1073 let v: #repr_ty = *self as _;
1074 <#repr_ty as #bc::HasEndianRepr>::to_big_endian(v).into_byte_array()
1075 },
1076 _ => quote! {
1077 let v: #repr_ty = *self as _;
1078 <#repr_ty as #bc::IntoByteArray>::into_byte_array(&v)
1079 },
1080 };
1081
1082 let try_from_byte_array_body = match endian_attr {
1083 AttributeType::LittleEndian => quote! {
1084 let le = <<#repr_ty as #bc::HasEndianRepr>::LE as #bc::FromByteArray>::from_byte_array(byte_array);
1085 let raw = <#repr_ty as #bc::FromEndianRepr>::from_little_endian(le);
1086 <Self as #bc::TryFromRawRepr>::try_from_raw(raw)
1087 },
1088 AttributeType::BigEndian => quote! {
1089 let be = <<#repr_ty as #bc::HasEndianRepr>::BE as #bc::FromByteArray>::from_byte_array(byte_array);
1090 let raw = <#repr_ty as #bc::FromEndianRepr>::from_big_endian(be);
1091 <Self as #bc::TryFromRawRepr>::try_from_raw(raw)
1092 },
1093 _ => quote! {
1094 let raw = <#repr_ty as #bc::FromByteArray>::from_byte_array(byte_array);
1095 <Self as #bc::TryFromRawRepr>::try_from_raw(raw)
1096 },
1097 };
1098
1099 quote! {
1100 impl #bc::RawRepr for #enum_name {
1101 type Raw = #repr_ty;
1102 fn to_raw(&self) -> #repr_ty {
1103 *self as _
1104 }
1105 }
1106
1107 impl #bc::TryFromRawRepr for #enum_name {
1108 fn try_from_raw(raw: Self::Raw) -> Result<Self, #bc::DecodeError> {
1109 match raw {
1110 #(#from_discriminant_arms)*
1111 _ => Err(#bc::DecodeError::InvalidDiscriminant { raw: raw as u64, type_name: ::core::stringify!(#enum_name) })
1112 }
1113 }
1114 }
1115
1116 impl #bc::IntoByteArray for #enum_name {
1117 type ByteArray = [u8; ::core::mem::size_of::<#repr_ty>()];
1118 fn into_byte_array(&self) -> Self::ByteArray {
1119 #into_byte_array_body
1120 }
1121 }
1122
1123 impl #bc::TryFromByteArray for #enum_name {
1124 fn try_from_byte_array(byte_array: Self::ByteArray) -> Result<Self, #bc::DecodeError> {
1125 #try_from_byte_array_body
1126 }
1127 }
1128
1129 }
1130 .into()
1131}