defmt icon indicating copy to clipboard operation
defmt copied to clipboard

Derive macro for Format does not set generic bound correctly

Open Sympatron opened this issue 1 year ago • 3 comments

The currently implementation of the derive macro does set T: defmt::Format bounds for all generic parameters T. This is not allways correct. Consider the following example:

trait Foo {
    type Bar;
}
#[derive(defmt::Format)]
struct Baz<T: Foo> {
    field: T::Bar
}

This will generate the trait bound T: defmt::Format instead of T::Bar: defmt::Format. I am not quite sure what the general rule is in this case, but the easiest thing to do would be to require : defmt::Format for all field types whether they are generic or not.

I could try to work on this, but I never wrote procedural macros before, so I am not quite sure how to solve this. Currently this code is responsible for the where clause: https://github.com/knurling-rs/defmt/blob/06650b9cad2e5c8ce35b5694e1b74b15d7ecf4b9/macros/src/derives/format/codegen.rs#L47-L55

I think where_clause should be moved to EncodeData and constructed in encode_struct_data from the fields.

Sympatron avatar Dec 15 '23 12:12 Sympatron

OK, interesting! We're going to need to think about this one, and we're also just about to head into Christmas break. We'll get back to this in 2024.

jonathanpallant avatar Dec 15 '23 12:12 jonathanpallant

My colleague offers this expansion of some built-in derives:

trait Foo {
    type Bar;
}
#[derive(Clone, Debug)]
struct Baz<T: Foo> {
    field: T::Bar,
}

expands to

#[automatically_derived]
impl<T: ::core::clone::Clone + Foo> ::core::clone::Clone for Baz<T>
where
    T::Bar: ::core::clone::Clone,
{
    #[inline]
    fn clone(&self) -> Baz<T> {
        Baz {
            field: ::core::clone::Clone::clone(&self.field),
        }
    }
}

#[automatically_derived]
impl<T: ::core::fmt::Debug + Foo> ::core::fmt::Debug for Baz<T>
where
    T::Bar: ::core::fmt::Debug,
{
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "Baz", "field", &&self.field)
    }
}

It probably makes sense to emit something similar. I don't fully understand why T: Debug and T: Clone in this expansion, but if that's what the standard library does, it probably makes sense.

jonathanpallant avatar Dec 15 '23 14:12 jonathanpallant

More details on this behaviour: https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/

jonathanpallant avatar Dec 15 '23 16:12 jonathanpallant

I see 2 non-exclusive options going forward:

  1. Provide a #[defmt(bounds = "")] container-attribute to overwrite the inferred bounds. This is what serde does.
  2. Infer the perfect bounds. This is also what serde does.

See https://github.com/jamesmunns/postcard/issues/153 for a similar issue in another crate.

I think the first option is pretty easy to implement and would provide a solid workaround.

ia0 avatar Aug 02 '24 15:08 ia0

Didn't PR #800 already fix this? I think this issue can be closed now.

Sympatron avatar Aug 05 '24 08:08 Sympatron

Is #800 part of 0.3.8? If yes, then I'll extract a minimal example from my concrete problem.

ia0 avatar Aug 05 '24 08:08 ia0

While trying to create a minimal example, I found the issue. The defmt:0.3.8 crate depends on defmt-macros:0.3.2 and my Cargo.lock was using 0.3.6 which doesn't seem to have the fix. I've run cargo update --precise=0.3.9 defmt-macros and now it works. It might be useful to bump the minimum version of defmt-macros in defmt as a minor bump since it adds a new feature.

ia0 avatar Aug 05 '24 08:08 ia0

The next release will bump the minimum version.

Urhengulas avatar Aug 14 '24 12:08 Urhengulas