-
-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement precedence of inner display hint #359
Conversation
dc6e35f
to
14b0600
Compare
14b0600
to
decfc08
Compare
macros/src/lib.rs
Outdated
@@ -429,7 +429,11 @@ fn fields( | |||
"?".to_string() | |||
}); | |||
if let Some(ident) = f.ident.as_ref() { | |||
core::write!(format, "{}: {{={}}}", ident, ty).ok(); | |||
if ty == "str" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem with this approach (attaching :?
only to the =str
type) is that it won't work with generic structures. For example:
#[derive(Format)]
struct S1<T> {
x: T,
y: u8,
}
// outputs: "S { x: hi, y: 42 }"
defmt::info!("{}", S1 { x: "hi", y: 42 });
// outputs: "S { x: hi, y: 0x2a }"
defmt::info!("{:x}", S1 { x: "hi", y: 42 });
This is because the expansion of the macro will use the format string "S1 {{ x: {=?}, y: {=u8} }}"
. The proc-macro cannot know whether the generic type T
will be instantiated to str
or not. Also, you don't the :?
format specifier if T
is instantiated to a type that's not a string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback. The code is changed now to apply the Debug hint :?
to all inner types. As a side effect this changes the output in the log
test program as well.
#[derive(Format)]
struct Data<'a> {
name: &'a [u8],
value: bool,
}
let data = &[Data { name: b"Hi", value: true }];
defmt::info!("{=[?]:a}", *data);
// before the change
// -> INFO [Data { name: b"Hi", value: true }]
// after the change
// -> INFO [Data { name: [72, 105], value: true }]
6a56b7f
to
2bbc44d
Compare
firmware/qemu/src/bin/log.out
Outdated
@@ -111,6 +111,6 @@ | |||
0.000110 INFO b"Hi" | |||
0.000111 INFO b"Hi" | |||
0.000112 INFO [45054, 49406] | |||
0.000113 INFO [Data { name: b"Hi", value: true }] | |||
0.000113 INFO [Data { name: [72, 105], value: true }] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This slice is formatted with {=[?]:a}
, why is the :a
hint ignored?
firmware/qemu/src/bin/hints_inner.rs
Outdated
y: u8, | ||
} | ||
|
||
// outputs: "S { x: hi, y: 42 }" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// outputs: "S { x: hi, y: 42 }" | |
// outputs: "S { x: "hi", y: 42 }" |
firmware/qemu/src/bin/hints_inner.rs
Outdated
|
||
// outputs: "S { x: hi, y: 42 }" | ||
defmt::info!("{}", S1 { x: "hi", y: 42 }); | ||
// outputs: "S { x: hi, y: 0x2a }" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// outputs: "S { x: hi, y: 0x2a }" | |
// outputs: "S { x: "hi", y: 0x2a }" |
2bbc44d
to
8d97af4
Compare
c7b7fbd
to
57e88fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks generally good to me. I have a couple of questions (not all particular about this PR) and style suggestions:
- Could and should we drop
:?
from our grammar, since "Both display hints {} and {:?} now produce the same desired output."? - Does this PR respect all of
x
,X
,#x
,b
?
let mut entries = BTreeMap::new(); | ||
|
||
entries.insert( | ||
0, | ||
TableEntry::new_without_symbol(Tag::Info, "x={:b}".to_owned()), | ||
); | ||
entries.insert( | ||
1, | ||
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=u8:?} }}".to_owned()), | ||
); | ||
|
||
let table = Table { | ||
entries, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about this pattern? It would also be applicable for some other tests.
let mut entries = BTreeMap::new(); | |
entries.insert( | |
0, | |
TableEntry::new_without_symbol(Tag::Info, "x={:b}".to_owned()), | |
); | |
entries.insert( | |
1, | |
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=u8:?} }}".to_owned()), | |
); | |
let table = Table { | |
entries, | |
let entries = vec![ | |
TableEntry::new_without_symbol(Tag::Info, "x={:b}".to_owned()), | |
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=u8:x} }}".to_owned()), | |
]; | |
let table = Table { | |
entries: entries.into_iter().enumerate().collect::<BTreeMap<_, _>>(), |
decoder/src/frame.rs
Outdated
_ => { | ||
if let Some(DisplayHint::Debug) = hint { | ||
format_u128(*x as u128, parent_hint, &mut buf)?; | ||
} else { | ||
format_u128(*x as u128, hint, &mut buf)?; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ => { | |
if let Some(DisplayHint::Debug) = hint { | |
format_u128(*x as u128, parent_hint, &mut buf)?; | |
} else { | |
format_u128(*x as u128, hint, &mut buf)?; | |
} | |
} | |
_ => match hint { | |
Some(DisplayHint::Debug) => { | |
format_u128(*x as u128, parent_hint, &mut buf)? | |
} | |
_ => format_u128(*x as u128, hint, &mut buf)?, | |
}, |
@@ -410,7 +410,7 @@ fn fields( | |||
"?".to_string() | |||
}); | |||
if let Some(ident) = f.ident.as_ref() { | |||
core::write!(format, "{}: {{={}}}", ident, ty).ok(); | |||
core::write!(format, "{}: {{={}:?}}", ident, ty).ok(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the :?
needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks good to me. let's see if bors accepts it
} | ||
} | ||
Arg::Ixx(x) => format_i128(*x as i128, hint, &mut buf)?, | ||
Arg::Str(x) | Arg::Preformatted(x) => format_str(x, hint, &mut buf)?, | ||
Arg::IStr(x) => format_str(x, hint, &mut buf)?, | ||
Arg::Format { format, args } => buf.push_str(&format_args(format, args, hint)), | ||
Arg::Format { format, args } => match parent_hint { | ||
Some(DisplayHint::Ascii) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite follow why :a
is a special case here. (there some bugs / limitations (?) around :a
and generics but this doesn't seem like it fixes them or creates more)
decoder/src/lib.rs
Outdated
entries, | ||
timestamp: Some(TableEntry::new_without_symbol( | ||
Tag::Timestamp, | ||
"{=u8:µs}".to_owned(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this may need to be changed to :us
after a recent PR but bors will point it out I think)
bors r+ |
359: Implement precedence of inner display hint r=japaric a=justahero This PR implements precedence of display hints inner types, e.g. `u8`, `u16` etc. It implements the display hint precedence rules given in #319. When formating variables using the `defmt::Format` trait, either by implementing it or using the `derive` macro, the outer display hint is ignored depending on the inner type & its display hint. For example, a struct that uses the `derive` marco ```rust #[derive(Format)] struct S { x: u8 } // derive would expand to impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ x: {=u8:?} }}", self.x); } } ``` This uses the `Debug` display hint (`:?`) for the inner type `x` of struct `S`. When displaying the above struct `S` with ```rust info!("{:x}", S { x: 42 }); // -> INFO S { x: 0x2a } ``` the outer display hint `:x` is applied to the inner type `x`. In this case the outer display hint hexadecimal is used. On the other hand if the inner type is one of `:x` (hex), `:b` (binary), the outer display hint will be ignored and the inner hint has precedence. This time the implementation uses the `:b` (binary) display hint to format inner type `x`. ```rust impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ x: {=u8:b} }}", self.x); } } ``` When logging the struct this time ```rust info!("{:x}", S { x: 42 }); // -> INFO S { x: 0b101010 } ``` the outer display hint `:x` is ignored, the inner display hint `:b` is used, therefore the value `42` is formatted as a binary string `0b101010`. Another precedence rule is related to `str` fields of structs. As given in the RFC #319, the following struct with an inner `str` field is deriving the `defmt::Format` trait as follows. ```rust #[derive(Format)] struct S { x: &'static str } // derive now expands to impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ x: {=str:?} }}", self.x); } } ``` The difference is `x: &'static str` is expanded to `{=str:?}` instead of a plain `{=str}`. This way string in `x` is enclosed by double quotes. ```rust defmt::info!("{} world", S { x: "hello" }); // -> "hello" world defmt::info("hello {:?}", S { x: "world" }); // -> hello "world" ``` Both display hints `{}` and `{:?}` now produce the same desired output. Co-authored-by: Sebastian Ziebell <sebastian.ziebell@asquera.de>
Build failed: |
defmt::info!("the answer is {}", "42"); // INFO the answer is 42
defmt::info!("the answer is {:?}", "42"); // INFO the answer is "42" |
those are the new tests. @justahero could you please update the timestamp format to match PR #522 ? feel free to |
c40cca2
to
1528d6e
Compare
* add test to check generated bytes for inner hint * add log output files for hints_inner program
This change checks the inner and outer type hint of nested `Uxx` values. For example: ```rust struct S { x: u8 } // derive expands to impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ x: {=u8:?} }}", self.x); } } ``` where inner value `x` is of type `u8` with display hint `Debug`. When this struct is formated using an outer display hint then the outer hint will be used. ```rust info!("{:x}", S { x: 42 }); ``` renders as: `INFO S { x: 0x2a }` due to the `:x` (hex) display hint. On the other hand if the inner type is one of `:x` (hex), `:b` (binary) or `:a` (ascii), the outer display hint will be ignored. ```rust impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ x: {=u8:b} }}", self.x); } } ``` The outer display hint (`:x`) in ```rust info!("{:x}", S { x: 42 }); ``` will be ignored, the output is rendered with inner display hint `:b` as `INFO S { x: 0b101010 }` * remove `seen_hits` property from `Arg::Format` variant
* add test log files for inner hints
Instead of applying the Debug type hint `:?` to all inner fields. * add test to check struct with generic type * update test output
This change adds display type hint precedence of `Uxxx` nested attributes. For example given the following struct and implementation. ```rust struct S { y: u8 } impl defmt::Format for S { fn format(&self, f: defmt::Formatter) { write!(f, "S {{ y: {=u8:?} }}") } } ``` If the user prints the struct as: ```rust info!("{:x}", S { y: 42 }); ``` the formatter should output `S { y: 0x2a }`, the outermost display hint `:x` applies.
The given test checks the display output of ```rust struct Data<'a> { name: &'a [u8], } let data = &[Data { name: b"Hi" }]; defmt::info!("{=[?]:a}", *data); ``` The test is currently failing, instead of `"Hi"` it outputs `[72, 105]` instead.
The display hint `"{=[?]:a}"` now applies as outer most type hint instead of the inner type. For example the struct with inner type: ```rust struct Data<'a'> { name: &'a [u8]', } let data = &[Data{name: b"Hi"}] dfmt::info!("{=[?]:a}"); ``` Before the string was displayed as `INFO [Data { name: [72, 105] }]`, while now it renders correctly as `INFO [Data { name: b"Hi" }]`. * update snapshot test * comment each single entry in `bytes` array in test
* apply pretty-printing to all `:b` and `:x` * change `µs` to `us`
1528d6e
to
b3b0af0
Compare
bors r+ |
Build succeeded: |
This PR implements precedence of display hints inner types, e.g.
u8
,u16
etc. It implements the display hint precedence rules given in #319.When formating variables using the
defmt::Format
trait, either by implementing it or using thederive
macro, the outer display hint is ignored depending on the inner type & its display hint.For example, a struct that uses the
derive
marcoThis uses the
Debug
display hint (:?
) for the inner typex
of structS
. When displaying the above structS
withthe outer display hint
:x
is applied to the inner typex
. In this case the outer display hint hexadecimal is used.On the other hand if the inner type is one of
:x
(hex),:b
(binary), the outer display hint will be ignored and the inner hint has precedence.This time the implementation uses the
:b
(binary) display hint to format inner typex
.When logging the struct this time
the outer display hint
:x
is ignored, the inner display hint:b
is used, therefore the value42
is formatted as a binary string0b101010
.Another precedence rule is related to
str
fields of structs. As given in the RFC #319, the following struct with an innerstr
field is deriving thedefmt::Format
trait as follows.The difference is
x: &'static str
is expanded to{=str:?}
instead of a plain{=str}
. This way string inx
is enclosed by double quotes.Both display hints
{}
and{:?}
now produce the same desired output.