diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index 0f719199e..31b52e44e 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -1,4 +1,4 @@ -use syn::{Attribute, Ident, Result}; +use syn::{Attribute, Ident, Result, Path}; use crate::{ attr::{parse_assign_inflection, parse_assign_str, Inflection}, @@ -11,6 +11,7 @@ pub struct EnumAttr { pub rename: Option, pub export_to: Option, pub export: bool, + pub unit_type: Option, tag: Option, untagged: bool, content: Option, @@ -59,6 +60,7 @@ impl EnumAttr { untagged, export_to, export, + unit_type, }: EnumAttr, ) { self.rename = self.rename.take().or(rename); @@ -68,6 +70,7 @@ impl EnumAttr { self.content = self.content.take().or(content); self.export = self.export || export; self.export_to = self.export_to.take().or(export_to); + self.unit_type = self.unit_type.take().or(unit_type); } } @@ -75,6 +78,9 @@ impl_parse! { EnumAttr(input, out) { "rename" => out.rename = Some(parse_assign_str(input)?), "rename_all" => out.rename_all = Some(parse_assign_inflection(input)?), + "unit_type" => out.unit_type = Some( + parse_assign_str(input).and_then(|x| syn::parse_str::(&x))? + ), "export_to" => out.export_to = Some(parse_assign_str(input)?), "export" => out.export = true } diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index e3539e2e1..e54072cfa 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use syn::{Attribute, Ident, Result}; +use syn::{Attribute, Ident, Result, Path}; use crate::{ attr::{parse_assign_str, Inflection, VariantAttr}, @@ -14,6 +14,7 @@ pub struct StructAttr { pub export_to: Option, pub export: bool, pub tag: Option, + pub unit_type: Option, } #[cfg(feature = "serde-compat")] @@ -37,6 +38,7 @@ impl StructAttr { export, export_to, tag, + unit_type, }: StructAttr, ) { self.rename = self.rename.take().or(rename); @@ -44,6 +46,7 @@ impl StructAttr { self.export_to = self.export_to.take().or(export_to); self.export = self.export || export; self.tag = self.tag.take().or(tag); + self.unit_type = self.unit_type.take().or(unit_type); } } @@ -66,6 +69,9 @@ impl_parse! { StructAttr(input, out) { "rename" => out.rename = Some(parse_assign_str(input)?), "rename_all" => out.rename_all = Some(parse_assign_str(input).and_then(Inflection::try_from)?), + "unit_type" => out.unit_type = Some( + parse_assign_str(input).and_then(|x| syn::parse_str::(&x))? + ), "export" => out.export = true, "export_to" => out.export_to = Some(parse_assign_str(input)?) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 452d9922e..c88cd1983 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,10 +2,10 @@ #![deny(unused)] use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse_quote, spanned::Spanned, ConstParam, GenericParam, Generics, Item, LifetimeParam, Result, - TypeParam, WhereClause, + TypeParam, WhereClause, Path, }; use crate::deps::Dependencies; @@ -22,6 +22,7 @@ struct DerivedTS { decl: TokenStream, inline_flattened: Option, dependencies: Dependencies, + unit_type: Option, export: bool, export_to: Option, @@ -34,7 +35,7 @@ impl DerivedTS { .params .iter() .filter(|param| matches!(param, GenericParam::Type(_))) - .map(|_| quote! { () }); + .map(|_| self.unit_type.as_ref().map(|x| x.to_token_stream()).unwrap_or(quote!{ () })); let ty = quote!(<#rust_ty<#(#generic_params),*> as ts_rs::TS>); Some(quote! { diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index a4738223e..7da81230a 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -29,6 +29,7 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result { decl: quote!("type {} = never;"), inline_flattened: None, dependencies: Dependencies::default(), + unit_type: enum_attr.unit_type, export: enum_attr.export, export_to: enum_attr.export_to, }); @@ -55,6 +56,7 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result { )), dependencies, name, + unit_type: enum_attr.unit_type, export: enum_attr.export, export_to: enum_attr.export_to, }) @@ -195,6 +197,7 @@ fn empty_enum(name: impl Into, enum_attr: EnumAttr) -> DerivedTS { name, inline_flattened: None, dependencies: Dependencies::default(), + unit_type: enum_attr.unit_type, export: enum_attr.export, export_to: enum_attr.export_to, } diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs index 7da2c48ba..7b077d5aa 100644 --- a/macros/src/types/generics.rs +++ b/macros/src/types/generics.rs @@ -59,10 +59,6 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi let generic_ident = generic.ident.clone(); let generic_ident_str = generic_ident.to_string(); - if !generic.bounds.is_empty() { - return quote!(#generic_ident_str.to_owned()); - } - return quote!( match <#generic_ident>::inline().as_str() { // When exporting a generic, the default type used is `()`, diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index d61267b89..901f14107 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -56,6 +56,7 @@ pub(crate) fn named( inline_flattened: Some(quote!(format!("{{ {} }}", #fields))), name: name.to_owned(), dependencies, + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index f1038b6f4..5b00b1477 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -70,6 +70,7 @@ pub(crate) fn newtype( inline_flattened: None, name: name.to_owned(), dependencies, + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index bc98005b7..9245e1e36 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -47,6 +47,7 @@ pub(crate) fn tuple( inline_flattened: None, name: name.to_owned(), dependencies, + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) diff --git a/macros/src/types/unit.rs b/macros/src/types/unit.rs index 1c31d9982..c41a98ff0 100644 --- a/macros/src/types/unit.rs +++ b/macros/src/types/unit.rs @@ -12,6 +12,7 @@ pub(crate) fn empty_object(attr: &StructAttr, name: &str) -> Result { inline_flattened: None, name: name.to_owned(), dependencies: Dependencies::default(), + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) @@ -26,6 +27,7 @@ pub(crate) fn empty_array(attr: &StructAttr, name: &str) -> Result { inline_flattened: None, name: name.to_owned(), dependencies: Dependencies::default(), + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) @@ -40,6 +42,7 @@ pub(crate) fn null(attr: &StructAttr, name: &str) -> Result { inline_flattened: None, name: name.to_owned(), dependencies: Dependencies::default(), + unit_type: attr.unit_type.clone(), export: attr.export, export_to: attr.export_to.clone(), }) diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 7397caaed..562af9f98 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -9,6 +9,14 @@ use std::{ use ts_rs::TS; +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, TS)] +struct Unit; +impl ToString for Unit { + fn to_string(&self) -> String { + "".to_owned() + } +} + #[derive(TS)] struct Generic where @@ -170,12 +178,9 @@ fn inline() { } #[test] -#[ignore = "We haven't figured out how to inline generics with bounds yet"] -#[allow(unreachable_code)] fn inline_with_bounds() { - todo!("FIX ME: https://github.com/Aleph-Alpha/ts-rs/issues/214"); - #[derive(TS)] + #[ts(unit_type = "Unit")] struct Generic { t: T, } @@ -190,15 +195,11 @@ fn inline_with_bounds() { #[ts(flatten)] t: Generic, } - - assert_eq!( - Generic::<&'static str>::decl(), - "type Generic = { t: T, }" - ); - // ^^^^^^^^^^^^ Replace with something else + + assert_eq!(Generic::::decl(), "type Generic = { t: T, }"); assert_eq!( Container::decl(), - "type Container = { g: Generic, gi: { t: string, }, t: number, }" // Actual output: { g: Generic, gi: { t: T, }, t: T, } + "type Container = { g: Generic, gi: { t: string, }, t: number, }" ); } @@ -262,16 +263,19 @@ fn default() { #[test] fn trait_bounds() { #[derive(TS)] + #[ts(unit_type = "Unit")] struct A { t: T, } - assert_eq!(A::::decl(), "type A = { t: T, }"); + assert_eq!(A::::decl(), "type A = { t: T, }"); #[derive(TS)] + #[ts(unit_type = "Unit")] struct B(T); - assert_eq!(B::<&'static str>::decl(), "type B = T;"); + assert_eq!(B::::decl(), "type B = T;"); #[derive(TS)] + #[ts(unit_type = "Unit")] enum C { A { t: T }, B(T), @@ -279,18 +283,16 @@ fn trait_bounds() { D(T, K), } assert_eq!( - C::<&'static str, i32>::decl(), + C::::decl(), r#"type C = { "A": { t: T, } } | { "B": T } | "C" | { "D": [T, K] };"# ); #[derive(TS)] + #[ts(unit_type = "Unit")] struct D { t: [T; N], } - let ty = format!( - "type D = {{ t: [{}], }}", - "T, ".repeat(41).trim_end_matches(", ") - ); - assert_eq!(D::<&str, 41>::decl(), ty) + let ty = format!("type D = {{ t: [{}], }}", "T, ".repeat(41).trim_end_matches(", ")); + assert_eq!(D::::decl(), ty) }