From 363b5cb652fdb09fce8f74669a8004fadda8a260 Mon Sep 17 00:00:00 2001 From: WillaWillNot <242288178+WillaWillNot@users.noreply.github.com> Date: Fri, 22 May 2026 15:41:03 -0400 Subject: [PATCH] Ensure all uses of core library macros which may be shadowed by defmt inside proc macros are done with the fully qualified core library names (affects assert!, panic!, quote!, todo!, unreachable!, and write!). Add test that compilation with both bilge macro usage and defmt #[macro_use] succeeds. Fix expected stderr strings in failing examples. --- Cargo.toml | 10 ++- bilge-impl/src/bitsize.rs | 2 +- bilge-impl/src/bitsize_internal/struct_gen.rs | 2 +- bilge-impl/src/debug_bits.rs | 2 +- bilge-impl/src/fmt_bits.rs | 6 +- bilge-impl/src/serde_bits.rs | 4 +- bilge-impl/src/shared.rs | 2 +- examples/zz_experimental_problems.rs | 3 +- tests/defmt_compatibility.rs | 85 +++++++++++++++++++ tests/ui/default-should-be-used.stderr | 9 +- tests/ui/fallback/more.stderr | 2 +- tests/ui/vis-privacy-is-respected.stderr | 8 +- 12 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 tests/defmt_compatibility.rs diff --git a/Cargo.toml b/Cargo.toml index 7dd1cef..56d96ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,16 @@ members = ["bilge-impl"] [workspace.package] version = "0.3.0" -categories = ["data-structures", "no-std::no-alloc", "embedded", "rust-patterns"] +categories = [ + "data-structures", + "no-std::no-alloc", + "embedded", + "rust-patterns", +] description = "Use bitsized types as if they were a feature of rust." documentation = "https://docs.rs/bilge" edition = "2021" -keywords = ["bilge", "bitfield", "bits", "register"] +keywords = ["bilge", "bitfield", "bits", "register"] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hecatia-elegua/bilge" @@ -55,6 +60,7 @@ custom_bits = { path = "tests/custom_bits" } assert_matches = "1.5.0" serde = "1.0" serde_test = "1.0" +defmt = "1.0.1" # examples # volatile = { git = "https://github.com/theseus-os/volatile" } diff --git a/bilge-impl/src/bitsize.rs b/bilge-impl/src/bitsize.rs index 68704ea..2766fb6 100644 --- a/bilge-impl/src/bitsize.rs +++ b/bilge-impl/src/bitsize.rs @@ -147,7 +147,7 @@ fn generate_struct(item: &ItemStruct, declared_bitsize: u8) -> TokenStream { #vis struct #ident #fields_def // constness: when we get const blocks evaluated at compile time, add a const computed_bitsize - const _: () = assert!( + const _: () = ::core::assert!( (#computed_bitsize) == (#declared_bitsize), concat!("struct size and declared bit size differ: ", // stringify!(#computed_bitsize), diff --git a/bilge-impl/src/bitsize_internal/struct_gen.rs b/bilge-impl/src/bitsize_internal/struct_gen.rs index 0808edf..e7ee107 100644 --- a/bilge-impl/src/bitsize_internal/struct_gen.rs +++ b/bilge-impl/src/bitsize_internal/struct_gen.rs @@ -163,7 +163,7 @@ pub(crate) fn generate_getter_inner(ty: &Type, is_getter: bool) -> TokenStream { #elem_value match <#ty>::try_from(elem_value) { Ok(v) => v, - Err(_) => panic!("unreachable"), + Err(_) => ::core::panic!("unreachable"), } } } else { diff --git a/bilge-impl/src/debug_bits.rs b/bilge-impl/src/debug_bits.rs index e425656..65f0e3e 100644 --- a/bilge-impl/src/debug_bits.rs +++ b/bilge-impl/src/debug_bits.rs @@ -40,7 +40,7 @@ pub(super) fn debug_bits(item: TokenStream) -> TokenStream { #(#calls)*.finish() } } - Fields::Unit => todo!("this is a unit struct, which is not supported right now"), + Fields::Unit => ::core::todo!("this is a unit struct, which is not supported right now"), }; quote! { diff --git a/bilge-impl/src/fmt_bits.rs b/bilge-impl/src/fmt_bits.rs index 6d71da7..e2c1632 100644 --- a/bilge-impl/src/fmt_bits.rs +++ b/bilge-impl/src/fmt_bits.rs @@ -16,7 +16,7 @@ pub(crate) fn binary(item: TokenStream) -> TokenStream { } fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields) -> TokenStream { - let write_underscore = quote! { write!(f, "_")?; }; + let write_underscore = quote! { ::core::write!(f, "_")?; }; // fields are printed from most significant to least significant, separated by an underscore let writes = fields @@ -32,7 +32,7 @@ fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields) -> TokenStr let first_bit_pos = last_bit_pos - field_size; last_bit_pos -= field_size; let extracted = field_mask & (self.value >> first_bit_pos); - write!(f, "{:0width$b}", extracted, width = field_size)?; + ::core::write!(f, "{:0width$b}", extracted, width = field_size)?; } }) .reduce(|acc, next| quote!(#acc #write_underscore #next)); @@ -62,7 +62,7 @@ fn generate_enum_binary_impl( let value = match self { #( #to_int_match_arms )* }; - write!(f, "{:0width$b}", value, width = <#enum_name as Bitsized>::BITS) + ::core::write!(f, "{:0width$b}", value, width = <#enum_name as Bitsized>::BITS) } }; diff --git a/bilge-impl/src/serde_bits.rs b/bilge-impl/src/serde_bits.rs index 66fc3f2..a5bb917 100644 --- a/bilge-impl/src/serde_bits.rs +++ b/bilge-impl/src/serde_bits.rs @@ -52,7 +52,7 @@ pub(super) fn serialize_bits(item: TokenStream) -> TokenStream { state.end() } } - Fields::Unit => todo!("this is a unit struct, which is not supported right now"), + Fields::Unit => ::core::todo!("this is a unit struct, which is not supported right now"), }; quote! { @@ -133,7 +133,7 @@ pub(super) fn deserialize_bits(item: TokenStream) -> TokenStream { .enumerate() .map(|(i, _)| deserialize_field_parts(i, &syn::parse_str(&format!("val_{}", i)).unwrap_or_else(unreachable))) .multiunzip(), - Fields::Unit => todo!("this is a unit struct, which is not supported right now"), + Fields::Unit => ::core::todo!("this is a unit struct, which is not supported right now"), }; if field_expecting.len() > 1 { diff --git a/bilge-impl/src/shared.rs b/bilge-impl/src/shared.rs index 40ed376..9521d79 100644 --- a/bilge-impl/src/shared.rs +++ b/bilge-impl/src/shared.rs @@ -142,7 +142,7 @@ pub fn enum_fills_bitsize(bitsize: u8, variants_count: usize) -> bool { #[inline] pub fn unreachable(_: T) -> U { - unreachable!("should have already been validated") + ::core::unreachable!("should have already been validated") } pub fn is_attribute(attr: &Attribute, name: &str) -> bool { diff --git a/examples/zz_experimental_problems.rs b/examples/zz_experimental_problems.rs index c0f488b..ceecbd8 100644 --- a/examples/zz_experimental_problems.rs +++ b/examples/zz_experimental_problems.rs @@ -14,6 +14,7 @@ enum Unfilled { C, } +#[allow(unused)] fn main() { // This file mostly shows one flaw to still be solved or at least to be made configurable: // The inner value of a bitfield, which holds invariants, can currently still be changed. @@ -50,7 +51,7 @@ mod somebits { // mean all the private items would not be accessible.. // // If we find any other way to make `value` inaccessible, update this. - #[allow(dead_code)] + #[allow(dead_code, unused)] fn modify_inner() { let a = 0b10101010; let mut b = SomeBits::new(true, true, true); diff --git a/tests/defmt_compatibility.rs b/tests/defmt_compatibility.rs new file mode 100644 index 0000000..2e64f41 --- /dev/null +++ b/tests/defmt_compatibility.rs @@ -0,0 +1,85 @@ +#![cfg(feature = "serde")] +use bilge::prelude::*; + +// defmt is intended to be used with macro_use to fully override all core formatting. +// We're checking that our macros don't use any unqualified macros shadowed by defmt (e.g. write!()) +// in proc macros where we don't intend to interact with the target's stdout. +#[allow(unused)] +#[macro_use] +extern crate defmt; + +#[bitsize(39)] +#[derive(FromBits, DebugBits, PartialEq)] +struct NestedMess { + tu_tuple_ple: (u1, (u2, u8), u1), + // this has special handling, transmuting [[]] to [] internally to generate less + arr_arr_ay_ay: [[InnerTupleStruct; 2]; 2], + bit: u1, + arr_arr_tu_arr_arr_tuple_ay_ay_ple_ay_ay: [[([[(InnerTupleStruct, u2); 2]; 1], u1); 2]; 1], +} + +#[bitsize(2)] +#[derive(Clone, Copy, FromBits, DebugBits, PartialEq)] +struct InnerTupleStruct(u1, bool); + +#[bitsize(18)] +#[derive(TryFromBits, DebugBits, PartialEq)] +struct UnfilledEnumMess { + big_fumble: [[([[(HaveFun, u2); 2]; 1], u1); 2]; 1], +} + +#[bitsize(2)] +#[derive(TryFromBits, Debug, PartialEq, Clone, Copy)] +enum HaveFun { + Yes, + No, + Maybe, +} + +/// Passes if it compiles, i.e. none of the derive macros are accidentally shadowed. +#[test] +fn nested_mess_compiles() { + let tu_tuple_ple = (u1::new(0), (u2::new(0b00), 0b1111_1111), u1::new(1)); + let arr_arr_ay_ay = [ + [InnerTupleStruct::from(u2::new(3)), InnerTupleStruct::from(u2::new(0b10))], + [InnerTupleStruct::from(u2::new(3)), InnerTupleStruct::from(u2::new(0))], + ]; + let bit = u1::new(1); + let arr_arr_tu_arr_arr_tuple_ay_ay_ple_ay_ay = [[ + ( + [[ + (InnerTupleStruct::from(u2::new(3)), u2::new(3)), + (InnerTupleStruct::from(u2::new(3)), u2::new(3)), + ]], + u1::new(0), + ), + ( + [[ + (InnerTupleStruct::from(u2::new(0b10)), u2::new(3)), + (InnerTupleStruct::from(u2::new(3)), u2::new(3)), + ]], + u1::new(0), + ), + ]]; + let _mess = NestedMess::new(tu_tuple_ple, arr_arr_ay_ay, bit, arr_arr_tu_arr_arr_tuple_ay_ay_ple_ay_ay); +} + +#[bitsize(31)] +#[derive(FromBits, PartialEq, SerializeBits, DeserializeBits, DebugBits)] +struct BitsStruct { + padding: u1, + reserved: u1, + field1: u8, + padding: u1, + field2: u5, + reserved: u1, + field3: i8, + padding: u1, + field4: i5, +} + +/// Passes if it compiles. Checks serde-related macros. +#[test] +fn serde_struct_compiles() { + let _serde_struct = BitsStruct::from(u31::new(0b01110_0_10110001_0_01001_0_00100011_0_0)); +} diff --git a/tests/ui/default-should-be-used.stderr b/tests/ui/default-should-be-used.stderr index 6385609..89a7d9c 100644 --- a/tests/ui/default-should-be-used.stderr +++ b/tests/ui/default-should-be-used.stderr @@ -22,4 +22,11 @@ error[E0277]: the trait bound `Inner: Default` is not satisfied --> tests/ui/default-should-be-used.rs:14:8 | 14 | b: Inner, - | ^^^^^ the trait `Default` is not implemented for `Inner` + | ^^^^^ unsatisfied trait bound + | +help: the trait `Default` is not implemented for `Inner` + --> tests/ui/default-should-be-used.rs:17:1 + | +17 | #[bitsize(2)] + | ^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `::bilge::bitsize_internal` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/fallback/more.stderr b/tests/ui/fallback/more.stderr index 1615002..399f52c 100644 --- a/tests/ui/fallback/more.stderr +++ b/tests/ui/fallback/more.stderr @@ -4,7 +4,7 @@ error: `#[fallback]` does not support variants with named fields --> tests/ui/fallback/more.rs:9:5 | -9 | / #[fallback] + 9 | / #[fallback] 10 | | Dee { fallback: u15 }, | |_________________________^ diff --git a/tests/ui/vis-privacy-is-respected.stderr b/tests/ui/vis-privacy-is-respected.stderr index ea52bce..8d2020a 100644 --- a/tests/ui/vis-privacy-is-respected.stderr +++ b/tests/ui/vis-privacy-is-respected.stderr @@ -1,7 +1,7 @@ error[E0624]: method `val_0` is private --> tests/ui/vis-privacy-is-respected.rs:29:11 | -5 | #[bitsize(96)] + 5 | #[bitsize(96)] | -------------- private method defined here ... 29 | diary.val_0(); @@ -10,7 +10,7 @@ error[E0624]: method `val_0` is private error[E0624]: method `set_val_0` is private --> tests/ui/vis-privacy-is-respected.rs:31:11 | -5 | #[bitsize(96)] + 5 | #[bitsize(96)] | -------------- private method defined here ... 31 | diary.set_val_0(rusti); @@ -19,7 +19,7 @@ error[E0624]: method `set_val_0` is private error[E0624]: method `val_1_at` is private --> tests/ui/vis-privacy-is-respected.rs:37:7 | -9 | #[bitsize(8)] + 9 | #[bitsize(8)] | ------------- private method defined here ... 37 | a.val_1_at(1); @@ -28,7 +28,7 @@ error[E0624]: method `val_1_at` is private error[E0624]: method `set_val_1_at` is private --> tests/ui/vis-privacy-is-respected.rs:38:7 | -9 | #[bitsize(8)] + 9 | #[bitsize(8)] | ------------- private method defined here ... 38 | a.set_val_1_at(1, u2::new(0));