Skip to content

Commit 93dcfff

Browse files
authored
Add optional defmt implementation (#42)
* add optional defmt::Format impl generation * add tests and info to README.md about defmt * remove redundant #[cfg(test)] in test file * consolidate debug/defmt implementation code into new function
1 parent cf68bb7 commit 93dcfff

File tree

5 files changed

+341
-27
lines changed

5 files changed

+341
-27
lines changed

Cargo.lock

+100-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ syn = { version = "2.0", features = ["full"] }
2020
proc-macro2 = "1.0"
2121

2222
[dev-dependencies]
23+
defmt = "0.3"
2324
endian-num = { version = "0.1", features = ["linux-types"] }

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ As this library provides a procedural macro, it has no runtime dependencies and
1313
- Rust-analyzer friendly (carries over documentation to accessor functions)
1414
- Exports field offsets and sizes as constants (useful for const asserts)
1515
- Generation of `fmt::Debug` and `Default`
16+
- Optional generation of `defmt::Format`
1617

1718
## Usage
1819

@@ -395,3 +396,16 @@ impl Default for CustomDebug {
395396
let val = CustomDebug::default();
396397
println!("{val:?}")
397398
```
399+
400+
## `defmt::Format`
401+
402+
This macro can automatically implement a `defmt::Format` that mirrors the default `fmt::Debug` implementation by passing the extra `defmt` argument. This implementation requires the defmt crate to be available as `defmt`, and has the same rules and caveats as `#[derive(defmt::Format)]`.
403+
404+
```rust
405+
use bitfield_struct::bitfield;
406+
407+
#[bitfield(u64, defmt = true)]
408+
struct DefmtExample {
409+
data: u64
410+
}
411+
````

src/lib.rs

+126-26
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn s_err(span: proc_macro2::Span, msg: impl fmt::Display) -> syn::Error {
2828
/// - `from` to specify a conversion function from repr to the bitfield's integer type
2929
/// - `into` to specify a conversion function from the bitfield's integer type to repr
3030
/// - `debug` to disable the `Debug` trait generation
31+
/// - `defmt` to enable the `defmt::Format` trait generation.
3132
/// - `default` to disable the `Default` trait generation
3233
/// - `order` to specify the bit order (Lsb, Msb)
3334
/// - `conversion` to disable the generation of into_bits and from_bits
@@ -55,6 +56,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr
5556
from,
5657
bits,
5758
debug,
59+
defmt,
5860
default,
5961
order,
6062
conversion,
@@ -104,20 +106,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr
104106
));
105107
}
106108

107-
let debug_impl = if debug {
108-
let debug_fields = members.iter().map(Member::debug);
109-
quote! {
110-
impl core::fmt::Debug for #name {
111-
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112-
f.debug_struct(stringify!(#name))
113-
#( #debug_fields )*
114-
.finish()
115-
}
116-
}
117-
}
118-
} else {
119-
quote!()
120-
};
109+
let debug_impl = implement_debug(debug, defmt, &name, &members);
121110

122111
let defaults = members.iter().map(Member::default);
123112

@@ -183,6 +172,111 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr
183172
})
184173
}
185174

175+
fn implement_debug(debug: bool, defmt: bool, name: &syn::Ident, members: &[Member]) -> TokenStream {
176+
let debug_impl = if debug {
177+
let fields = members.iter().flat_map(|m| {
178+
let inner = m.inner.as_ref()?;
179+
180+
if inner.from.is_empty() {
181+
return None;
182+
}
183+
184+
let ident = &inner.ident;
185+
Some(quote!(.field(stringify!(#ident), &self.#ident())))
186+
});
187+
188+
quote! {
189+
impl core::fmt::Debug for #name {
190+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191+
f.debug_struct(stringify!(#name))
192+
#( #fields )*
193+
.finish()
194+
}
195+
}
196+
}
197+
} else {
198+
quote!()
199+
};
200+
201+
let defmt_impl = if defmt {
202+
// build a part of the format string for each field
203+
let formats = members.iter().flat_map(|m| {
204+
let inner = m.inner.as_ref()?;
205+
206+
if inner.from.is_empty() {
207+
return None;
208+
}
209+
210+
// default to using {:?}
211+
let mut spec = "{:?}".to_owned();
212+
213+
// primitives supported by defmt
214+
const PRIMITIVES: &[&str] = &[
215+
"bool", "usize", "isize", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32",
216+
"i64", "i128", "f32", "f64",
217+
];
218+
219+
// get the type name so we can use more efficient defmt formats
220+
// if it's a primitive
221+
if let syn::Type::Path(syn::TypePath { path, .. }) = &inner.ty {
222+
if let Some(ident) = path.get_ident() {
223+
if PRIMITIVES.iter().any(|s| ident == s) {
224+
// defmt supports this primitive, use special spec
225+
spec = format!("{{={}}}", ident);
226+
}
227+
}
228+
}
229+
230+
let ident = &inner.ident;
231+
Some(format!("{ident}: {spec}"))
232+
});
233+
234+
// find the corresponding format argument for each field
235+
let args = members.iter().flat_map(|m| {
236+
let inner = m.inner.as_ref()?;
237+
238+
if inner.from.is_empty() {
239+
return None;
240+
}
241+
242+
let ident = &inner.ident;
243+
Some(quote!(self.#ident()))
244+
});
245+
246+
// build a string like "Foo { field_name: {:?}, ... }"
247+
// four braces, two to escape *this* format, times two to escape
248+
// the defmt::write! call below.
249+
let format_string = format!(
250+
"{} {{{{ {} }}}} ",
251+
name,
252+
formats.collect::<Vec<_>>().join(", ")
253+
);
254+
255+
// note: we use defmt paths here, not ::defmt, because many crates
256+
// in the embedded space will rename defmt (e.g. to defmt_03) in
257+
// order to support multiple incompatible defmt versions.
258+
//
259+
// defmt itself avoids ::defmt for this reason. For more info, see:
260+
// https://github.com/knurling-rs/defmt/pull/835
261+
262+
quote! {
263+
impl defmt::Format for #name {
264+
fn format(&self, f: defmt::Formatter) {
265+
defmt::write!(f, #format_string, #( #args, )*)
266+
}
267+
}
268+
}
269+
} else {
270+
quote!()
271+
};
272+
273+
quote!(
274+
#debug_impl
275+
276+
#defmt_impl
277+
)
278+
}
279+
186280
/// Represents a member where accessor functions should be generated for.
187281
struct Member {
188282
offset: usize,
@@ -306,16 +400,6 @@ impl Member {
306400
}
307401
}
308402

309-
fn debug(&self) -> TokenStream {
310-
if let Some(inner) = &self.inner {
311-
if !inner.from.is_empty() {
312-
let ident = &inner.ident;
313-
return quote!(.field(stringify!(#ident), &self.#ident()));
314-
}
315-
}
316-
quote!()
317-
}
318-
319403
fn default(&self) -> TokenStream {
320404
let default = &self.default;
321405

@@ -686,6 +770,7 @@ struct Params {
686770
from: Option<syn::Path>,
687771
bits: usize,
688772
debug: bool,
773+
defmt: bool,
689774
default: bool,
690775
order: Order,
691776
conversion: bool,
@@ -705,6 +790,7 @@ impl Parse for Params {
705790
let mut from = None;
706791
let mut into = None;
707792
let mut debug = true;
793+
let mut defmt = false;
708794
let mut default = true;
709795
let mut order = Order::Lsb;
710796
let mut conversion = true;
@@ -726,6 +812,9 @@ impl Parse for Params {
726812
"debug" => {
727813
debug = syn::LitBool::parse(input)?.value;
728814
}
815+
"defmt" => {
816+
defmt = syn::LitBool::parse(input)?.value;
817+
}
729818
"default" => {
730819
default = syn::LitBool::parse(input)?.value;
731820
}
@@ -757,6 +846,7 @@ impl Parse for Params {
757846
into,
758847
bits,
759848
debug,
849+
defmt,
760850
default,
761851
order,
762852
conversion,
@@ -800,11 +890,21 @@ mod test {
800890
fn parse_args() {
801891
let args = quote!(u64);
802892
let params = syn::parse2::<Params>(args).unwrap();
803-
assert!(params.bits == u64::BITS as usize && params.debug == true);
893+
assert_eq!(params.bits, u64::BITS as usize);
894+
assert_eq!(params.debug, true);
895+
assert_eq!(params.defmt, false);
804896

805897
let args = quote!(u32, debug = false);
806898
let params = syn::parse2::<Params>(args).unwrap();
807-
assert!(params.bits == u32::BITS as usize && params.debug == false);
899+
assert_eq!(params.bits, u32::BITS as usize);
900+
assert_eq!(params.debug, false);
901+
assert_eq!(params.defmt, false);
902+
903+
let args = quote!(u32, defmt = true);
904+
let params = syn::parse2::<Params>(args).unwrap();
905+
assert_eq!(params.bits, u32::BITS as usize);
906+
assert_eq!(params.debug, true);
907+
assert_eq!(params.defmt, true);
808908

809909
let args = quote!(u32, order = Msb);
810910
let params = syn::parse2::<Params>(args).unwrap();

0 commit comments

Comments
 (0)