mcarton/rust-derivative

`format_with` misses bounds on the Dummy struct and its instantiation.

glandium opened this issue · 2 comments

Consider the following rust code:

use derivative::Derivative;

trait Foo {}

fn fmt<T>(t: &T, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(f, "foo")
}

#[derive(Debug)]
struct Qux<'a, T: Foo>(&'a T);

#[derive(Derivative)]
#[derivative(Debug)]
struct Bar<'a, T: Foo>(#[derivative(Debug(format_with="fmt"))] Qux<'a, T>);

fn main() {
}

This fails to build with

error[E0277]: the trait bound `T: Foo` is not satisfied
  --> src/main.rs:14:24
   |
14 | struct Bar<'a, T: Foo>(#[derivative(Debug(format_with="fmt"))] Qux<'a, T>);
   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Foo` is not implemented for `T`
   |
   = help: consider adding a `where T: Foo` bound
note: required by `Qux`
  --> src/main.rs:10:1
   |
10 | struct Qux<'a, T: Foo>(&'a T);
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expanded, the code looks like:

impl <'a, T: Foo> ::std::fmt::Debug for Bar<'a, T> where T: ::std::fmt::Debug
 {
    fn fmt(&self, __f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            Bar(ref __arg_0) => {
                let mut __debug_trait_builder = __f.debug_tuple("Bar");
                let __arg_0 =
                    {
                        struct Dummy<'a, '_derivative,
                                     T>(&'_derivative Qux<'a, T>,
                                        ::std::marker::PhantomData<(T)>) where
                               T: '_derivative;
                        impl <'a, '_derivative, T: Foo> ::std::fmt::Debug for
                         Dummy<'a, '_derivative, T> where T: '_derivative {
                            fn fmt(&self, __f: &mut ::std::fmt::Formatter)
                             -> ::std::fmt::Result {
                                fmt(&self.0, __f)
                            }
                        }
                        Dummy::<'a, T>(__arg_0, ::std::marker::PhantomData)
                    };
                let _ = __debug_trait_builder.field(&__arg_0);
                __debug_trait_builder.finish()
            }
        }
    }
}

With the code expanded, the error becomes:

error[E0277]: the trait bound `T: Foo` is not satisfied
  --> src/main.rs:23:41
   |
23 |                                      T>(&'_derivative Qux<'a, T>,
   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Foo` is not implemented for `T`
   |
   = help: consider adding a `where T: Foo` bound
note: required by `Qux`
  --> src/main.rs:10:1
   |
10 | struct Qux<'a, T: Foo>(&'a T);
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The code around line 23 is:

struct Dummy<'a, '_derivative,
             T: Foo>(&'_derivative Qux<'a, T>,
                ::std::marker::PhantomData<(T)>) where
       T: '_derivative;

So what is missing here is the original bound for T, because Qux needs it (as per the error message).

But adding that is not enough in this case, because fixing up the expanded code still fails, with a different error:

error[E0107]: wrong number of lifetime arguments: expected 2, found 1
  --> src/main.rs:33:25
   |
33 |                         Dummy::<'a, T>(__arg_0, ::std::marker::PhantomData)
   |                         ^^^^^^^^^^^^^^ expected 2 lifetime arguments

What works, here, is Dummy::<'a, '_, T>(...).

Not sure this is completely correct, but the following patch works for this testcase:

diff --git a/src/debug.rs b/src/debug.rs
index a4ffa10..4f14a9b 100644
--- a/src/debug.rs
+++ b/src/debug.rs
@@ -131,9 +131,6 @@ fn format_with(
     let fmt_path = fmt_path();
     let phantom_path = phantom_path();
 
-    let ctor_generics = generics.clone();
-    let (_, ctor_ty_generics, _) = ctor_generics.split_for_impl();
-
     generics
         .make_where_clause()
         .predicates
@@ -178,11 +175,14 @@ fn format_with(
     // Leave off the type parameter bounds, defaults, and attributes
     let phantom = generics.type_params().map(|tp| &tp.ident);
 
+    let mut ctor_generics = generics.clone();
+    *ctor_generics.lifetimes_mut().last().unwrap() = syn::LifetimeDef::new(parse_quote!('_));
+    let (_, ctor_ty_generics, _) = ctor_generics.split_for_impl();
     let ctor_ty_generics = ctor_ty_generics.as_turbofish();
 
     quote_spanned!(f.span=>
         let #arg_n = {
-            struct Dummy #ty_generics (&'_derivative #ty, #phantom_path <(#(#phantom),*)>) #where_clause;
+            struct Dummy #impl_generics (&'_derivative #ty, #phantom_path <(#(#phantom),*)>) #where_clause;
 
             impl #impl_generics #debug_trait_path for Dummy #ty_generics #where_clause {
                 fn fmt(&self, __f: &mut #fmt_path::Formatter) -> #fmt_path::Result {

Fixed in #61 and released as v2.0.2.

Thanks for the report and patch!