diff --git a/book/src/format.md b/book/src/format.md index 1b5e6972..7517e4c0 100644 --- a/book/src/format.md +++ b/book/src/format.md @@ -21,3 +21,56 @@ enum Request { SetAddress { address: u8 }, } ``` + +NOTE: for generic structs and enums the `derive` macro adds `Format` bounds to the *types of the generic fields* rather than to all the generic (input) parameters of the struct / enum. +Built-in `derive` attributes like `#[derive(Debug)]` use the latter approach. +To our knowledge `derive(Format)` approach is more accurate in that it doesn't over-constrain the generic type parameters. +The different between the two approaches is depicted below: + +``` rust +# extern crate defmt; +# use defmt::Format; + +#[derive(Format)] +struct S<'a, T> { + x: Option<&'a T>, + y: u8, +} +``` + +``` rust +# extern crate defmt; +# use defmt::Format; + +// `Format` produces this implementation +impl<'a, T> Format for S<'a, T> +where + Option<&'a T>: Format // <- main difference +{ + // .. + # fn format(&self, f: &mut defmt::Formatter) {} +} + +#[derive(Debug)] +struct S<'a, T> { + x: Option<&'a T>, + y: u8, +} +``` + +``` rust +# use std::fmt::Debug; +# struct S<'a, T> { +# x: Option<&'a T>, +# y: u8, +# } + +// `Debug` produces this implementation +impl<'a, T> Debug for S<'a, T> +where + T: Debug // <- main difference +{ + // .. + # fn fmt(&self, f: &mut core::fmt::Formatter) -> std::fmt::Result { Ok(()) } +} +``` diff --git a/firmware/qemu/src/bin/log.rs b/firmware/qemu/src/bin/log.rs index 8f4378d9..9a9d97a6 100644 --- a/firmware/qemu/src/bin/log.rs +++ b/firmware/qemu/src/bin/log.rs @@ -133,6 +133,129 @@ fn main() -> ! { // issue #111 defmt::info!("{:[?]}", [true, true, false]); + /* issue #124 (start) */ + // plain generic struct + { + #[derive(Format)] + struct S { + x: u8, + y: T, + } + + defmt::info!("{:?}", S { x: 42, y: 43u8 }); + } + + // generic struct with bounds + { + #[derive(Format)] + struct S + where + T: Copy, + { + x: u8, + y: T, + } + + defmt::info!("{:?}", S { x: 44, y: 45u8 }); + } + + // generic struct with `Option` field + { + #[derive(Format)] + struct S + where + T: Copy, + { + x: u8, + y: Option, + } + + defmt::info!( + "{:?}", + S { + x: 46, + y: Some(47u8) + } + ); + } + + // generic struct with lifetimes and lifetime bounds + { + #[derive(Format)] + struct S<'a, T> + where + T: 'a, + { + x: Option<&'a u8>, + y: T, + } + + defmt::info!("{:?}", S { x: Some(&48), y: 49u8 }); + } + + // plain generic enum + { + #[derive(Format)] + enum E { + A, + B(X), + C { y: Y }, + } + + defmt::info!("{:?}", E::::A); + defmt::info!("{:?}", E::::B(42)); + defmt::info!("{:?}", E::::C { y: 43 }); + } + + // generic enum with bounds + { + #[derive(Format)] + enum E + where + X: Copy, + { + A, + B(X), + C { y: Y }, + } + + defmt::info!("{:?}", E::::A); + defmt::info!("{:?}", E::::B(44)); + defmt::info!("{:?}", E::::C { y: 45 }); + } + + /* issue #124 (end) */ + // generic enum with `Option`/`Result` fields + { + #[derive(Format)] + enum E { + A, + B(Option), + C { y: Result }, + } + + defmt::info!("{:?}", E::::A); + defmt::info!("{:?}", E::::B(Some(46))); + defmt::info!("{:?}", E::::C { y: Ok(47) }); + } + + // generic enum with lifetimes and lifetime bounds + { + #[derive(Format)] + enum E<'a, T> + where + T: 'a, + { + A, + B(Option<&'a u8>), + C { y: T }, + } + + defmt::info!("{:?}", E::::A); + defmt::info!("{:?}", E::::B(Some(&48))); + defmt::info!("{:?}", E::C { y: 49u8 }); + } + loop { debug::exit(debug::EXIT_SUCCESS) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 91c717cd..8cde46c5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -143,12 +143,7 @@ impl MLevel { MLevel::Info => { // defmt-default is enabled for dev & release profile so debug_assertions // does not matter - &[ - "defmt-info", - "defmt-debug", - "defmt-trace", - "defmt-default", - ] + &["defmt-info", "defmt-debug", "defmt-trace", "defmt-default"] } MLevel::Warn => { // defmt-default is enabled for dev & release profile so debug_assertions @@ -185,6 +180,7 @@ pub fn format(ts: TokenStream) -> TokenStream { let ident = input.ident; let mut fs = String::new(); + let mut field_types = vec![]; let mut exprs = vec![]; match input.data { Data::Enum(de) => { @@ -216,6 +212,7 @@ pub fn format(ts: TokenStream) -> TokenStream { let exprs = fields( &var.fields, &mut fs, + &mut field_types, Kind::Enum { patterns: &mut pats, }, @@ -243,7 +240,7 @@ pub fn format(ts: TokenStream) -> TokenStream { Data::Struct(ds) => { fs = ident.to_string(); - let args = fields(&ds.fields, &mut fs, Kind::Struct); + let args = fields(&ds.fields, &mut fs, &mut field_types, Kind::Struct); // FIXME expand this `write!` and conditionally omit the tag (string index) exprs.push(quote!(defmt::export::write!(f, #fs #(,#args)*);)) } @@ -255,8 +252,30 @@ pub fn format(ts: TokenStream) -> TokenStream { } } + let params = input.generics.params; + let predicates = if params.is_empty() { + vec![] + } else { + // `Format` bounds for non-native field types + let mut preds = field_types + .into_iter() + .map(|ty| quote!(#ty: defmt::Format)) + .collect::>(); + // extend with the where clause from the struct/enum declaration + if let Some(where_clause) = input.generics.where_clause { + preds.extend( + where_clause + .predicates + .into_iter() + .map(|pred| quote!(#pred)), + ) + } + preds + }; quote!( - impl defmt::Format for #ident { + impl<#params> defmt::Format for #ident<#params> + where #(#predicates),* + { fn format(&self, f: &mut defmt::Formatter) { #(#exprs)* } @@ -270,7 +289,13 @@ enum Kind<'p> { Enum { patterns: &'p mut TokenStream2 }, } -fn fields(fields: &Fields, format: &mut String, mut kind: Kind) -> Vec { +fn fields( + fields: &Fields, + format: &mut String, + // collect all *non-native* types that appear as fields + field_types: &mut Vec, + mut kind: Kind, +) -> Vec { let mut list = vec![]; match fields { Fields::Named(FieldsNamed { named: fs, .. }) @@ -295,7 +320,10 @@ fn fields(fields: &Fields, format: &mut String, mut kind: Kind) -> Vec