1#![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#[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#[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 #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 #vis const fn from_bits(bits: #repr) -> Self {
218 Self(bits)
219 }
220 #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
266struct 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 let offset = if order == Order::Lsb {
322 offset
323 } else {
324 base_bits - offset - bits
325 };
326
327 if bits > 0 && !ignore {
328 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 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 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 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 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 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#[derive(Debug, PartialEq, Eq, Clone, Copy)]
531enum TypeClass {
532 Bool,
534 UInt,
536 SInt,
538 Other,
540}
541
542struct Field {
544 bits: usize,
545 ty: syn::Type,
546
547 default: TokenStream,
548 into: TokenStream,
549 from: TokenStream,
550
551 access: Access,
552}
553
554fn 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 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 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 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 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 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 if !ignore && ret.access != Access::None && class == TypeClass::SInt {
681 let bits = ret.bits as u32;
682 if ret.into.is_empty() {
683 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 ret.from = quote! {{
696 let shift = #ty::BITS - #bits;
697 ((this as #ty) << shift) >> shift
698 }};
699 }
700 }
701
702 Ok(ret)
703}
704
705fn 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); }
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}