Repository: not-fl3/nanoserde
Pull request #129 (2025-03-02 22:52:06)

Cleanup and optimize derive macro code generation
I've been getting some clippy warnings when using `DeJson`, so I've cleaned up some unnecessary destructuring and replaced it with more fitting functions. I also found some `if` statements that were mutually exclusive and turned them into `if/else` statements.

knickish: I think this is looking pretty good now. However, I'd like to wait for @not-fl3 to have time to publish the existing version on `crates.io` before merging this. Will be able to test this patch more easily at that point, and want to avoid adding more to the release at this point
not-fl3: > I think this is looking pretty good now. However, I'd like to wait for @not-fl3 to have time to publish the existing version on `crates.io` before merging this. Will be able to test this patch more easily at that point, and want to avoid adding more to the release at this point

Sure, on it!

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 95f424b..fb739b9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nanoserde"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["makepad <info@makepad.nl>", "Fedor <not.fl3@gmail.com>"]
 license = "MIT OR Apache-2.0"
 description = """Serialization library with zero dependencies.
@@ -20,4 +20,4 @@ toml = []
 std = []
 
 [dependencies]
-nanoserde-derive = { path = "derive", version = "=0.2.0", optional = true }
+nanoserde-derive = { path = "derive", version = "=0.2.1", optional = true }
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index e5bc311..b53cc4d 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nanoserde-derive"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Makepad <info@makepad.nl>", "Fedor <not.fl3@gmail.com>"]
 edition = "2021"
 rust-version = "1.81.0"
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
index 56c79ae..7bb3506 100644
--- a/derive/src/parse.rs
+++ b/derive/src/parse.rs
@@ -1391,7 +1391,7 @@ fn next_generic<T: Iterator<Item = TokenTree> + Clone>(
 fn get_all_bounds<T: Iterator<Item = TokenTree> + Clone>(source: &mut Peekable<T>) -> Vec<Generic> {
     let mut ret = Vec::new();
     let mut already = BTreeSet::new();
-    if source.peek().map_or(false, |x| x.to_string() == "<") {
+    if source.peek().is_some_and(|x| x.to_string() == "<") {
         source.next();
     } else {
         return ret;
diff --git a/derive/src/serde_json.rs b/derive/src/serde_json.rs
index c4e97c6..f77baed 100644
--- a/derive/src/serde_json.rs
+++ b/derive/src/serde_json.rs
@@ -151,9 +151,11 @@ pub fn derive_de_json_named(
         let default_val = if let Some(v) = field_attr_default {
             if let Some(mut val) = v {
                 if field.ty.base() == "String"
-                    || field.ty.wraps.as_ref().map_or(false, |wrapped| {
-                        wrapped.iter().any(|ty| ty.base() == "String")
-                    })
+                    || field
+                        .ty
+                        .wraps
+                        .as_ref()
+                        .is_some_and(|wrapped| wrapped.iter().any(|ty| ty.base() == "String"))
                 {
                     val = format!("\"{}\".to_string()", val)
                 }
@@ -188,23 +190,21 @@ pub fn derive_de_json_named(
         };
 
         if !skip {
-            if field.ty.base() == "Option" {
-                unwraps.push(format!(
-                    "{{if let Some(t) = {} {{ {} }} else {{ {} }} }}",
-                    localvar,
-                    proxified_t,
-                    default_val.unwrap_or_else(|| String::from("None"))
-                ));
-            } else if container_attr_default || default_val.is_some() {
-                unwraps.push(format!(
-                    "{{if let Some(t) = {} {{ {} }} else {{ {} }} }}",
-                    localvar,
-                    proxified_t,
-                    default_val.unwrap_or_else(|| String::from("Default::default()"))
-                ));
+            if field.ty.base() == "Option" || container_attr_default || default_val.is_some() {
+                if let Some(default_val) = default_val {
+                    unwraps.push(format!(
+                        "{}.map_or_else(|| {}, |t| {})",
+                        localvar, default_val, proxified_t,
+                    ));
+                } else {
+                    unwraps.push(format!(
+                        "{}.map(|t| {}).unwrap_or_default()",
+                        localvar, proxified_t,
+                    ));
+                }
             } else {
                 unwraps.push(format!(
-                    "{{if let Some(t) = {} {{ {} }} else {{return Err(s.err_nf(\"{}\"))}} }}",
+                    "{}.map(|t| {}).ok_or_else(|| s.err_nf(\"{}\"))?",
                     localvar, proxified_t, struct_fieldname
                 ));
             }
@@ -223,7 +223,7 @@ pub fn derive_de_json_named(
         l!(r, "let mut {} = None;", local_var);
     }
     l!(r, "s.curly_open(i) ?;");
-    l!(r, "while let Some(_) = s.next_str() {");
+    l!(r, "while s.next_str().is_some() {");
 
     if !json_field_names.is_empty() {
         l!(r, "match AsRef::<str>::as_ref(&s.strbuf) {");
@@ -244,7 +244,7 @@ pub fn derive_de_json_named(
         l!(r, "_ => {s.next_colon(i)?; s.whole_field(i)?; }");
         l!(r, "}");
     }
-    l!(r, "s.eat_comma_curly(i) ?");
+    l!(r, "s.eat_comma_curly(i) ?;");
     l!(r, "}");
     l!(r, "s.curly_close(i) ?;");
     l!(r, "{} {{", name);
diff --git a/derive/src/serde_ron.rs b/derive/src/serde_ron.rs
index 44f5118..870a08e 100644
--- a/derive/src/serde_ron.rs
+++ b/derive/src/serde_ron.rs
@@ -145,8 +145,7 @@ pub fn derive_de_ron_named(
             if let Some(mut val) = v {
                 if field.ty.base() == "String" {
                     val = format!("\"{}\".to_string()", val)
-                }
-                if field.ty.base() == "Option" {
+                } else if field.ty.base() == "Option" {
                     val = format!("Some({})", val);
                 }
                 Some(val)
@@ -166,39 +165,15 @@ pub fn derive_de_ron_named(
         let skip = crate::shared::attrs_skip(&field.attributes);
 
         if !skip {
-            if field.ty.base() == "Option" {
-                unwraps.push(format!(
-                    "{{
-                        if let Some(t) = {} {{
-                            t
-                        }} else {{
-                            {}
-                        }}
-                    }}",
-                    localvar,
-                    default_val.unwrap_or_else(|| String::from("None"))
-                ));
-            } else if container_attr_default || default_val.is_some() {
-                unwraps.push(format!(
-                    "{{
-                        if let Some(t) = {} {{
-                            t
-                        }} else {{
-                            {}
-                        }}
-                    }}",
-                    localvar,
-                    default_val.unwrap_or_else(|| String::from("Default::default()"))
-                ));
+            if field.ty.base() == "Option" || container_attr_default || default_val.is_some() {
+                if let Some(default_val) = default_val {
+                    unwraps.push(format!("{}.unwrap_or_else(|| {})", localvar, default_val));
+                } else {
+                    unwraps.push(format!("{}.unwrap_or_default()", localvar));
+                }
             } else {
                 unwraps.push(format!(
-                    "{{
-                        if let Some(t) = {} {{
-                            t
-                        }} else {{
-                            return Err(s.err_nf(\"{}\"))
-                        }}
-                    }}",
+                    "{}.ok_or_else(|| s.err_nf(\"{}\"))?",
                     localvar, struct_fieldname
                 ));
             }
@@ -260,7 +235,7 @@ pub fn derive_de_ron_named(
         "{{
             {}
             s.paren_open(i)?;
-            while let Some(_) = s.next_ident() {{
+            while s.next_ident().is_some() {{
                 {}
                 s.eat_comma_paren(i)?;
             }};
@@ -391,7 +366,7 @@ pub fn derive_ser_ron_enum(enum_: &Enum, crate_name: &str) -> TokenStream {
                     let name = format!("f{}", index);
                     l!(inner, "{}.ser_ron(d, s);", name);
                     if index != last {
-                        l!(inner, "s.out.push_str(\", \");")
+                        l!(inner, "s.out.push_str(\",\");")
                     }
                     names.push(name);
                 }
diff --git a/src/serde_ron.rs b/src/serde_ron.rs
index f6cc080..994313e 100644
--- a/src/serde_ron.rs
+++ b/src/serde_ron.rs
@@ -13,10 +13,10 @@ pub struct SerRonState {
 }
 
 impl SerRonState {
-    pub fn indent(&mut self, d: usize) {
-        for _ in 0..d {
-            self.out.push_str("    ");
-        }
+    pub fn indent(&mut self, _d: usize) {
+        // for _ in 0..d {
+        //     self.out.push_str("    ");
+        // }
     }
 
     pub fn field(&mut self, d: usize, field: &str) {
@@ -26,11 +26,11 @@ impl SerRonState {
     }
 
     pub fn conl(&mut self) {
-        self.out.push_str(",\n")
+        self.out.push(',')
     }
 
     pub fn st_pre(&mut self) {
-        self.out.push_str("(\n");
+        self.out.push('(');
     }
 
     pub fn st_post(&mut self, d: usize) {
@@ -870,7 +870,7 @@ where
     T: SerRon,
 {
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
-        s.out.push_str("[\n");
+        s.out.push('[');
         for item in self {
             s.indent(d + 1);
             item.ser_ron(d + 1, s);
@@ -1021,7 +1021,7 @@ where
         for (index, item) in self.iter().enumerate() {
             item.ser_ron(d + 1, s);
             if index != last {
-                s.out.push_str(", ");
+                s.out.push(',');
             }
         }
         s.out.push(')');
@@ -1112,7 +1112,7 @@ where
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
         s.out.push('(');
         self.0.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.1.ser_ron(d, s);
         s.out.push(')');
     }
@@ -1140,9 +1140,9 @@ where
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
         s.out.push('(');
         self.0.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.1.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.2.ser_ron(d, s);
         s.out.push(')');
     }
@@ -1176,11 +1176,11 @@ where
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
         s.out.push('(');
         self.0.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.1.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.2.ser_ron(d, s);
-        s.out.push_str(", ");
+        s.out.push(',');
         self.3.ser_ron(d, s);
         s.out.push(')');
     }
@@ -1213,7 +1213,7 @@ where
     V: SerRon,
 {
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
-        s.out.push_str("{\n");
+        s.out.push('{');
         for (k, v) in self {
             s.indent(d + 1);
             k.ser_ron(d + 1, s);
@@ -1253,7 +1253,7 @@ where
     V: SerRon,
 {
     fn ser_ron(&self, d: usize, s: &mut SerRonState) {
-        s.out.push_str("{\n");
+        s.out.push('{');
         for (k, v) in self {
             s.indent(d + 1);
             k.ser_ron(d + 1, s);


Test patch:
diff --git a/tests/ron.rs b/tests/ron.rs
index cfb95fa..395ba27 100644
--- a/tests/ron.rs
+++ b/tests/ron.rs
@@ -7,7 +7,7 @@ use std::{
 };
 
 #[cfg(feature = "std")]
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 
 #[test]
 fn ron_de() {
@@ -450,7 +450,7 @@ fn de_ser_enum() {
         foo3: Fud,
     }
 
-    let ron = "(\n    foo1:A,\n    foo2:B,\n    foo3:C,\n)";
+    let ron = "(foo1:A,foo2:B,foo3:C,)";
 
     let data = Bar {
         foo1: Fud::A,
@@ -481,7 +481,7 @@ fn ser_enum_complex() {
         foo3: Foo,
     }
 
-    let ron = "(\n    foo1:A,\n    foo2:B(1, \"asd\"),\n    foo3:C(\n        a:2,\n        b:\"qwe\",\n    ),\n)";
+    let ron = "(foo1:A,foo2:B(1,\"asd\"),foo3:C(a:2,b:\"qwe\",),)";
 
     let data = Bar {
         foo1: Foo::A,
@@ -662,3 +662,50 @@ fn ron_crate() {
     assert_eq!(test.c, None);
     assert_eq!(test.d.unwrap(), "hello");
 }
+
+#[test]
+fn no_whitespace_when_serialized() {
+    // A vec of every type which implements `SerRon`. Actual values were picked arbitrarily.
+    let mut rons: Vec<Box<dyn SerRon>> = vec![
+        Box::new(()),
+        Box::new((0, 1.0)),
+        Box::new((0, 1.0, [2])),
+        Box::new((0, 1.0, [2], false)),
+        Box::new((0..5).collect::<BTreeSet<i32>>()),
+        Box::new((0..5).map(|x| (x, true)).collect::<BTreeMap<i32, bool>>()),
+        Box::new(Box::new(12_usize)),
+        Box::new((0..5).collect::<LinkedList<i32>>()),
+        Box::new(Some(false)),
+        Box::new(None::<bool>),
+        Box::new(String::from("a_string!")),
+        Box::new(vec![false, true, false]),
+        Box::new([true, false, true]),
+        Box::new(true),
+        Box::new(-32.0_f32),
+        Box::new(64.0_f64),
+        Box::new(-8_i8),
+        Box::new(-16_i16),
+        Box::new(-32_i32),
+        Box::new(-64_i64),
+        Box::new(8_u8),
+        Box::new(16_u16),
+        Box::new(32_u32),
+        Box::new(64_u64),
+        Box::new(usize::MAX),
+    ];
+
+    #[cfg(feature = "std")]
+    {
+        rons.push(Box::new((0..5).collect::<HashSet<i32>>()));
+        rons.push(Box::new(
+            (0..5).map(|x| (x, true)).collect::<HashMap<i32, bool>>(),
+        ));
+    }
+
+    for ron in rons {
+        let serialized = ron.serialize_ron();
+        let no_whitespace = serialized.chars().all(|char| !char.is_whitespace());
+
+        assert!(no_whitespace);
+    }
+}