bitfield_struct/
lib.rs

1// Generate docs from readme
2#![doc = include_str!("../README.md")]
3#![warn(clippy::unwrap_used)]
4
5use proc_macro as pc;
6use proc_macro2::{Ident, TokenStream};
7use quote::{format_ident, quote, ToTokens};
8use std::{fmt, stringify};
9use syn::spanned::Spanned;
10
11mod attr;
12use attr::*;
13mod bitenum;
14mod traits;
15
16/// Creates a bitfield for this struct.
17///
18/// The arguments first, have to begin with the integer type of the bitfield:
19/// For example: `#[bitfield(u64)]`.
20///
21/// It can contain the following additional parameters, like the `debug` argument
22/// for disabling the `Debug` trait generation (`#[bitfield(u64, debug = false)]`).
23///
24/// Parameters of the `bitfield` attribute:
25/// - the bitfield integer type (required)
26/// - `repr` specifies the bitfield's representation in memory
27/// - `from` to specify a conversion function from repr to the bitfield's integer type
28/// - `into` to specify a conversion function from the bitfield's integer type to repr
29/// - `new` to disable the `new` function generation
30/// - `binread` to enable the `BinRead` trait generation
31/// - `binwrite` to enable the `BinWrite` trait generation
32/// - `binrw` to enable both `BinRead` and `BinWrite` trait generation
33/// - `clone` to disable the `Clone` trait generation
34/// - `debug` to disable the `Debug` trait generation
35/// - `defmt` to enable the `defmt::Format` trait generation
36/// - `default` to disable the `Default` trait generation
37/// - `hash` to generate the `Hash` trait
38/// - `order` to specify the bit order (Lsb, Msb)
39/// - `conversion` to disable the generation of `into_bits` and `from_bits`
40///
41/// > For `new`, `clone`, `debug`, `defmt` or `default`, you can either use booleans
42/// > (`#[bitfield(u8, debug = false)]`) or cfg attributes
43/// > (`#[bitfield(u8, debug = cfg(test))]`) to enable/disable them.
44///
45/// Parameters of the `bits` attribute (for fields):
46/// - the number of bits
47/// - `access` to specify the access mode (RW, RO, WO, None)
48/// - `default` to set a default value
49/// - `into` to specify a conversion function from the field type to the bitfield type
50/// - `from` to specify a conversion function from the bitfield type to the field type
51#[proc_macro_attribute]
52pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream {
53    match bitfield_inner(args.into(), input.into()) {
54        Ok(result) => result.into(),
55        Err(e) => e.into_compile_error().into(),
56    }
57}
58
59/// Implements the constant `into_bits` and `from_bits` functions for the given enum.
60///
61/// # Parameters
62///
63/// - `into`: Enable/disable the `into_bits` function generation.
64/// - `from`: Enable/disable the `from_bits` function generation.
65/// - `all`: Enable/disable the `all` function generation.
66///
67/// > For `into`, `from`, and `all`, you can either use booleans
68/// > (`#[bitfield(u8, debug = false)]`) or cfg attributes
69/// > (`#[bitfield(u8, debug = cfg(test))]`) to enable/disable them.
70///
71/// # Examples
72///
73/// ```rust
74/// use bitfield_struct::bitenum;
75///
76/// #[bitenum(all = false)]
77/// #[repr(u8)]
78/// #[derive(Debug, PartialEq, Eq)]
79/// enum MyEnum {
80///     #[fallback]
81///     A = 0,
82///     B = 1,
83///     C = 2,
84/// }
85///
86/// assert_eq!(MyEnum::from_bits(1), MyEnum::B);
87/// assert_eq!(MyEnum::C.into_bits(), 2);
88/// ```
89///
90/// Use the `#[fallback]` attribute to specify a fallback variant for invalid bit patterns.
91#[proc_macro_attribute]
92pub fn bitenum(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream {
93    match bitenum::bitenum_inner(args.into(), input.into()) {
94        Ok(result) => result.into(),
95        Err(e) => e.into_compile_error().into(),
96    }
97}
98
99fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
100    let input = syn::parse2::<syn::ItemStruct>(input)?;
101    let Params {
102        ty,
103        repr,
104        into,
105        from,
106        bits,
107        binread,
108        binwrite,
109        new,
110        clone,
111        debug,
112        defmt,
113        default,
114        hash,
115        order,
116        conversion,
117    } = syn::parse2(args)?;
118
119    let span = input.fields.span();
120    let name = input.ident;
121    let vis = input.vis;
122    let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect();
123    let derive = match clone {
124        Enable::No => None,
125        Enable::Yes => Some(quote! { #[derive(Copy, Clone)] }),
126        Enable::Cfg(cfg) => Some(quote! { #[cfg_attr(#cfg, derive(Copy, Clone))] }),
127    };
128
129    let syn::Fields::Named(fields) = input.fields else {
130        return Err(s_err(span, "only named fields are supported"));
131    };
132
133    let mut offset = 0;
134    let mut members = Vec::with_capacity(fields.named.len());
135    for field in fields.named {
136        let f = Member::new(
137            ty.clone(),
138            bits,
139            into.clone(),
140            from.clone(),
141            field,
142            offset,
143            order,
144        )?;
145        offset += f.bits;
146        members.push(f);
147    }
148
149    if offset < bits {
150        return Err(s_err(
151            span,
152            format!(
153                "The bitfield size ({bits} bits) has to be equal to the sum of its fields ({offset} bits). \
154                You might have to add padding (a {} bits large field prefixed with \"_\").",
155                bits - offset
156            ),
157        ));
158    }
159    if offset > bits {
160        return Err(s_err(
161            span,
162            format!(
163                "The size of the fields ({offset} bits) is larger than the type ({bits} bits)."
164            ),
165        ));
166    }
167
168    let mut impl_debug = TokenStream::new();
169    if let Some(cfg) = debug.cfg() {
170        impl_debug.extend(traits::debug(&name, &members, cfg));
171    }
172    if let Some(cfg) = defmt.cfg() {
173        impl_debug.extend(traits::defmt(&name, &members, cfg));
174    }
175    if let Some(cfg) = hash.cfg() {
176        impl_debug.extend(traits::hash(&name, &members, cfg));
177    }
178    if let Some(cfg) = binread.cfg() {
179        impl_debug.extend(traits::binread(&name, &repr, cfg));
180    }
181    if let Some(cfg) = binwrite.cfg() {
182        impl_debug.extend(traits::binwrite(&name, cfg));
183    }
184
185    let defaults = members.iter().map(Member::default).collect::<Vec<_>>();
186
187    let impl_new = new.cfg().map(|cfg| {
188        let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)]));
189        quote! {
190            /// Creates a new default initialized bitfield.
191            #attr
192            #vis const fn new() -> Self {
193                let mut this = Self(#from(0));
194                #( #defaults )*
195                this
196            }
197        }
198    });
199
200    let impl_default = default.cfg().map(|cfg| {
201        let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)]));
202        quote! {
203            #attr
204            impl Default for #name {
205                fn default() -> Self {
206                    let mut this = Self(#from(0));
207                    #( #defaults )*
208                    this
209                }
210            }
211        }
212    });
213
214    let conversion = conversion.then(|| {
215        quote! {
216            /// Convert from bits.
217            #vis const fn from_bits(bits: #repr) -> Self {
218                Self(bits)
219            }
220            /// Convert into bits.
221            #vis const fn into_bits(self) -> #repr {
222                self.0
223            }
224        }
225    });
226
227    Ok(quote! {
228        #attrs
229        #derive
230        #[repr(transparent)]
231        #vis struct #name(#repr);
232
233        #[allow(unused_comparisons)]
234        #[allow(clippy::unnecessary_cast)]
235        #[allow(clippy::assign_op_pattern)]
236        #[allow(clippy::double_parens)]
237        impl #name {
238            #impl_new
239
240            #conversion
241
242            #( #members )*
243        }
244
245        #[allow(unused_comparisons)]
246        #[allow(clippy::unnecessary_cast)]
247        #[allow(clippy::assign_op_pattern)]
248        #[allow(clippy::double_parens)]
249        #impl_default
250
251        impl From<#repr> for #name {
252            fn from(v: #repr) -> Self {
253                Self(v)
254            }
255        }
256        impl From<#name> for #repr {
257            fn from(v: #name) -> #repr {
258                v.0
259            }
260        }
261
262        #impl_debug
263    })
264}
265
266/// Represents a member where accessor functions should be generated for.
267struct Member {
268    offset: usize,
269    bits: usize,
270    base_ty: syn::Type,
271    repr_into: Option<syn::Path>,
272    repr_from: Option<syn::Path>,
273    default: TokenStream,
274    inner: Option<MemberInner>,
275}
276
277struct MemberInner {
278    ident: syn::Ident,
279    ty: syn::Type,
280    attrs: Vec<syn::Attribute>,
281    vis: syn::Visibility,
282    into: TokenStream,
283    from: TokenStream,
284}
285
286impl Member {
287    fn new(
288        base_ty: syn::Type,
289        base_bits: usize,
290        repr_into: Option<syn::Path>,
291        repr_from: Option<syn::Path>,
292        field: syn::Field,
293        offset: usize,
294        order: Order,
295    ) -> syn::Result<Self> {
296        let span = field.span();
297
298        let syn::Field {
299            mut attrs,
300            vis,
301            ident,
302            ty,
303            ..
304        } = field;
305
306        let ident = ident.ok_or_else(|| s_err(span, "Not supported"))?;
307        let ignore = ident.to_string().starts_with('_');
308
309        let Field {
310            bits,
311            ty,
312            mut default,
313            into,
314            from,
315            access,
316        } = parse_field(&base_ty, &attrs, &ty, ignore)?;
317
318        let ignore = ignore || access == Access::None;
319
320        // compute the offset
321        let offset = if order == Order::Lsb {
322            offset
323        } else {
324            base_bits - offset - bits
325        };
326
327        if bits > 0 && !ignore {
328            // overflow check
329            if offset + bits > base_bits {
330                return Err(s_err(
331                    ty.span(),
332                    "The sum of the members overflows the type size",
333                ));
334            };
335
336            // clear conversion expr if not needed
337            let (from, into) = match access {
338                Access::ReadWrite => (from, into),
339                Access::ReadOnly => (from, quote!()),
340                Access::WriteOnly => (from, into),
341                Access::None => (quote!(), quote!()),
342            };
343
344            // auto-conversion from zero
345            if default.is_empty() {
346                if !from.is_empty() {
347                    default = quote!({ let this = 0; #from });
348                } else {
349                    default = quote!(0);
350                }
351            }
352
353            // remove our attribute
354            attrs.retain(|a| !a.path().is_ident("bits"));
355
356            Ok(Self {
357                offset,
358                bits,
359                base_ty,
360                repr_into,
361                repr_from,
362                default,
363                inner: Some(MemberInner {
364                    ident,
365                    ty,
366                    attrs,
367                    vis,
368                    into,
369                    from,
370                }),
371            })
372        } else {
373            if default.is_empty() {
374                default = quote!(0);
375            }
376
377            Ok(Self {
378                offset,
379                bits,
380                base_ty,
381                repr_into,
382                repr_from,
383                default,
384                inner: None,
385            })
386        }
387    }
388
389    fn default(&self) -> TokenStream {
390        let default = &self.default;
391
392        if let Some(inner) = &self.inner {
393            if !inner.into.is_empty() {
394                let ident = &inner.ident;
395                let with_ident = format_ident!("with_{}", ident);
396                return quote!(this = this.#with_ident(#default););
397            }
398        }
399
400        // fallback when there is no setter
401        let offset = self.offset;
402        let base_ty = &self.base_ty;
403        let repr_into = &self.repr_into;
404        let repr_from = &self.repr_from;
405        let bits = self.bits as u32;
406
407        quote! {
408            let mask = #base_ty::MAX >> (#base_ty::BITS - #bits);
409            this.0 = #repr_from(#repr_into(this.0) | (((#default as #base_ty) & mask) << #offset));
410        }
411    }
412}
413
414impl ToTokens for Member {
415    fn to_tokens(&self, tokens: &mut TokenStream) {
416        let Self {
417            offset,
418            bits,
419            base_ty,
420            repr_into,
421            repr_from,
422            default: _,
423            inner:
424                Some(MemberInner {
425                    ident,
426                    ty,
427                    attrs,
428                    vis,
429                    into,
430                    from,
431                }),
432        } = self
433        else {
434            return Default::default();
435        };
436
437        let ident_str = ident.to_string().to_uppercase();
438        let ident_upper = Ident::new(
439            ident_str.strip_prefix("R#").unwrap_or(&ident_str),
440            ident.span(),
441        );
442
443        let with_ident = format_ident!("with_{}", ident);
444        let with_ident_checked = format_ident!("with_{}_checked", ident);
445        let set_ident = format_ident!("set_{}", ident);
446        let set_ident_checked = format_ident!("set_{}_checked", ident);
447        let bits_ident = format_ident!("{}_BITS", ident_upper);
448        let offset_ident = format_ident!("{}_OFFSET", ident_upper);
449
450        let location = format!("\n\nBits: {offset}..{}", offset + bits);
451
452        let doc: TokenStream = attrs
453            .iter()
454            .filter(|a| !a.path().is_ident("bits"))
455            .map(ToTokens::to_token_stream)
456            .collect();
457
458        tokens.extend(quote! {
459            const #bits_ident: usize = #bits;
460            const #offset_ident: usize = #offset;
461        });
462
463        if !from.is_empty() {
464            tokens.extend(quote! {
465                #doc
466                #[doc = #location]
467                #vis const fn #ident(&self) -> #ty {
468                    let mask = #base_ty::MAX >> (#base_ty::BITS - Self::#bits_ident as u32);
469                    let this = (#repr_into(self.0) >> Self::#offset_ident) & mask;
470                    #from
471                }
472            });
473        }
474
475        if !into.is_empty() {
476            let (class, _) = type_info(ty);
477            // generate static strings for the error messages (due to const)
478            let bounds = if class == TypeClass::SInt {
479                let min = -((u128::MAX >> (128 - (bits - 1))) as i128) - 1;
480                let max = u128::MAX >> (128 - (bits - 1));
481                format!("[{}, {}]", min, max)
482            } else {
483                format!("[0, {}]", u128::MAX >> (128 - bits))
484            };
485            let bounds_error = format!("value out of bounds {bounds}");
486
487            tokens.extend(quote! {
488                #doc
489                #[doc = #location]
490                #vis const fn #with_ident_checked(mut self, value: #ty) -> core::result::Result<Self, ()> {
491                    match self.#set_ident_checked(value) {
492                        Ok(_) => Ok(self),
493                        Err(_) => Err(()),
494                    }
495                }
496                #doc
497                #[doc = #location]
498                #[cfg_attr(debug_assertions, track_caller)]
499                #vis const fn #with_ident(mut self, value: #ty) -> Self {
500                    self.#set_ident(value);
501                    self
502                }
503
504                #doc
505                #[doc = #location]
506                #vis const fn #set_ident(&mut self, value: #ty) {
507                    if let Err(_) = self.#set_ident_checked(value) {
508                        panic!(#bounds_error)
509                    }
510                }
511                #doc
512                #[doc = #location]
513                #vis const fn #set_ident_checked(&mut self, value: #ty) -> core::result::Result<(), ()> {
514                    let this = value;
515                    let value: #base_ty = #into;
516                    let mask = #base_ty::MAX >> (#base_ty::BITS - Self::#bits_ident as u32);
517                    if value > mask {
518                        return Err(());
519                    }
520                    let bits = #repr_into(self.0) & !(mask << Self::#offset_ident) | (value & mask) << Self::#offset_ident;
521                    self.0 = #repr_from(bits);
522                    Ok(())
523                }
524            });
525        }
526    }
527}
528
529/// Distinguish between different types for code generation.
530#[derive(Debug, PartialEq, Eq, Clone, Copy)]
531enum TypeClass {
532    /// Booleans with 1 bit size
533    Bool,
534    /// Unsigned ints with fixes sizes: u8, u64, ...
535    UInt,
536    /// Signed ints with fixes sizes: i8, i64, ...
537    SInt,
538    /// Custom types
539    Other,
540}
541
542/// Field information, including the `bits` attribute
543struct Field {
544    bits: usize,
545    ty: syn::Type,
546
547    default: TokenStream,
548    into: TokenStream,
549    from: TokenStream,
550
551    access: Access,
552}
553
554/// Parses the `bits` attribute that allows specifying a custom number of bits.
555fn parse_field(
556    base_ty: &syn::Type,
557    attrs: &[syn::Attribute],
558    ty: &syn::Type,
559    ignore: bool,
560) -> syn::Result<Field> {
561    fn malformed(mut e: syn::Error, attr: &syn::Attribute) -> syn::Error {
562        e.combine(s_err(attr.span(), "malformed #[bits] attribute"));
563        e
564    }
565
566    let access = if ignore {
567        Access::None
568    } else {
569        Access::ReadWrite
570    };
571
572    // Defaults for the different types
573    let (class, ty_bits) = type_info(ty);
574    let mut ret = match class {
575        TypeClass::Bool => Field {
576            bits: ty_bits,
577            ty: ty.clone(),
578            default: quote!(false),
579            into: quote!(this as _),
580            from: quote!(this != 0),
581            access,
582        },
583        TypeClass::SInt => Field {
584            bits: ty_bits,
585            ty: ty.clone(),
586            default: quote!(0),
587            into: quote!(),
588            from: quote!(),
589            access,
590        },
591        TypeClass::UInt => Field {
592            bits: ty_bits,
593            ty: ty.clone(),
594            default: quote!(0),
595            into: quote!(this as _),
596            from: quote!(this as _),
597            access,
598        },
599        TypeClass::Other => Field {
600            bits: ty_bits,
601            ty: ty.clone(),
602            default: quote!(),
603            into: quote!(<#ty>::into_bits(this) as _),
604            from: quote!(<#ty>::from_bits(this as _)),
605            access,
606        },
607    };
608
609    // Find and parse the bits attribute
610    for attr in attrs {
611        let syn::Attribute {
612            style: syn::AttrStyle::Outer,
613            meta: syn::Meta::List(syn::MetaList { path, tokens, .. }),
614            ..
615        } = attr
616        else {
617            continue;
618        };
619        if !path.is_ident("bits") {
620            continue;
621        }
622
623        let span = tokens.span();
624        let BitsAttr {
625            bits,
626            default,
627            into,
628            from,
629            access,
630        } = syn::parse2(tokens.clone()).map_err(|e| malformed(e, attr))?;
631
632        // bit size
633        if let Some(bits) = bits {
634            if bits == 0 {
635                return Err(s_err(span, "bits cannot bit 0"));
636            }
637            if ty_bits != 0 && bits > ty_bits {
638                return Err(s_err(span, "overflowing field type"));
639            }
640            ret.bits = bits;
641        }
642
643        // read/write access
644        if let Some(access) = access {
645            if ignore {
646                return Err(s_err(
647                    tokens.span(),
648                    "'access' is not supported for padding",
649                ));
650            }
651            ret.access = access;
652        }
653
654        // conversion
655        if let Some(into) = into {
656            if ret.access == Access::None {
657                return Err(s_err(into.span(), "'into' is not supported on padding"));
658            }
659            ret.into = quote!(#into(this) as _);
660        }
661        if let Some(from) = from {
662            if ret.access == Access::None {
663                return Err(s_err(from.span(), "'from' is not supported on padding"));
664            }
665            ret.from = quote!(#from(this as _));
666        }
667        if let Some(default) = default {
668            ret.default = default.into_token_stream();
669        }
670    }
671
672    if ret.bits == 0 {
673        return Err(s_err(
674            ty.span(),
675            "Custom types and isize/usize require an explicit bit size",
676        ));
677    }
678
679    // Signed integers need some special handling...
680    if !ignore && ret.access != Access::None && class == TypeClass::SInt {
681        let bits = ret.bits as u32;
682        if ret.into.is_empty() {
683            // Bounds check and remove leading ones from negative values
684            ret.into = quote! {{
685                let m = #ty::MIN >> (#ty::BITS - #bits);
686                if !(m <= this && this <= -(m + 1)) {
687                    return Err(())
688                }
689                let mask = #base_ty::MAX >> (#base_ty::BITS - #bits);
690                (this as #base_ty & mask)
691            }};
692        }
693        if ret.from.is_empty() {
694            // Sign extend negative values
695            ret.from = quote! {{
696                let shift = #ty::BITS - #bits;
697                ((this as #ty) << shift) >> shift
698            }};
699        }
700    }
701
702    Ok(ret)
703}
704
705/// Returns the number of bits for a given type
706fn type_info(ty: &syn::Type) -> (TypeClass, usize) {
707    let syn::Type::Path(syn::TypePath { path, .. }) = ty else {
708        return (TypeClass::Other, 0);
709    };
710    let Some(ident) = path.get_ident() else {
711        return (TypeClass::Other, 0);
712    };
713    if ident == "bool" {
714        return (TypeClass::Bool, 1);
715    }
716    if ident == "isize" || ident == "usize" {
717        return (TypeClass::UInt, 0); // they have architecture dependend sizes
718    }
719    macro_rules! integer {
720        ($ident:ident => $($uint:ident),* ; $($sint:ident),*) => {
721            match ident {
722                $(_ if ident == stringify!($uint) => (TypeClass::UInt, $uint::BITS as _),)*
723                $(_ if ident == stringify!($sint) => (TypeClass::SInt, $sint::BITS as _),)*
724                _ => (TypeClass::Other, 0)
725            }
726        };
727    }
728    integer!(ident => u8, u16, u32, u64, u128 ; i8, i16, i32, i64, i128)
729}
730
731fn s_err(span: proc_macro2::Span, msg: impl fmt::Display) -> syn::Error {
732    syn::Error::new(span, msg)
733}
734
735#[cfg(test)]
736mod test {
737    #![allow(clippy::unwrap_used)]
738    use quote::quote;
739
740    use crate::{Access, BitsAttr, Enable, Order, Params};
741
742    #[test]
743    fn parse_args() {
744        let args = quote!(u64);
745        let params = syn::parse2::<Params>(args).unwrap();
746        assert_eq!(params.bits, u64::BITS as usize);
747        assert!(matches!(params.debug, Enable::Yes));
748        assert!(matches!(params.defmt, Enable::No));
749
750        let args = quote!(u32, debug = false);
751        let params = syn::parse2::<Params>(args).unwrap();
752        assert_eq!(params.bits, u32::BITS as usize);
753        assert!(matches!(params.debug, Enable::No));
754        assert!(matches!(params.defmt, Enable::No));
755
756        let args = quote!(u32, defmt = true);
757        let params = syn::parse2::<Params>(args).unwrap();
758        assert_eq!(params.bits, u32::BITS as usize);
759        assert!(matches!(params.debug, Enable::Yes));
760        assert!(matches!(params.defmt, Enable::Yes));
761
762        let args = quote!(u32, defmt = cfg(test), debug = cfg(feature = "foo"));
763        let params = syn::parse2::<Params>(args).unwrap();
764        assert_eq!(params.bits, u32::BITS as usize);
765        assert!(matches!(params.debug, Enable::Cfg(_)));
766        assert!(matches!(params.defmt, Enable::Cfg(_)));
767
768        let args = quote!(u32, order = Msb);
769        let params = syn::parse2::<Params>(args).unwrap();
770        assert!(params.bits == u32::BITS as usize && params.order == Order::Msb);
771    }
772
773    #[test]
774    fn parse_bits() {
775        let args = quote!(8);
776        let attr = syn::parse2::<BitsAttr>(args).unwrap();
777        assert_eq!(attr.bits, Some(8));
778        assert!(attr.default.is_none());
779        assert!(attr.into.is_none());
780        assert!(attr.from.is_none());
781        assert!(attr.access.is_none());
782
783        let args = quote!(8, default = 8, access = RW);
784        let attr = syn::parse2::<BitsAttr>(args).unwrap();
785        assert_eq!(attr.bits, Some(8));
786        assert!(attr.default.is_some());
787        assert!(attr.into.is_none());
788        assert!(attr.from.is_none());
789        assert_eq!(attr.access, Some(Access::ReadWrite));
790
791        let args = quote!(access = RO);
792        let attr = syn::parse2::<BitsAttr>(args).unwrap();
793        assert_eq!(attr.bits, None);
794        assert!(attr.default.is_none());
795        assert!(attr.into.is_none());
796        assert!(attr.from.is_none());
797        assert_eq!(attr.access, Some(Access::ReadOnly));
798
799        let args = quote!(default = 8, access = WO);
800        let attr = syn::parse2::<BitsAttr>(args).unwrap();
801        assert_eq!(attr.bits, None);
802        assert!(attr.default.is_some());
803        assert!(attr.into.is_none());
804        assert!(attr.from.is_none());
805        assert_eq!(attr.access, Some(Access::WriteOnly));
806
807        let args = quote!(
808            3,
809            into = into_something,
810            default = 1,
811            from = from_something,
812            access = None
813        );
814        let attr = syn::parse2::<BitsAttr>(args).unwrap();
815        assert_eq!(attr.bits, Some(3));
816        assert!(attr.default.is_some());
817        assert!(attr.into.is_some());
818        assert!(attr.from.is_some());
819        assert_eq!(attr.access, Some(Access::None));
820    }
821
822    #[test]
823    fn parse_access_mode() {
824        let args = quote!(RW);
825        let mode = syn::parse2::<Access>(args).unwrap();
826        assert_eq!(mode, Access::ReadWrite);
827
828        let args = quote!(RO);
829        let mode = syn::parse2::<Access>(args).unwrap();
830        assert_eq!(mode, Access::ReadOnly);
831
832        let args = quote!(WO);
833        let mode = syn::parse2::<Access>(args).unwrap();
834        assert_eq!(mode, Access::WriteOnly);
835
836        let args = quote!(None);
837        let mode = syn::parse2::<Access>(args).unwrap();
838        assert_eq!(mode, Access::None);
839
840        let args = quote!(garbage);
841        let mode = syn::parse2::<Access>(args);
842        assert!(mode.is_err());
843    }
844}