Repository: Aleph-Alpha/ts-rs
Pull request #161 (2023-08-07 12:52:57)

properly implement all 3 unit types


Patch:
diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs
index e244ebe..7a94e21 100644
--- a/macros/src/types/mod.rs
+++ b/macros/src/types/mod.rs
@@ -26,14 +26,14 @@ fn type_def(
     let name = attr.rename.clone().unwrap_or_else(|| to_ts_ident(ident));
     match fields {
         Fields::Named(named) => match named.named.len() {
-            0 => unit::unit(attr, &name),
+            0 => unit::empty_object(attr, &name),
             _ => named::named(attr, &name, named, generics),
         },
         Fields::Unnamed(unnamed) => match unnamed.unnamed.len() {
-            0 => unit::unit(attr, &name),
+            0 => unit::empty_array(attr, &name),
             1 => newtype::newtype(attr, &name, unnamed, generics),
             _ => tuple::tuple(attr, &name, unnamed, generics),
         },
-        Fields::Unit => unit::unit(attr, &name),
+        Fields::Unit => unit::null(attr, &name),
     }
 }
diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs
index e30de76..8786124 100644
--- a/macros/src/types/named.rs
+++ b/macros/src/types/named.rs
@@ -35,7 +35,7 @@ pub(crate) fn named(
         )?;
     }
 
-    let fields = quote!([#(#formatted_fields),*].join(" "));
+    let fields = quote!(<[String]>::join(&[#(#formatted_fields),*], " "));
     let generic_args = format_generics(&mut dependencies, generics);
 
     Ok(DerivedTS {
diff --git a/macros/src/types/unit.rs b/macros/src/types/unit.rs
index 9068600..1c31d99 100644
--- a/macros/src/types/unit.rs
+++ b/macros/src/types/unit.rs
@@ -3,14 +3,36 @@ use syn::Result;
 
 use crate::{attr::StructAttr, deps::Dependencies, DerivedTS};
 
-pub(crate) fn unit(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
-    if attr.rename_all.is_some() {
-        syn_err!("`rename_all` is not applicable to unit structs");
-    }
+pub(crate) fn empty_object(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
+    check_attributes(attr)?;
 
-    if attr.tag.is_some() {
-        syn_err!("`tag` is not applicable to unit structs");
-    }
+    Ok(DerivedTS {
+        inline: quote!("Record<string, never>".to_owned()),
+        decl: quote!(format!("type {} = Record<string, never>;", #name)),
+        inline_flattened: None,
+        name: name.to_owned(),
+        dependencies: Dependencies::default(),
+        export: attr.export,
+        export_to: attr.export_to.clone(),
+    })
+}
+
+pub(crate) fn empty_array(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
+    check_attributes(attr)?;
+
+    Ok(DerivedTS {
+        inline: quote!("never[]".to_owned()),
+        decl: quote!(format!("type {} = never[];", #name)),
+        inline_flattened: None,
+        name: name.to_owned(),
+        dependencies: Dependencies::default(),
+        export: attr.export,
+        export_to: attr.export_to.clone(),
+    })
+}
+
+pub(crate) fn null(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
+    check_attributes(attr)?;
 
     Ok(DerivedTS {
         inline: quote!("null".to_owned()),
@@ -22,3 +44,15 @@ pub(crate) fn unit(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
         export_to: attr.export_to.clone(),
     })
 }
+
+fn check_attributes(attr: &StructAttr) -> Result<()> {
+    if attr.rename_all.is_some() {
+        syn_err!("`rename_all` is not applicable to unit structs");
+    }
+
+    if attr.tag.is_some() {
+        syn_err!("`tag` is not applicable to unit structs");
+    }
+
+    Ok(())
+}
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index 1eaf5df..b9f7639 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -145,7 +145,6 @@ use std::{
     },
     ops::{Range, RangeInclusive},
     path::{Path, PathBuf},
-    time::Duration,
 };
 
 pub use ts_rs_macros::TS;


Test patch:
diff --git a/ts-rs/tests/unit.rs b/ts-rs/tests/unit.rs
index b452e29..db2cdba 100644
--- a/ts-rs/tests/unit.rs
+++ b/ts-rs/tests/unit.rs
@@ -1,17 +1,22 @@
 use ts_rs::TS;
 
+// serde_json serializes this to `null`, so it's TS type is `null` as well.
 #[derive(TS)]
 struct Unit;
 
+// serde_json serializes this to `{}`.
+// The TS type best describing an empty object is `Record<string, never>`.
 #[derive(TS)]
 struct Unit2 {}
 
+// serde_json serializes this to `[]`.
+// The TS type best describing an empty array is `never[]`.
 #[derive(TS)]
 struct Unit3();
 
 #[test]
 fn test() {
     assert_eq!("type Unit = null;", Unit::decl());
-    assert_eq!("type Unit2 = null;", Unit2::decl());
-    assert_eq!("type Unit3 = null;", Unit3::decl());
+    assert_eq!("type Unit2 = Record<string, never>;", Unit2::decl());
+    assert_eq!("type Unit3 = never[];", Unit3::decl());
 }