Skip to content

Commit

Permalink
Merge #662
Browse files Browse the repository at this point in the history
662: Add attributes for Format derive to use Debug2Format on specific fields r=jonas-schievink a=mattico

I often have structs or enums where most fields impl `Format`, but a few third-party types do not. It is a pain to have to manually impl `Format` for the whole type when only one field doesn't work. I added an attribute that lets you override specific fields to use `Debug2Format` or `Display2Format` like so:

```rust
#[derive(defmt::Format)]
pub enum Error {
    Serial(serial::Error),
    Timeout(us_timer::TimeoutError),
    Serde(#[defmt(Debug2Format)] serde_json::Error),
    ResponseTooLarge,
}
```

Co-authored-by: Matt Ickstadt <mattico8@gmail.com>
  • Loading branch information
bors[bot] and mattico authored Feb 22, 2022
2 parents 8b34b86 + 98603a8 commit e64138f
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 34 deletions.
27 changes: 27 additions & 0 deletions book/src/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,32 @@ If you quickly want to get some code running and do not care about it being effi
Note that this always uses `{:?}` to format the contained value, meaning that any provided defmt display hints will be ignored.

When using `#[derive(Format)]` you may use the `#[defmt()]` attribute on specific fields to use these adapter types.
Example below:

``` rust
# extern crate defmt;
# use defmt::Format;
# mod serde_json {
# #[derive(Debug)]
# pub enum Error {}
# }
#[derive(Format)]
enum Error {
Serde(#[defmt(Debug2Format)] serde_json::Error),
ResponseTooLarge,
}

# struct Celsius();
# impl std::fmt::Display for Celsius {
# fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) }
# }
#[derive(Format)]
struct Reading {
#[defmt(Display2Format)]
temperature: Celsius,
}
```

[`Display2Format`]: https://docs.rs/defmt/*/defmt/struct.Display2Format.html
[`Debug2Format`]: https://docs.rs/defmt/*/defmt/struct.Debug2Format.html
89 changes: 87 additions & 2 deletions defmt/tests/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,91 @@ fn bitfields_assert_range_exclusive() {
]);
}

#[test]
fn debug_attr_struct() {
#[derive(Debug)]
struct DebugOnly {
_a: i32,
}

#[derive(Format)]
struct X {
y: bool,
#[defmt(Debug2Format)]
d: DebugOnly,
}

let index = fetch_string_index();
check_format!(
&X {
y: false,
d: DebugOnly { _a: 3 }
},
[
index, // "X {{ y: {=bool}, d: {=?} }}"
0b0u8, // y
inc(index, 1), // DebugOnly's format string
b'D', // Text of the Debug output
b'e',
b'b',
b'u',
b'g',
b'O',
b'n',
b'l',
b'y',
b' ',
b'{',
b' ',
b'_',
b'a',
b':',
b' ',
b'3',
b' ',
b'}',
0xffu8
],
)
}

#[test]
fn display_attr_enum() {
use std::fmt;
struct DisplayOnly {}

impl fmt::Display for DisplayOnly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Display")
}
}

#[derive(Format)]
enum X {
#[allow(dead_code)]
Bool(bool),
Display(#[defmt(Display2Format)] DisplayOnly),
}

let index = fetch_string_index();
check_format!(
&X::Display(DisplayOnly {}),
[
index, // "Bool({=bool})|Display({=?})"
0b1u8, // Variant: Display
inc(index, 1), // DisplayOnly's format string
b'D', // Text of the Display output
b'i',
b's',
b'p',
b'l',
b'a',
b'y',
0xffu8
],
)
}

