Repository: tokio-rs/prost (language: rust)

Issue:
prost-derive generic type?
Hi there!

Currently prost-derive does not handle generics. I have a use case where I would need to have something like this:

```rust
pub trait MyTrait: prost::Message + other stuff...

#[derive(prost::Message, ....)]
struct Element<A: MyTrait> {
   #[prost(message, tag = "1")]
   data: A,
   #[prost(uint64, tag = "2")]
   timestamp:  u64,
}
```
As long as the generic implements prost::Message, it shouldn't be any problem I guess?  Just thought I would post here first to see if it is an easy change before I start working on a PR for it.

Cheers



Discussion / hints:

Fix patch:
diff --git a/prost-derive/src/lib.rs b/prost-derive/src/lib.rs
index b59a0788a..056ef4ae8 100644
--- a/prost-derive/src/lib.rs
+++ b/prost-derive/src/lib.rs
@@ -29,9 +29,8 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
         Data::Union(..) => bail!("Message can not be derived for a union"),
     };
 
-    if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
-        bail!("Message may not be derived for generic type");
-    }
+    let generics = &input.generics;
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
     let fields = match variant_data {
         DataStruct {
@@ -148,7 +147,7 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
     } else {
         quote! {
             #[allow(dead_code)]
-            impl #ident {
+            impl #impl_generics #ident #ty_generics #where_clause {
                 #(#methods)*
             }
         }
@@ -175,7 +174,7 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
     };
 
     let expanded = quote! {
-        impl ::prost::Message for #ident {
+        impl #impl_generics ::prost::Message for #ident #ty_generics #where_clause {
             #[allow(unused_variables)]
             fn encode_raw<B>(&self, buf: &mut B) where B: ::prost::bytes::BufMut {
                 #(#encode)*
@@ -207,15 +206,15 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
             }
         }
 
-        impl Default for #ident {
-            fn default() -> #ident {
+        impl #impl_generics Default for #ident #ty_generics #where_clause {
+            fn default() -> Self {
                 #ident {
                     #(#default)*
                 }
             }
         }
 
-        impl ::core::fmt::Debug for #ident {
+        impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
             fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                 let mut builder = #debug_builder;
                 #(#debugs;)*
@@ -238,9 +237,8 @@ fn try_enumeration(input: TokenStream) -> Result<TokenStream, Error> {
     let input: DeriveInput = syn::parse(input)?;
     let ident = input.ident;
 
-    if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
-        bail!("Message may not be derived for generic type");
-    }
+    let generics = &input.generics;
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
     let punctuated_variants = match input.data {
         Data::Enum(DataEnum { variants, .. }) => variants,
@@ -290,7 +288,7 @@ fn try_enumeration(input: TokenStream) -> Result<TokenStream, Error> {
     );
 
     let expanded = quote! {
-        impl #ident {
+        impl #impl_generics #ident #ty_generics #where_clause {
             #[doc=#is_valid_doc]
             pub fn is_valid(value: i32) -> bool {
                 match value {
@@ -308,13 +306,13 @@ fn try_enumeration(input: TokenStream) -> Result<TokenStream, Error> {
             }
         }
 
-        impl ::core::default::Default for #ident {
+        impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause {
             fn default() -> #ident {
                 #ident::#default
             }
         }
 
-        impl ::core::convert::From<#ident> for i32 {
+        impl #impl_generics ::core::convert::From::<#ident> for i32 #ty_generics #where_clause {
             fn from(value: #ident) -> i32 {
                 value as i32
             }
@@ -340,9 +338,8 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
         Data::Union(..) => bail!("Oneof can not be derived for a union"),
     };
 
-    if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
-        bail!("Message may not be derived for generic type");
-    }
+    let generics = &input.generics;
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
     // Map the variants into 'fields'.
     let mut fields: Vec<(Ident, Field)> = Vec::new();
@@ -428,7 +425,7 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
     });
 
     let expanded = quote! {
-        impl #ident {
+        impl #impl_generics #ident #ty_generics #where_clause {
             pub fn encode<B>(&self, buf: &mut B) where B: ::prost::bytes::BufMut {
                 match *self {
                     #(#encode,)*
@@ -436,7 +433,7 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
             }
 
             pub fn merge<B>(
-                field: &mut ::core::option::Option<#ident>,
+                field: &mut ::core::option::Option<#ident #ty_generics>,
                 tag: u32,
                 wire_type: ::prost::encoding::WireType,
                 buf: &mut B,
@@ -457,7 +454,7 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
             }
         }
 
-        impl ::core::fmt::Debug for #ident {
+        impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
             fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                 match *self {
                     #(#debug,)*


Test patch:
diff --git a/tests/src/generic_derive.rs b/tests/src/generic_derive.rs
new file mode 100644
index 000000000..e724809d4
--- /dev/null
+++ b/tests/src/generic_derive.rs
@@ -0,0 +1,28 @@
+pub trait CustomType: prost::Message + Default {}
+
+impl CustomType for u64 {}
+
+#[derive(Clone, prost::Oneof)]
+enum GenericEnum<A: CustomType> {
+    #[prost(message, tag = "1")]
+    Data(GenericMessage<A>),
+    #[prost(uint64, tag = "2")]
+    #[allow(dead_code)]
+    Number(u64),
+}
+
+#[derive(Clone, prost::Message)]
+struct GenericMessage<A: CustomType> {
+    #[prost(message, tag = "1")]
+    data: Option<A>,
+}
+
+#[test]
+fn generic_enum() {
+    let msg = GenericMessage { data: Some(100u64) };
+    let enumeration = GenericEnum::Data(msg);
+    match enumeration {
+        GenericEnum::Data(d) => assert_eq!(100, d.data.unwrap()),
+        GenericEnum::Number(_) => panic!("Not supposed to reach"),
+    }
+}
diff --git a/tests/src/lib.rs b/tests/src/lib.rs
index 7978c0119..26d86b184 100644
--- a/tests/src/lib.rs
+++ b/tests/src/lib.rs
@@ -36,6 +36,8 @@ mod debug;
 #[cfg(test)]
 mod deprecated_field;
 #[cfg(test)]
+mod generic_derive;
+#[cfg(test)]
 mod message_encoding;
 #[cfg(test)]
 mod no_unused_results;