diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 11b87a830..7e56924e3 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -97,6 +97,7 @@ impl DerivedTS { .unwrap_or_else(TokenStream::new); let impl_start = generate_impl(&rust_ty, &generics); + let generic_idents = generics.type_params().map(|x| x.ident.clone()); quote! { #impl_start { const EXPORT_TO: Option<&'static str> = Some(#export_to); @@ -108,7 +109,11 @@ impl DerivedTS { #decl } fn name() -> String { - #name.to_owned() + let generics: Vec = vec![#(<#generic_idents>::name()),*]; + match generics.is_empty() { + true => #name.to_owned(), + false => format!("{}<{}>", #name, generics.join(", ")), + } } fn inline() -> String { #inline diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs index 7da2c48ba..c1b658fe4 100644 --- a/macros/src/types/generics.rs +++ b/macros/src/types/generics.rs @@ -64,15 +64,12 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi } return quote!( - match <#generic_ident>::inline().as_str() { - // When exporting a generic, the default type used is `()`, - // which gives "null" when calling `.name()`. In this case, we - // want to preserve the type param's identifier as the name used + match <#generic_ident>::name().as_str() { "null" => #generic_ident_str.to_owned(), - // If name is not "null", a type has been provided, so we use its - // name instead - x => x.to_owned() + x => { + x.to_owned() + } } ); } diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index efd26d569..d852b267d 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -298,7 +298,10 @@ pub trait TS { /// Name of this type in TypeScript, with type arguments. fn name_with_type_args(args: Vec) -> String { - format!("{}<{}>", Self::name(), args.join(", ")) + match Self::name().split_once('<') { + None => Self::name(), + Some(x) => format!("{}<{}>", x.0, args.join(", ")) + } } /// Formats this types definition in TypeScript, e.g `{ user_id: number }`. @@ -397,7 +400,10 @@ impl Dependency { let exported_to = T::get_export_to()?; Some(Dependency { type_id: TypeId::of::(), - ts_name: T::name(), + ts_name: match T::name() { + x if !x.contains('<') => x, + x => x.split('<').next().unwrap().to_owned() + }, exported_to, }) } @@ -535,7 +541,7 @@ impl TS for Result { impl TS for Vec { fn name() -> String { - "Array".to_owned() + format!("Array<{}>", T::name()) } fn inline() -> String { @@ -561,7 +567,13 @@ impl TS for [T; N] { return Vec::::name(); } - "[]".to_owned() + format!( + "[{}]", + (0..N) + .map(|_| T::name()) + .collect::>() + .join(", ") + ) } fn name_with_type_args(args: Vec) -> String { @@ -610,7 +622,7 @@ impl TS for [T; N] { impl TS for HashMap { fn name() -> String { - "Record".to_owned() + format!("Record<{}, {}>", K::name(), V::name()) } fn name_with_type_args(args: Vec) -> String { diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 048f8473c..1f9791be6 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -256,7 +256,7 @@ fn default() { // #[ts(inline)] // xi2: X } - assert_eq!(Y::decl(), "type Y = { a1: A, a2: A, }") + assert_eq!(Y::decl(), "type Y = { a1: A, a2: A, }") } #[test] @@ -349,3 +349,36 @@ fn deeply_nested() { }" ); } + +#[test] +fn inline_generic_enum() { + #[derive(TS)] + struct OtherType(T); + + #[derive(TS)] + struct SomeType(OtherType); + + #[derive(TS)] + enum MyEnum { + VariantA(A), + VariantB(B) + } + + #[derive(TS)] + struct Parent { + e: MyEnum, + #[ts(inline)] + e1: MyEnum> + } + + // This fails! + // The #[ts(inline)] seems to inline recursively, so not only the definition of `MyEnum`, but + // also the definition of `SomeType`. + assert_eq!( + Parent::decl(), + "type Parent = { \ + e: MyEnum, \ + e1: { \"VariantA\": number } | { \"VariantB\": SomeType }, \ + }" + ); +} diff --git a/ts-rs/tests/type_alias.rs b/ts-rs/tests/type_alias.rs new file mode 100644 index 000000000..52f47b7b1 --- /dev/null +++ b/ts-rs/tests/type_alias.rs @@ -0,0 +1,32 @@ +#![allow(dead_code)] + +use std::collections::HashMap; +use ts_rs::TS; + +type TypeAlias = HashMap; + +#[derive(TS)] +enum Enum { + A(TypeAlias), + B(HashMap), +} + +#[derive(TS)] +struct Struct { + a: TypeAlias, + b: HashMap +} + +#[test] +fn type_alias() { + assert_eq!( + Enum::decl(), + r#"type Enum = { "A": Record } | { "B": Record };"# + ); + + assert_eq!( + Struct::decl(), + "type Struct = { a: Record, b: Record, }" + ); +} +