Repository: not-fl3/nanoserde (Rust)

Issue:
Derive stuck on enum's unit variants that are followed by empty braces or not followed by a comma
When using the derive macros (SerJson, DeJson, SerBin, DeBin, SerRon, DeRon) on an enum, if the last of the variants it's an unit variant not followed by a comma, none of the macros will compile, giving an error message), this means this wouldn't compile:

`#[derive(SerJson, DeJson, SerBin, DeBin, SerRon, DeRon)]`
`enum Message {
    Goodbye,
    Greeting
}`

> message: not implemented: Unexpected type in enum: Type { ident: UnNamed, wraps: None, ref_type: None, as_other: None }

but this would:

`#[derive(SerJson, DeJson, SerBin, DeBin, SerRon, DeRon)]`
`enum Message {
    Goodbye,
    Greeting,
}`

Although if braces are open an unit variant, every macro except SerJson will compile (Where SerJson would give an error message as `message: attempt to subtract with overflow`) , meaning this compiles everything but SerJson:

`#[derive(SerJson, DeJson, SerBin, DeBin, SerRon, DeRon)]`
`enum Message {
    Goodbye,
    Greeting{}
}`

and this too:

`#[derive(SerJson, DeJson, SerBin, DeBin, SerRon, DeRon)]`
`enum Message {
    Goodbye{},
    Greeting,
}`

Also, thanks for this crate!


Discussion:
Thanks for writing this up, I will try to look into this this weekend.
Sorry for the delay, busy time of year. I'm unable to reproduce the first one (no trailing comma), I think that was addressed for #83. A new version hasn't been released for that yet, but should be soon. I'm investigating a fix for the second one.



Patch:
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
index e91a521..f027a75 100644
--- a/derive/src/parse.rs
+++ b/derive/src/parse.rs
@@ -551,8 +551,12 @@ pub fn _debug_current_token(source: &mut Peekable<impl Iterator<Item = TokenTree
 }
 
 pub fn next_lifetime<T: Iterator<Item = TokenTree>>(source: &mut Peekable<T>) -> Option<Lifetime> {
-    let Some (TokenTree::Punct(punct)) = source.peek() else { return None; };
-    let '\'' = punct.as_char() else { return None; };
+    let Some(TokenTree::Punct(punct)) = source.peek() else {
+        return None;
+    };
+    let '\'' = punct.as_char() else {
+        return None;
+    };
 
     let _ = source.next();
     Some(Lifetime {
@@ -592,7 +596,15 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
 
         let Some(_) = next_exact_punct(&mut source, ";") else {
             // This is an unbounded array, legal at end for unsized types
-            return Some(Type { ident: Category::Array { content_type: Box::new(next.clone()), len: None }, wraps: Some(vec![next]), ref_type: None, as_other: None })
+            return Some(Type {
+                ident: Category::Array {
+                    content_type: Box::new(next.clone()),
+                    len: None,
+                },
+                wraps: Some(vec![next]),
+                ref_type: None,
+                as_other: None,
+            });
         };
 
         //need to cover both the const generic and literal case
@@ -662,7 +674,10 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
             source: &mut Peekable<T>,
         ) -> Option<Type> {
             let mut tmp = source.clone();
-            let (Some(_), Some(_)) = ( next_exact_punct(&mut tmp, "-"),  next_exact_punct(&mut tmp, ">")) else {
+            let (Some(_), Some(_)) = (
+                next_exact_punct(&mut tmp, "-"),
+                next_exact_punct(&mut tmp, ">"),
+            ) else {
                 return None;
             };
             drop(tmp);
@@ -719,7 +734,7 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
         };
         let true = matches!(ident.to_string().as_str(), "fn" | "FnOnce" | "FnMut" | "Fn") else {
             return None;
-       };
+        };
         let tok_str = source.next().unwrap().to_string();
 
         match tok_str.as_str() {
@@ -770,10 +785,10 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
         source: &mut Peekable<T>,
     ) -> Option<Type> {
         let Some(TokenTree::Ident(ident)) = source.peek() else {
-            return None
+            return None;
         };
         let true = matches!(ident.to_string().as_str(), "impl" | "dyn") else {
-             return None;
+            return None;
         };
         match source.next().unwrap().to_string().as_str() {
             "impl" => {
@@ -841,7 +856,14 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
     };
 
     let None = next_exact_punct(source, "\'") else {
-        return Some(Type{ ident: Category::Lifetime { path: next_ident(source).expect("Need lifetime name") }, wraps: None, ref_type: None, as_other: None })
+        return Some(Type {
+            ident: Category::Lifetime {
+                path: next_ident(source).expect("Need lifetime name"),
+            },
+            wraps: None,
+            ref_type: None,
+            as_other: None,
+        });
     };
 
     let ref_type = match next_exact_punct(&mut source, "&") {
@@ -909,7 +931,10 @@ fn next_type<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
     let mut ty = next_ident(&mut source).unwrap_or_default();
     while let Some(TokenTree::Punct(_)) = source.peek() {
         let mut tmp = source.clone();
-        let (Some(_), Some(_)) = ( next_exact_punct(&mut tmp, ":"),  next_exact_punct(&mut tmp, ":")) else {
+        let (Some(_), Some(_)) = (
+            next_exact_punct(&mut tmp, ":"),
+            next_exact_punct(&mut tmp, ":"),
+        ) else {
             break;
         };
         drop(tmp);
@@ -1015,7 +1040,7 @@ fn next_attribute<T: Iterator<Item = TokenTree>>(
     // all attributes, even doc-comments, starts with "#"
     let next_attr_punct = next_punct(&mut source);
     let Some("#") = next_attr_punct.as_deref() else {
-        return None
+        return None;
     };
 
     let mut attr_group = next_group(&mut source)
@@ -1213,7 +1238,12 @@ fn next_enum<T: Iterator<Item = TokenTree> + Clone>(mut source: &mut Peekable<T>
         let ty = next_type(&mut body);
         let Some(ty) = ty else {
             variants.push(Field {
-                ty: Type { ident: Category::None, wraps: None, ref_type: None, as_other: None },
+                ty: Type {
+                    ident: Category::None,
+                    wraps: None,
+                    ref_type: None,
+                    as_other: None,
+                },
                 attributes,
                 vis: Visibility::Public,
                 field_name: Some(variant_name),
diff --git a/derive/src/serde_json.rs b/derive/src/serde_json.rs
index d6d1607..5caeda7 100644
--- a/derive/src/serde_json.rs
+++ b/derive/src/serde_json.rs
@@ -286,7 +286,7 @@ pub fn derive_ser_json_enum(enum_: &Enum) -> TokenStream {
             } => {
                 let mut items = String::new();
                 let mut field_names = vec![];
-                let last = contents.fields.len() - 1;
+                let last = contents.fields.len().saturating_sub(1);
                 for (index, field) in contents.fields.iter().enumerate() {
                     if let Some(name) = &&field.field_name {
                         let proxied_field = ser_proxy_guard(name, field);


Test patch:
diff --git a/tests/parse.rs b/tests/parse.rs
index fd26909..1f75dc9 100644
--- a/tests/parse.rs
+++ b/tests/parse.rs
@@ -9,3 +9,11 @@ fn test_trailing_comma() {
         A
     }
 }
+
+// https://github.com/not-fl3/nanoserde/issues/88
+#[test]
+fn test_empty_brackets() {
+    #[rustfmt::skip]
+    #[derive(SerJson, DeJson, SerBin, DeBin, SerRon, DeRon)]
+    enum Message { Goodbye, Greeting{} }
+}