#[test]
fn boolean_struct() {
#[derive(Format)]
Expand All @@ -163,7 +248,7 @@ fn boolean_struct() {
check_format!(
&X { y: false, z: true },
[
index, // "X {{ x: {=bool}, y: {=bool} }}"
index, // "X {{ y: {=bool}, z: {=bool} }}"
0b0u8, // y
0b1u8, // z
],
Expand All @@ -182,7 +267,7 @@ fn single_struct() {
check_format!(
&X { y: 1, z: 2 },
[
index, // "X {{ x: {=u8}, y: {=u16} }}"
index, // "X {{ y: {=u8}, z: {=u16} }}"
1u8, // x
2u8, // y.low
0u8, // y.high
Expand Down
7 changes: 7 additions & 0 deletions defmt/tests/ui/derive-empty-attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(defmt::Format)]
struct S {
#[defmt()]
f: bool,
}

fn main() {}
5 changes: 5 additions & 0 deletions defmt/tests/ui/derive-empty-attr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: expected 1 attribute argument
--> $DIR/derive-empty-attr.rs:3:7
|
3 | #[defmt()]
| ^^^^^^^
7 changes: 7 additions & 0 deletions defmt/tests/ui/derive-invalid-attr-arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(defmt::Format)]
struct S {
#[defmt(FooBar)]
f: bool,
}

fn main() {}
5 changes: 5 additions & 0 deletions defmt/tests/ui/derive-invalid-attr-arg.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: expected `Debug2Format` or `Display2Format`
--> $DIR/derive-invalid-attr-arg.rs:3:13
|
3 | #[defmt(FooBar)]
| ^^^^^^
8 changes: 8 additions & 0 deletions defmt/tests/ui/derive-multi-attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(defmt::Format)]
struct S {
#[defmt(Debug2Format)]
#[defmt()]
f: bool,
}

fn main() {}
7 changes: 7 additions & 0 deletions defmt/tests/ui/derive-multi-attr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: multiple `defmt` attributes not supported
--> $DIR/derive-multi-attr.rs:3:5
|
3 | / #[defmt(Debug2Format)]
4 | | #[defmt()]
5 | | f: bool,
| |___________^
7 changes: 6 additions & 1 deletion macros/src/derives/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ pub(crate) fn expand(input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);

let ident = &input.ident;
let codegen::EncodeData { format_tag, stmts } = match &input.data {
let encode_data = match &input.data {
Data::Enum(data) => codegen::encode_enum_data(ident, data),
Data::Struct(data) => codegen::encode_struct_data(ident, data),
Data::Union(_) => abort_call_site!("`#[derive(Format)]` does not support unions"),
};

let codegen::EncodeData { format_tag, stmts } = match encode_data {
Ok(data) => data,
Err(e) => return e.into_compile_error().into(),
};

let codegen::Generics {
impl_generics,
type_generics,
Expand Down
6 changes: 3 additions & 3 deletions macros/src/derives/format/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ pub(crate) struct EncodeData {
pub(crate) stmts: Vec<TokenStream2>,
}

pub(crate) fn encode_struct_data(ident: &Ident, data: &DataStruct) -> EncodeData {
pub(crate) fn encode_struct_data(ident: &Ident, data: &DataStruct) -> syn::Result<EncodeData> {
let mut format_string = ident.to_string();
let mut stmts = vec![];
let mut field_patterns = vec![];

let encode_fields_stmts =
fields::codegen(&data.fields, &mut format_string, &mut field_patterns);
fields::codegen(&data.fields, &mut format_string, &mut field_patterns)?;

stmts.push(quote!(match self {
Self { #(#field_patterns),* } => {
Expand All @@ -29,7 +29,7 @@ pub(crate) fn encode_struct_data(ident: &Ident, data: &DataStruct) -> EncodeData
}));

let format_tag = construct::interned_string(&format_string, "derived", false);
EncodeData { format_tag, stmts }
Ok(EncodeData { format_tag, stmts })
}

pub(crate) struct Generics<'a> {
Expand Down
10 changes: 5 additions & 5 deletions macros/src/derives/format/codegen/enum_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use crate::construct;

use super::EncodeData;

pub(crate) fn encode(ident: &Ident, data: &DataEnum) -> EncodeData {
pub(crate) fn encode(ident: &Ident, data: &DataEnum) -> syn::Result<EncodeData> {
if data.variants.is_empty() {
return EncodeData {
return Ok(EncodeData {
stmts: vec![quote!(match *self {})],
format_tag: construct::interned_string("!", "derived", false),
};
});
}

let mut format_string = String::new();
Expand All @@ -33,7 +33,7 @@ pub(crate) fn encode(ident: &Ident, data: &DataEnum) -> EncodeData {

let mut field_patterns = vec![];
let encode_fields_stmts =
super::fields::codegen(&variant.fields, &mut format_string, &mut field_patterns);
super::fields::codegen(&variant.fields, &mut format_string, &mut field_patterns)?;
let pattern = quote!( { #(#field_patterns),* } );

let encode_discriminant_stmt = discriminant_encoder.encode(index);
Expand All @@ -51,7 +51,7 @@ pub(crate) fn encode(ident: &Ident, data: &DataEnum) -> EncodeData {
#(#match_arms)*
})];

EncodeData { format_tag, stmts }
Ok(EncodeData { format_tag, stmts })
}

enum DiscriminantEncoder {
Expand Down
Loading

0 comments on commit e64138f

Please sign in to comment.