Repository: not-fl3/nanoserde
Pull request #134 (2025-05-30 07:36:10)

Add support for generic parameters on enums
Adds the ability to specify a generic parameter on enums. In some cases for JSON and RON formats, the generic parameter was not propagated properly.

novafacing: I improved the tests as suggested :)

Patch:
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
index 7bb3506..f7ed622 100644
--- a/derive/src/parse.rs
+++ b/derive/src/parse.rs
@@ -203,12 +203,10 @@ impl Generic {
         }
     }
 
-    #[cfg(any(feature = "binary", feature = "json"))]
     pub fn ident_only(&self) -> String {
         format!("{}{}", self.lifetime_prefix(), self.full())
     }
 
-    #[cfg(any(feature = "binary", feature = "json"))]
     pub fn full_with_const(&self, extra_bounds: &[&str], bounds: bool) -> String {
         let bounds = match (bounds, &self) {
             (true, Generic::Lifetime { .. }) => self.get_bounds().join(" + "),
diff --git a/derive/src/serde_json.rs b/derive/src/serde_json.rs
index f77baed..992216c 100644
--- a/derive/src/serde_json.rs
+++ b/derive/src/serde_json.rs
@@ -298,6 +298,7 @@ pub fn derive_de_json_struct(struct_: &Struct, crate_name: &str) -> TokenStream
 
 pub fn derive_ser_json_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
     let mut r = String::new();
+    let (generic_w_bounds, generic_no_bounds) = enum_bounds_strings(enum_, "SerJson", crate_name);
 
     for variant in enum_.variants.iter() {
         let field_name = variant.field_name.clone().unwrap();
@@ -423,14 +424,14 @@ pub fn derive_ser_json_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
 
     format!(
         "
-        impl {}::SerJson for {} {{
+        impl{} {}::SerJson for {}{} {{
             fn ser_json(&self, d: usize, s: &mut {}::SerJsonState) {{
                 match self {{
                     {}
                 }}
             }}
         }}",
-        crate_name, enum_.name, crate_name, r
+        generic_w_bounds, crate_name, enum_.name, generic_no_bounds, crate_name, r
     )
     .parse()
     .unwrap()
diff --git a/derive/src/serde_ron.rs b/derive/src/serde_ron.rs
index aaac871..29c4050 100644
--- a/derive/src/serde_ron.rs
+++ b/derive/src/serde_ron.rs
@@ -2,7 +2,10 @@ use alloc::format;
 use alloc::string::{String, ToString};
 use alloc::vec::Vec;
 
-use crate::parse::{Attribute, Category, Enum, Field, Struct, Type};
+use crate::{
+    parse::{Attribute, Category, Enum, Field, Struct, Type},
+    shared::{enum_bounds_strings, struct_bounds_strings},
+};
 
 use proc_macro::TokenStream;
 
@@ -38,6 +41,8 @@ pub fn derive_de_ron_proxy(proxy_type: &str, type_: &str, crate_name: &str) -> T
 
 pub fn derive_ser_ron_struct(struct_: &Struct, crate_name: &str) -> TokenStream {
     let mut s = String::new();
+    let (generic_w_bounds, generic_no_bounds) =
+        struct_bounds_strings(struct_, "SerRon", crate_name);
 
     for field in struct_.fields.iter().filter(|f| !attrs_skip(&f.attributes)) {
         let struct_fieldname = field.field_name.clone().unwrap();
@@ -72,7 +77,7 @@ pub fn derive_ser_ron_struct(struct_: &Struct, crate_name: &str) -> TokenStream
 
     format!(
         "
-        impl {}::SerRon for {} {{
+        impl{} {}::SerRon for {}{} {{
             fn ser_ron(&self, d: usize, s: &mut {}::SerRonState) {{
                 s.st_pre();
                 {}
@@ -80,11 +85,13 @@ pub fn derive_ser_ron_struct(struct_: &Struct, crate_name: &str) -> TokenStream
             }}
         }}
     ",
+        generic_w_bounds,
         crate_name,
         struct_
             .name
             .as_ref()
             .expect("Cannot implement for anonymous struct"),
+        generic_no_bounds,
         crate_name,
         s
     )
@@ -94,6 +101,8 @@ pub fn derive_ser_ron_struct(struct_: &Struct, crate_name: &str) -> TokenStream
 
 pub fn derive_ser_ron_struct_unnamed(struct_: &Struct, crate_name: &str) -> TokenStream {
     let mut body = String::new();
+    let (generic_w_bounds, generic_no_bounds) =
+        struct_bounds_strings(struct_, "SerRon", crate_name);
 
     let last = struct_.fields.len() - 1;
     for (n, _field) in struct_
@@ -109,18 +118,20 @@ pub fn derive_ser_ron_struct_unnamed(struct_: &Struct, crate_name: &str) -> Toke
     }
     format!(
         "
-        impl {}::SerRon for {} {{
+        impl{} {}::SerRon for {}{} {{
             fn ser_ron(&self, d: usize, s: &mut {}::SerRonState) {{
                 s.out.push('(');
                 {}
                 s.out.push(')');
             }}
         }}",
+        generic_w_bounds,
         crate_name,
         struct_
             .name
             .as_ref()
             .expect("Cannot implement for anonymous struct"),
+        generic_no_bounds,
         crate_name,
         body
     )
@@ -169,7 +180,8 @@ pub fn derive_de_ron_named(
         } else {
             None
         };
-        let ron_fieldname =(!field_attr_skip).then(|| shared::attrs_rename(&field.attributes).unwrap_or(struct_fieldname.clone()));
+        let ron_fieldname = (!field_attr_skip)
+            .then(|| shared::attrs_rename(&field.attributes).unwrap_or(struct_fieldname.clone()));
 
         unwraps.push(match default_val {
             Some(def) => format!("{}.unwrap_or_else(|| {})", localvar, def),
@@ -256,19 +268,32 @@ pub fn derive_de_ron_struct(struct_: &Struct, crate_name: &str) -> TokenStream {
         &struct_.attributes,
         crate_name,
     );
+    let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "DeRon", crate_name);
 
     format!(
-        "impl {}::DeRon for {} {{
+        "impl{} {}::DeRon for {}{} {{
             fn de_ron(s: &mut {}::DeRonState, i: &mut core::str::Chars) -> ::core::result::Result<Self,{}::DeRonErr> {{
                 ::core::result::Result::Ok({})
             }}
-        }}", crate_name, struct_.name.as_ref().expect("Cannot implement for anonymous struct"), crate_name, crate_name, body)
+        }}", 
+        generic_w_bounds,
+        crate_name,
+        struct_
+            .name
+            .as_ref()
+            .expect("Cannot implement for anonymous struct"),
+        generic_no_bounds,
+        crate_name,
+        crate_name,
+        body
+    )
     .parse()
     .unwrap()
 }
 
 pub fn derive_de_ron_struct_unnamed(struct_: &Struct, crate_name: &str) -> TokenStream {
     let mut body = String::new();
+    let (generic_w_bounds, generic_no_bounds) = struct_bounds_strings(struct_, "DeRon", crate_name);
 
     for _ in &struct_.fields {
         l!(
@@ -283,19 +308,30 @@ pub fn derive_de_ron_struct_unnamed(struct_: &Struct, crate_name: &str) -> Token
     }
 
     format! ("
-        impl {}::DeRon for {} {{
+        impl{} {}::DeRon for {}{} {{
             fn de_ron(s: &mut {}::DeRonState, i: &mut core::str::Chars) -> ::core::result::Result<Self,{}::DeRonErr> {{
                 s.paren_open(i)?;
                 let r = Self({});
                 s.paren_close(i)?;
                 ::core::result::Result::Ok(r)
             }}
-        }}", crate_name, struct_.name.as_ref().expect("Cannot implement for anonymous struct"), crate_name, crate_name, body
+        }}",
+        generic_w_bounds,
+        crate_name,
+        struct_
+            .name
+            .as_ref()
+            .expect("Cannot implement for anonymous struct"),
+        generic_no_bounds,
+        crate_name,
+        crate_name,
+        body
     ).parse().unwrap()
 }
 
 pub fn derive_ser_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
     let mut body = String::new();
+    let (generic_w_bounds, generic_no_bounds) = enum_bounds_strings(enum_, "SerRon", crate_name);
 
     for variant in &enum_.variants {
         let ident = &variant.field_name.clone().unwrap();
@@ -389,14 +425,14 @@ pub fn derive_ser_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
     }
     format!(
         "
-        impl {}::SerRon for {} {{
+        impl{} {}::SerRon for {}{} {{
             fn ser_ron(&self, d: usize, s: &mut {}::SerRonState) {{
                 match self {{
                     {}
                 }}
             }}
         }}",
-        crate_name, enum_.name, crate_name, body
+        generic_w_bounds, crate_name, enum_.name, generic_no_bounds, crate_name, body
     )
     .parse()
     .unwrap()
@@ -404,6 +440,8 @@ pub fn derive_ser_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
 
 pub fn derive_de_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
     let mut body = String::new();
+    let (generic_w_bounds, generic_no_bounds) = enum_bounds_strings(enum_, "DeRon", crate_name);
+
     for variant in &enum_.variants {
         let ident = variant.field_name.clone().unwrap();
 
@@ -461,7 +499,7 @@ pub fn derive_de_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
     }
 
     format! ("
-        impl {}::DeRon for {} {{
+        impl{} {}::DeRon for {}{} {{
             fn de_ron(s: &mut {}::DeRonState, i: &mut core::str::Chars) -> ::core::result::Result<Self,{}::DeRonErr> {{
                 // we are expecting an identifier
                 s.ident(i)?;
@@ -470,5 +508,5 @@ pub fn derive_de_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
                     _ => return ::core::result::Result::Err(s.err_enum(&s.identbuf))
                 }})
             }}
-        }}", crate_name, enum_.name, crate_name, crate_name, body).parse().unwrap()
+        }}", generic_w_bounds, crate_name, enum_.name, generic_no_bounds, crate_name, crate_name, body).parse().unwrap()
 }
diff --git a/derive/src/shared.rs b/derive/src/shared.rs
index 55cbb83..a8bfc6c 100644
--- a/derive/src/shared.rs
+++ b/derive/src/shared.rs
@@ -1,12 +1,8 @@
 #![cfg(any(feature = "json", feature = "ron", feature = "binary"))]
 
-#[cfg(any(feature = "json", feature = "binary"))]
-use alloc::{format, string::ToString, vec::Vec};
-
-use alloc::string::String;
-
-#[cfg(any(feature = "binary", feature = "json"))]
 use crate::parse::{Enum, Struct};
+use alloc::string::String;
+use alloc::{format, string::ToString, vec::Vec};
 
 macro_rules! l {
     ($target:ident, $line:expr) => {
@@ -94,7 +90,6 @@ pub fn attrs_crate(attributes: &[crate::parse::Attribute]) -> Option<&str> {
     })
 }
 
-#[cfg(any(feature = "binary", feature = "json"))]
 pub(crate) fn struct_bounds_strings(
     struct_: &Struct,
     bound_name: &str,
@@ -123,7 +118,6 @@ pub(crate) fn struct_bounds_strings(
     (generic_w_bounds, generic_no_bounds)
 }
 
-#[cfg(any(feature = "binary", feature = "json"))]
 pub(crate) fn enum_bounds_strings(
     enum_: &Enum,
     bound_name: &str,


Test patch:
diff --git a/tests/bin.rs b/tests/bin.rs
index b7f0a14..a6ca33d 100644
--- a/tests/bin.rs
+++ b/tests/bin.rs
@@ -325,6 +325,43 @@ fn binary_crate() {
     assert!(test == test_deserialized);
 }
 
+#[test]
+fn generic_enum() {
+    #[derive(DeBin, SerBin, PartialEq, Debug)]
+    pub enum Foo<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        A,
+        B(T, String),
+        C { a: U, b: String },
+    }
+
+    #[derive(DeBin, SerBin, PartialEq, Debug)]
+    pub struct Bar<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        foo1: Foo<T, U>,
+        foo2: Foo<T, U>,
+        foo3: Foo<T, U>,
+    }
+
+    let test = Bar {
+        foo1: Foo::A,
+        foo2: Foo::B(1, "asd".to_string()),
+        foo3: Foo::C {
+            a: 2,
+            b: "qwe".to_string(),
+        },
+    };
+    let bytes = SerBin::serialize_bin(&test);
+    let test_deserialized = DeBin::deserialize_bin(&bytes).unwrap();
+    assert!(test == test_deserialized);
+}
+
 #[cfg(feature = "std")]
 #[test]
 fn std_time() {
diff --git a/tests/json.rs b/tests/json.rs
index a99c5fa..5aa8a4a 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -1201,6 +1201,60 @@ fn json_crate() {
     assert_eq!(test.c, None);
 }
 
+#[test]
+fn generic_enum() {
+    #[derive(DeJson, SerJson, PartialEq, Debug)]
+    pub enum Foo<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        A,
+        B(T, String),
+        C { a: U, b: String },
+    }
+
+    #[derive(DeJson, SerJson, PartialEq, Debug)]
+    pub struct Bar<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        foo1: Foo<T, U>,
+        foo2: Foo<T, U>,
+        foo3: Foo<T, U>,
+    }
+
+    let json = r#"
+       {
+          "foo1": "A",
+          "foo2": { "B": [ 1, "asd" ] },
+          "foo3": { "C": { "a": 2, "b": "qwe" } }
+       }
+    "#;
+
+    let test: Bar<i32, u64> = DeJson::deserialize_json(json).unwrap();
+    assert_eq!(test.foo1, Foo::A);
+    assert_eq!(test.foo2, Foo::B(1, "asd".to_string()));
+    assert_eq!(
+        test.foo3,
+        Foo::C {
+            a: 2,
+            b: "qwe".to_string()
+        }
+    );
+    let bytes = SerJson::serialize_json(&test);
+    assert_eq!(
+        bytes
+            .lines()
+            .map(|l| l.trim().replace(' ', ""))
+            .collect::<String>(),
+        json.lines()
+            .map(|l| l.trim().replace(' ', ""))
+            .collect::<String>()
+    );
+}
+
 #[cfg(feature = "std")]
 #[test]
 fn std_time() {
diff --git a/tests/ron.rs b/tests/ron.rs
index 146e58a..a7b3ac3 100644
--- a/tests/ron.rs
+++ b/tests/ron.rs
@@ -710,6 +710,51 @@ fn no_whitespace_when_serialized() {
     }
 }
 
+#[test]
+fn generic_enum() {
+    #[derive(DeRon, PartialEq, Debug)]
+    pub enum Foo<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        A,
+        B(T, String),
+        C { a: U, b: String },
+    }
+
+    #[derive(DeRon, PartialEq, Debug)]
+    pub struct Bar<T, U>
+    where
+        T: Copy,
+        U: Clone,
+    {
+        foo1: Foo<T, U>,
+        foo2: Foo<T, U>,
+        foo3: Foo<T, U>,
+    }
+
+    let ron = r#"
+       (
+          foo1: A,
+          foo2: B(1, "asd"),
+          foo3: C(a: 2, b: "qwe"),
+       )
+    "#;
+
+    let test: Bar<i32, u64> = DeRon::deserialize_ron(ron).unwrap();
+
+    assert_eq!(test.foo1, Foo::A);
+    assert_eq!(test.foo2, Foo::B(1, "asd".to_string()));
+    assert_eq!(
+        test.foo3,
+        Foo::C {
+            a: 2,
+            b: "qwe".to_string()
+        }
+    );
+}
+
 #[cfg(feature = "std")]
 #[test]
 fn std_time() {