Skip to main content

capos_bitstruct/
lib.rs

1use std::collections::HashMap;
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::{format_ident, quote};
6use syn::parse::{Parse, ParseStream};
7use syn::spanned::Spanned;
8use syn::{
9  braced, parse_macro_input, Attribute, Error, Expr, Generics, Ident, LitInt, Result, Token, Type,
10  Visibility,
11};
12
13#[proc_macro]
14pub fn bitstruct(input: TokenStream) -> TokenStream {
15  let input = parse_macro_input!(input as BitstructInput);
16  match expand_bitstruct(input) {
17    Ok(tokens) => tokens.into(),
18    Err(err) => err.to_compile_error().into(),
19  }
20}
21
22fn expand_bitstruct(input: BitstructInput) -> Result<proc_macro2::TokenStream> {
23  let BitstructInput {
24    attrs,
25    vis,
26    ident,
27    generics,
28    fields,
29  } = input;
30
31  validate_name_collisions(&fields)?;
32
33  let mut storage_fields = Vec::new();
34  let mut methods = Vec::new();
35  let mut field_initializers = Vec::new();
36
37  for field in fields {
38    let StructField {
39      attrs,
40      vis,
41      name: field_name,
42      ty,
43      bitfields,
44    } = field;
45    let field_ident = field_name
46      .as_ident()
47      .expect("field names were validated before code generation");
48
49    storage_fields.push(quote! {
50      #(#attrs)*
51      #field_ident: #ty,
52    });
53
54    let field_getter_name = field_ident.clone();
55    let field_setter_name = format_ident!("set_{}", field_ident);
56    let field_stream_setter_name = format_ident!("with_{}", field_ident);
57    methods.push(quote! {
58      #vis fn #field_getter_name(&self) -> &#ty {
59        &self.#field_ident
60      }
61
62      #vis fn #field_setter_name(&mut self, value: #ty) {
63        self.#field_ident = value;
64      }
65
66      #vis fn #field_stream_setter_name(&mut self, value: #ty) -> &mut Self {
67        self.#field_setter_name(value);
68        self
69      }
70    });
71
72    let Some(bitfield_blocks) = bitfields else {
73      continue;
74    };
75
76    let total_bits = integer_bit_width(&ty).ok_or_else(|| {
77      Error::new(
78        ty.span(),
79        "bitfield containers must be primitive integer types (u8..u128, i8..i128, usize, isize)",
80      )
81    })?;
82    let work_ty = integer_work_ty(&ty).ok_or_else(|| {
83      Error::new(
84        ty.span(),
85        "bitfield containers must be primitive integer types (u8..u128, i8..i128, usize, isize)",
86      )
87    })?;
88    let mut field_init_stmts = Vec::new();
89    field_init_stmts.push(quote! {
90      self.#field_ident = 0 as #ty;
91    });
92
93    for (block_index, block) in bitfield_blocks.into_iter().enumerate() {
94      let mut block_offset: u16 = 0;
95      for bitfield in block.bitfields {
96        let BitField {
97          attrs,
98          name: bit_name,
99          ty: value_ty,
100          width,
101          default,
102        } = bitfield;
103        let bit_name_str = bit_name.as_string();
104
105        if width == 0 {
106          return Err(Error::new(
107            bit_name.span(),
108            format!("bitfield '{}' must have a non-zero width", bit_name_str),
109          ));
110        }
111
112        let start = block_offset;
113        let end = start + width as u16 - 1;
114        if end >= total_bits as u16 {
115          return Err(Error::new(
116            bit_name.span(),
117            format!(
118              "bitfield '{}' with width {} at computed offset {} exceeds container width {}",
119              bit_name_str, width, start, total_bits
120            ),
121          ));
122        }
123        block_offset += width as u16;
124
125        let is_undefined = bit_name_str == "UNDEFINED";
126        let is_underscore = bit_name_str == "_";
127
128        if is_bool(&value_ty) && width != 1 {
129          return Err(Error::new(
130            value_ty.span(),
131            format!("bool bitfield '{}' must be exactly one bit", bit_name_str),
132          ));
133        }
134
135        let start_lit = syn::LitInt::new(&start.to_string(), Span::call_site());
136        let width_lit = syn::LitInt::new(&width.to_string(), Span::call_site());
137
138        let value_mask_expr = if width == 128 {
139          quote! { !0 as #work_ty }
140        } else {
141          quote! { ((1 as #work_ty) << #width_lit) - (1 as #work_ty) }
142        };
143
144        if block_index == 0 {
145          if let Some(default_expr) = default {
146            if is_undefined || is_underscore {
147              let default_bits_expr = if is_bool(&value_ty) {
148                quote! { if (#default_expr) { 1 as #work_ty } else { 0 as #work_ty } }
149              } else {
150                quote! { (<#work_ty as ::core::convert::From<#value_ty>>::from(#default_expr)) & #value_mask_expr }
151              };
152              field_init_stmts.push(quote! {
153                {
154                  let value_mask = #value_mask_expr;
155                  let field_mask = value_mask << #start_lit;
156                  let value_bits = #default_bits_expr;
157                  let current = self.#field_ident as #work_ty;
158                  let updated = (current & !field_mask) | ((value_bits << #start_lit) & field_mask);
159                  self.#field_ident = updated as #ty;
160                }
161              });
162            } else {
163              let bit_ident = bit_name
164                .as_ident()
165                .expect("non-special bitfields must parse as identifiers");
166              let setter_name_for_init = format_ident!("set_{}", bit_ident);
167              field_init_stmts.push(quote! {
168                self.#setter_name_for_init(#default_expr);
169              });
170            }
171          }
172        }
173
174        if is_undefined || is_underscore {
175          continue;
176        }
177
178        let bit_ident = bit_name
179          .as_ident()
180          .expect("non-underscore bitfields must parse as identifiers");
181        let getter_name = bit_ident.clone();
182        let setter_name = format_ident!("set_{}", bit_ident);
183        let stream_setter_name = format_ident!("with_{}", bit_ident);
184
185        let getter_body = if is_bool(&value_ty) {
186          quote! {
187            let raw = (self.#field_ident as #work_ty >> #start_lit) & #value_mask_expr;
188            raw != 0
189          }
190        } else {
191          quote! {
192            let raw = (self.#field_ident as #work_ty >> #start_lit) & #value_mask_expr;
193            <#value_ty as ::core::convert::TryFrom<#work_ty>>::try_from(raw)
194              .ok()
195              .expect("bitfield getter conversion failed")
196          }
197        };
198
199        let value_bits_expr = if is_bool(&value_ty) {
200          quote! { if value { 1 as #work_ty } else { 0 as #work_ty } }
201        } else {
202          quote! { (<#work_ty as ::core::convert::From<#value_ty>>::from(value)) & #value_mask_expr }
203        };
204
205        methods.push(quote! {
206          #(#attrs)*
207          #vis fn #getter_name(&self) -> #value_ty {
208            #getter_body
209          }
210
211          #(#attrs)*
212          #vis fn #setter_name(&mut self, value: #value_ty) {
213            let value_mask = #value_mask_expr;
214            let field_mask = value_mask << #start_lit;
215            let value_bits = #value_bits_expr;
216            let current = self.#field_ident as #work_ty;
217            let updated = (current & !field_mask) | ((value_bits << #start_lit) & field_mask);
218            self.#field_ident = updated as #ty;
219          }
220
221          #(#attrs)*
222          #vis fn #stream_setter_name(&mut self, value: #value_ty) -> &mut Self {
223            self.#setter_name(value);
224            self
225          }
226        });
227      }
228    }
229
230    field_initializers.push(quote! {
231      #(#field_init_stmts)*
232    });
233  }
234
235  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
236
237  Ok(quote! {
238    #(#attrs)*
239    #vis struct #ident #generics {
240      #(#storage_fields)*
241    }
242
243    impl #impl_generics #ident #ty_generics #where_clause {
244      pub fn init_bitstruct(&mut self) {
245        #(#field_initializers)*
246      }
247
248      #(#methods)*
249    }
250  })
251}
252
253fn validate_name_collisions(fields: &[StructField]) -> Result<()> {
254  let mut field_names: HashMap<String, Span> = HashMap::new();
255  for field in fields {
256    let name = field.name.as_string();
257    if is_disallowed_struct_field_name(&name) {
258      return Err(Error::new(
259        field.name.span(),
260        format!("struct field name '{}' is reserved and cannot be used", name),
261      ));
262    }
263    if field_names.insert(name.clone(), field.name.span()).is_some() {
264      return Err(Error::new(
265        field.name.span(),
266        format!("duplicate struct field name '{}'", name),
267      ));
268    }
269  }
270
271  let mut bitfield_names: HashMap<String, Span> = HashMap::new();
272  for field in fields {
273    let Some(bitfield_blocks) = &field.bitfields else {
274      continue;
275    };
276
277    for block in bitfield_blocks {
278      for bitfield in &block.bitfields {
279        let bit_name = bitfield.name.as_string();
280        if is_exempt_bitfield_collision_name(&bit_name) {
281          continue;
282        }
283        if !is_snake_case_name(&bit_name) {
284          return Err(Error::new(
285            bitfield.name.span(),
286            format!("bitfield name '{}' must be snake_case", bit_name),
287          ));
288        }
289
290        if field_names.contains_key(&bit_name) {
291          return Err(Error::new(
292            bitfield.name.span(),
293            format!(
294              "bitfield name '{}' collides with a struct field name",
295              bit_name
296            ),
297          ));
298        }
299
300        if bitfield_names
301          .insert(bit_name.clone(), bitfield.name.span())
302          .is_some()
303        {
304          return Err(Error::new(
305            bitfield.name.span(),
306            format!("duplicate bitfield name '{}'", bit_name),
307          ));
308        }
309      }
310    }
311  }
312
313  Ok(())
314}
315
316fn is_disallowed_struct_field_name(name: &str) -> bool {
317  matches!(name, "_" | "RESEREVED" | "UNDEFINED")
318}
319
320fn is_exempt_bitfield_collision_name(name: &str) -> bool {
321  matches!(name, "_" | "RESEREVED" | "UNDEFINED")
322}
323
324fn is_snake_case_name(name: &str) -> bool {
325  !name.is_empty()
326    && name
327      .bytes()
328      .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
329}
330
331fn integer_bit_width(ty: &Type) -> Option<u8> {
332  let Type::Path(path) = ty else {
333    return None;
334  };
335  if path.qself.is_some() {
336    return None;
337  }
338
339  let seg = path.path.segments.last()?;
340  if !seg.arguments.is_empty() {
341    return None;
342  }
343
344  match seg.ident.to_string().as_str() {
345    "u8" | "i8" => Some(8),
346    "u16" | "i16" => Some(16),
347    "u32" | "i32" => Some(32),
348    "u64" | "i64" => Some(64),
349    "u128" | "i128" => Some(128),
350    "usize" | "isize" => Some((usize::BITS) as u8),
351    _ => None,
352  }
353}
354
355fn integer_work_ty(ty: &Type) -> Option<proc_macro2::TokenStream> {
356  let Type::Path(path) = ty else {
357    return None;
358  };
359  if path.qself.is_some() {
360    return None;
361  }
362
363  let seg = path.path.segments.last()?;
364  if !seg.arguments.is_empty() {
365    return None;
366  }
367
368  match seg.ident.to_string().as_str() {
369    "u8" | "i8" => Some(quote! { u8 }),
370    "u16" | "i16" => Some(quote! { u16 }),
371    "u32" | "i32" => Some(quote! { u32 }),
372    "u64" | "i64" => Some(quote! { u64 }),
373    "u128" | "i128" => Some(quote! { u128 }),
374    "usize" | "isize" => Some(quote! { usize }),
375    _ => None,
376  }
377}
378
379fn is_bool(ty: &Type) -> bool {
380  let Type::Path(path) = ty else {
381    return false;
382  };
383  path.qself.is_none() && path.path.is_ident("bool")
384}
385
386struct BitstructInput {
387  attrs: Vec<Attribute>,
388  vis: Visibility,
389  ident: Ident,
390  generics: Generics,
391  fields: Vec<StructField>,
392}
393
394impl Parse for BitstructInput {
395  fn parse(input: ParseStream<'_>) -> Result<Self> {
396    let attrs = input.call(Attribute::parse_outer)?;
397    let vis = input.parse::<Visibility>()?;
398    input.parse::<Token![struct]>()?;
399    let ident = input.parse::<Ident>()?;
400    let generics = input.parse::<Generics>()?;
401
402    let content;
403    braced!(content in input);
404
405    let mut fields = Vec::new();
406    while !content.is_empty() {
407      fields.push(content.parse::<StructField>()?);
408    }
409
410    Ok(Self {
411      attrs,
412      vis,
413      ident,
414      generics,
415      fields,
416    })
417  }
418}
419
420struct StructField {
421  attrs: Vec<Attribute>,
422  vis: Visibility,
423  name: StructFieldName,
424  ty: Type,
425  bitfields: Option<Vec<BitFieldBlock>>,
426}
427
428enum StructFieldName {
429  Ident(Ident),
430  Underscore(Token![_]),
431}
432
433impl StructFieldName {
434  fn as_ident(&self) -> Option<&Ident> {
435    match self {
436      Self::Ident(ident) => Some(ident),
437      Self::Underscore(_) => None,
438    }
439  }
440
441  fn as_string(&self) -> String {
442    match self {
443      Self::Ident(ident) => ident.to_string(),
444      Self::Underscore(_) => "_".to_string(),
445    }
446  }
447
448  fn span(&self) -> Span {
449    match self {
450      Self::Ident(ident) => ident.span(),
451      Self::Underscore(token) => token.span(),
452    }
453  }
454}
455
456impl Parse for StructField {
457  fn parse(input: ParseStream<'_>) -> Result<Self> {
458    let attrs = input.call(Attribute::parse_outer)?;
459    let vis = input.parse::<Visibility>()?;
460    let name = if input.peek(Token![_]) {
461      StructFieldName::Underscore(input.parse::<Token![_]>()?)
462    } else {
463      StructFieldName::Ident(input.parse::<Ident>()?)
464    };
465    input.parse::<Token![:]>()?;
466    let ty = input.parse::<Type>()?;
467    if input.peek(Token![=]) {
468      return Err(Error::new(
469        input.span(),
470        "struct field initializers are not supported; use bitfield-level initializers",
471      ));
472    }
473
474    let mut bitfield_blocks = Vec::new();
475    let mut has_bitfields = false;
476    let mut consumed_field_terminator = false;
477
478    while input.peek(syn::token::Brace) {
479      has_bitfields = true;
480      let content;
481      braced!(content in input);
482
483      let mut bitfields = Vec::new();
484      while !content.is_empty() {
485        bitfields.push(content.parse::<BitField>()?);
486      }
487      bitfield_blocks.push(BitFieldBlock { bitfields });
488
489      if input.peek(Token![,]) {
490        let ahead = input.fork();
491        ahead.parse::<Token![,]>()?;
492        if ahead.peek(syn::token::Brace) {
493          input.parse::<Token![,]>()?;
494          continue;
495        } else {
496          input.parse::<Token![,]>()?;
497          consumed_field_terminator = true;
498          break;
499        }
500      }
501    }
502
503    if !consumed_field_terminator {
504      input.parse::<Token![,]>()?;
505    }
506
507    let bitfields = if has_bitfields {
508      Some(bitfield_blocks)
509    } else {
510      None
511    };
512
513    Ok(Self {
514      attrs,
515      vis,
516      name,
517      ty,
518      bitfields,
519    })
520  }
521}
522
523struct BitFieldBlock {
524  bitfields: Vec<BitField>,
525}
526
527struct BitField {
528  attrs: Vec<Attribute>,
529  name: BitFieldName,
530  ty: Type,
531  width: u8,
532  default: Option<Expr>,
533}
534
535enum BitFieldName {
536  Ident(Ident),
537  Underscore(Token![_]),
538}
539
540impl BitFieldName {
541  fn as_ident(&self) -> Option<&Ident> {
542    match self {
543      Self::Ident(ident) => Some(ident),
544      Self::Underscore(_) => None,
545    }
546  }
547
548  fn as_string(&self) -> String {
549    match self {
550      Self::Ident(ident) => ident.to_string(),
551      Self::Underscore(_) => "_".to_string(),
552    }
553  }
554
555  fn span(&self) -> Span {
556    match self {
557      Self::Ident(ident) => ident.span(),
558      Self::Underscore(token) => token.span(),
559    }
560  }
561}
562
563impl Parse for BitField {
564  fn parse(input: ParseStream<'_>) -> Result<Self> {
565    let attrs = input.call(Attribute::parse_outer)?;
566    let name = if input.peek(Token![_]) {
567      BitFieldName::Underscore(input.parse::<Token![_]>()?)
568    } else {
569      BitFieldName::Ident(input.parse::<Ident>()?)
570    };
571    input.parse::<Token![:]>()?;
572    let ty = input.parse::<Type>()?;
573
574    let width_lit = input.parse::<LitInt>()?;
575    let width = parse_width_literal(&width_lit)?;
576
577    let default = if input.peek(Token![=]) {
578      input.parse::<Token![=]>()?;
579      Some(input.parse::<Expr>()?)
580    } else {
581      None
582    };
583
584    input.parse::<Token![,]>()?;
585
586    Ok(Self {
587      attrs,
588      name,
589      ty,
590      width,
591      default,
592    })
593  }
594}
595
596fn parse_width_literal(lit: &LitInt) -> Result<u8> {
597  let raw = lit.to_string();
598  let digits: String = raw.chars().take_while(|c| c.is_ascii_hexdigit() || *c == '_' || *c == 'x' || *c == 'o' || *c == 'b').collect();
599
600  let cleaned: String = digits.chars().filter(|c| *c != '_').collect();
601  let (radix, body) = if let Some(rest) = cleaned.strip_prefix("0x") {
602    (16, rest)
603  } else if let Some(rest) = cleaned.strip_prefix("0o") {
604    (8, rest)
605  } else if let Some(rest) = cleaned.strip_prefix("0b") {
606    (2, rest)
607  } else {
608    (10, cleaned.as_str())
609  };
610
611  if body.is_empty() {
612    return Err(Error::new(lit.span(), "bitfield width must be an integer literal"));
613  }
614
615  let value = u16::from_str_radix(body, radix)
616    .map_err(|_| Error::new(lit.span(), "bitfield width is out of supported range"))?;
617
618  if value > u8::MAX as u16 {
619    return Err(Error::new(
620      lit.span(),
621      "bitfield width is too large; maximum supported value is 255",
622    ));
623  }
624
625  Ok(value as u8)
626}
627
628#[cfg(test)]
629mod tests {
630  use super::*;
631  use quote::quote;
632  use syn::parse2;
633
634  #[test]
635  fn expands_readme_example() {
636    let input = quote! {
637      struct MultWordA {
638        cw: u32 {
639          swizzled: bool 1,
640          typ: uint8 5,
641          restrictions: uint8 5,
642          _: uint8 2 = 0,
643        },
644        ordinary_field: AnyTypeYouChoose,
645      }
646    };
647
648    let parsed = parse2::<BitstructInput>(input).expect("README example should parse");
649    let expanded = expand_bitstruct(parsed).expect("README example should expand");
650    let rendered = expanded.to_string();
651
652    // Original struct fields are preserved.
653    assert!(rendered.contains("struct MultWordA"));
654    assert!(rendered.contains("cw : u32"));
655    assert!(rendered.contains("ordinary_field : AnyTypeYouChoose"));
656    assert!(!rendered.contains("pub cw : u32"));
657    assert!(!rendered.contains("pub ordinary_field : AnyTypeYouChoose"));
658
659    // Defaults method is generated and includes writes for defaulted bitfields.
660    assert!(rendered.contains("fn init_bitstruct (& mut self)"));
661    assert!(rendered.contains("self . cw = 0 as u32"));
662
663    // Non-special bitfields get getters/setters.
664    assert!(rendered.contains("fn swizzled (& self) -> bool"));
665    assert!(rendered.contains("fn set_swizzled (& mut self , value : bool)"));
666    assert!(rendered.contains("fn with_swizzled (& mut self , value : bool) -> & mut Self"));
667    assert!(rendered.contains("fn typ (& self) -> uint8"));
668    assert!(rendered.contains("fn set_typ (& mut self , value : uint8)"));
669    assert!(rendered.contains("fn with_typ (& mut self , value : uint8) -> & mut Self"));
670
671    // Struct fields also get getters/setters/stream setters.
672    assert!(rendered.contains("fn cw (& self) -> & u32"));
673    assert!(rendered.contains("fn set_cw (& mut self , value : u32)"));
674    assert!(rendered.contains("fn with_cw (& mut self , value : u32) -> & mut Self"));
675    assert!(rendered.contains("fn ordinary_field (& self) -> & AnyTypeYouChoose"));
676    assert!(rendered.contains("fn set_ordinary_field (& mut self , value : AnyTypeYouChoose)"));
677    assert!(rendered.contains("fn with_ordinary_field (& mut self , value : AnyTypeYouChoose) -> & mut Self"));
678
679    // UNDEFINED and pad entries do not generate accessors.
680    assert!(!rendered.contains("fn _ "));
681    assert!(!rendered.contains("fn set__"));
682    assert!(!rendered.contains("fn UNDEFINED"));
683    assert!(!rendered.contains("fn set_UNDEFINED"));
684  }
685
686  #[test]
687  fn rejects_bitfield_field_name_collision() {
688    let input = quote! {
689      struct NameCollision {
690        flags: u32 {
691          ordinary_field: bool 1,
692        },
693        ordinary_field: u32,
694      }
695    };
696
697    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
698    let err = expand_bitstruct(parsed).expect_err("name collision should fail expansion");
699    assert!(
700      err
701        .to_string()
702        .contains("bitfield name 'ordinary_field' collides with a struct field name")
703    );
704  }
705
706  #[test]
707  fn rejects_duplicate_bitfield_names() {
708    let input = quote! {
709      struct DuplicateBitfields {
710        left: u32 {
711          mode: uint8 2,
712        },
713        right: u32 {
714          mode: uint8 2,
715        },
716      }
717    };
718
719    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
720    let err = expand_bitstruct(parsed).expect_err("duplicate bitfield names should fail");
721    assert!(err.to_string().contains("duplicate bitfield name 'mode'"));
722  }
723
724  #[test]
725  fn accepts_bool_default_initializers() {
726    let input = quote! {
727      struct BoolDefaults {
728        control_word: u32 {
729          enabled: bool 1 = true,
730          ready: bool 1 = false,
731        },
732      }
733    };
734
735    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
736    let expanded = expand_bitstruct(parsed).expect("bool defaults should expand");
737    let rendered = expanded.to_string();
738
739    assert!(rendered.contains("fn init_bitstruct (& mut self)"));
740    assert!(rendered.contains("self . control_word = 0 as u32"));
741    assert!(rendered.contains("self . set_enabled (true)"));
742    assert!(rendered.contains("self . set_ready (false)"));
743    let zero_pos = rendered
744      .find("self . control_word = 0 as u32")
745      .expect("zero initialization should be generated");
746    let enabled_pos = rendered
747      .find("self . set_enabled (true)")
748      .expect("enabled initializer should be generated");
749    let ready_pos = rendered
750      .find("self . set_ready (false)")
751      .expect("ready initializer should be generated");
752    assert!(zero_pos < enabled_pos);
753    assert!(enabled_pos < ready_pos);
754  }
755
756  #[test]
757  fn generates_zero_init_without_first_block_bitfield_defaults() {
758    let input = quote! {
759      struct NoFirstBlockDefaults {
760        control_word: u32 {
761          enabled: bool 1,
762          ready: bool 1,
763        }, {
764          alternate: u8 2 = 0x1,
765        },
766      }
767    };
768
769    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
770    let expanded = expand_bitstruct(parsed).expect("expansion should succeed");
771    let rendered = expanded.to_string();
772
773    assert!(rendered.contains("fn init_bitstruct (& mut self)"));
774    assert!(rendered.contains("self . control_word = 0 as u32"));
775    assert!(!rendered.contains("self . set_enabled (true)"));
776    assert!(!rendered.contains("self . set_ready (true)"));
777    assert!(!rendered.contains("self . set_ready (false)"));
778    assert!(!rendered.contains("self . set_alternate (0x1)"));
779  }
780
781  #[test]
782  fn propagates_doc_comments() {
783    let input = quote! {
784      /// Struct docs
785      struct DocExample {
786        /// Field docs
787        pub flags: u32 {
788          /// Enabled bit docs
789          enabled: bool 1,
790        },
791      }
792    };
793
794    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
795    let expanded = expand_bitstruct(parsed).expect("doc comments should expand");
796    let rendered = expanded.to_string();
797
798    // Struct and container field docs are preserved on the emitted struct.
799    assert!(rendered.contains("doc = r\" Struct docs\""));
800    assert!(rendered.contains("struct DocExample"));
801    assert!(rendered.contains("doc = r\" Field docs\""));
802    assert!(rendered.contains("flags : u32"));
803    assert!(!rendered.contains("pub flags : u32"));
804
805    // Bitfield docs are emitted on all generated methods.
806    assert!(rendered.contains("doc = r\" Enabled bit docs\""));
807    assert!(rendered.contains("pub fn enabled (& self) -> bool"));
808    assert!(rendered.contains("pub fn set_enabled (& mut self , value : bool)"));
809    assert!(rendered.contains("pub fn with_enabled (& mut self , value : bool) -> & mut Self"));
810    assert!(rendered.contains("pub fn flags (& self) -> & u32"));
811    assert!(rendered.contains("pub fn set_flags (& mut self , value : u32)"));
812    assert!(rendered.contains("pub fn with_flags (& mut self , value : u32) -> & mut Self"));
813  }
814
815  #[test]
816  fn accepts_underscore_bitfield_without_methods() {
817    let input = quote! {
818      struct UnderscoreBitfield {
819        flags: u32 {
820          _: bool 1 = true,
821          visible: bool 1,
822        },
823      }
824    };
825
826    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
827    let expanded = expand_bitstruct(parsed).expect("underscore bitfield should expand");
828    let rendered = expanded.to_string();
829
830    // underscore entry contributes to field initialization
831    assert!(rendered.contains("self . flags = 0 as u32"));
832    assert!(rendered.contains("if (true) { 1 as u32 } else { 0 as u32 }"));
833
834    // underscore entry does not get accessor methods
835    assert!(!rendered.contains("fn _ "));
836    assert!(!rendered.contains("fn set__"));
837    assert!(!rendered.contains("fn with__"));
838
839    // normal bitfields still get methods
840    assert!(rendered.contains("fn visible (& self) -> bool"));
841    assert!(rendered.contains("fn set_visible (& mut self , value : bool)"));
842    assert!(rendered.contains("fn with_visible (& mut self , value : bool) -> & mut Self"));
843  }
844
845  #[test]
846  fn rejects_reserved_struct_field_names() {
847    for disallowed in ["_", "RESEREVED", "UNDEFINED"] {
848      let source = format!(
849        "struct BadFieldName {{
850          {}: u32,
851        }}",
852        disallowed
853      );
854      let parsed = syn::parse_str::<BitstructInput>(&source).expect("input should parse");
855      let err = expand_bitstruct(parsed).expect_err("reserved struct field name should fail");
856      assert!(
857        err
858          .to_string()
859          .contains(&format!("struct field name '{}' is reserved and cannot be used", disallowed))
860      );
861    }
862  }
863
864  #[test]
865  fn exempts_undefined_and_pad_names_from_collision_checks() {
866    let input = quote! {
867      struct SpecialBitfieldNames {
868        value: u32 {
869          _: bool 1,
870          UNDEFINED: bool 1,
871          RESEREVED: bool 1,
872        },
873      }
874    };
875
876    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
877    let _ = expand_bitstruct(parsed).expect("UNDEFINED/pad names should not trigger collisions");
878  }
879
880  #[test]
881  fn rejects_reserved_name_as_bitfield() {
882    let input = quote! {
883      struct ReservedNameRejected {
884        value: u32 {
885          RESERVED: u32 1 = 1,
886        },
887      }
888    };
889
890    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
891    let err = expand_bitstruct(parsed).expect_err("RESERVED should be rejected");
892    assert!(
893      err
894        .to_string()
895        .contains("bitfield name 'RESERVED' must be snake_case")
896    );
897  }
898
899  #[test]
900  fn rejects_struct_field_initializer_with_bitfields() {
901    let err = syn::parse_str::<BitstructInput>(
902      "struct FieldInitRejected {
903        flags: u32 = 0 {
904          enabled: bool 1 = true,
905        },
906      }",
907    )
908    .err()
909    .expect("struct field initializer should be rejected");
910    assert!(err.to_string().contains(
911      "struct field initializers are not supported; use bitfield-level initializers"
912    ));
913  }
914
915  #[test]
916  fn accepts_multiple_bitfield_blocks_per_field() {
917    let input = quote! {
918      struct MultiBlock {
919        flags: u32 {
920          low: uint8 4 = 0x5,
921        }, {
922          high: uint8 4 = 0xA,
923        },
924        other: u32,
925      }
926    };
927
928    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
929    let expanded = expand_bitstruct(parsed).expect("multiple bitfield blocks should expand");
930    let rendered = expanded.to_string();
931
932    assert!(rendered.contains("fn low (& self) -> uint8"));
933    assert!(rendered.contains("fn high (& self) -> uint8"));
934    assert!(rendered.contains("fn init_bitstruct (& mut self)"));
935    assert!(rendered.contains("self . flags = 0 as u32"));
936    assert!(rendered.contains("self . set_low (0x5)"));
937    assert!(!rendered.contains("self . set_high (0xA)"));
938  }
939
940  #[test]
941  fn rejects_non_snake_case_bitfield_name() {
942    let input = quote! {
943      struct BadBitfieldName {
944        flags: u32 {
945          notSnakeCase: u32 1,
946        },
947      }
948    };
949
950    let parsed = parse2::<BitstructInput>(input).expect("input should parse");
951    let err = expand_bitstruct(parsed).expect_err("non-snake-case bitfield name should fail");
952    assert!(
953      err
954        .to_string()
955        .contains("bitfield name 'notSnakeCase' must be snake_case")
956    );
957  }
958
959}