From 495a6bfab27eeea07a99da72bc4b124da81b7fb4 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 12 Feb 2024 18:56:50 +0100 Subject: [PATCH 1/4] add test --- ts-rs/tests/generics.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 048f8473c..7089f951c 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -349,3 +349,33 @@ fn deeply_nested() { }" ); } + +#[test] +fn inline_generic_enum() { + #[derive(TS)] + struct SomeType(String); + + #[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 }, \ + }" + ); +} \ No newline at end of file From d5e5f994e599be8317b6ed4f535b6824d264bc3f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 14 Feb 2024 10:46:36 -0300 Subject: [PATCH 2/4] Change how T::name() and T::name_with_type_args() work --- macros/src/lib.rs | 7 ++++++- macros/src/types/generics.rs | 11 ++++------- ts-rs/src/lib.rs | 19 ++++++++++++++----- ts-rs/tests/generics.rs | 13 ++++++++----- 4 files changed, 32 insertions(+), 18 deletions(-) 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..8fdeed3e8 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -298,7 +298,7 @@ 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(", ")) + format!("{}<{}>", Self::name().split_once('<').unwrap().0, args.join(", ")) } /// Formats this types definition in TypeScript, e.g `{ user_id: number }`. @@ -397,7 +397,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 +538,7 @@ impl TS for Result { impl TS for Vec { fn name() -> String { - "Array".to_owned() + format!("Array<{}>", T::name()) } fn inline() -> String { @@ -561,7 +564,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 +619,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 7089f951c..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] @@ -353,7 +353,10 @@ fn deeply_nested() { #[test] fn inline_generic_enum() { #[derive(TS)] - struct SomeType(String); + struct OtherType(T); + + #[derive(TS)] + struct SomeType(OtherType); #[derive(TS)] enum MyEnum { @@ -365,7 +368,7 @@ fn inline_generic_enum() { struct Parent { e: MyEnum, #[ts(inline)] - e1: MyEnum + e1: MyEnum> } // This fails! @@ -375,7 +378,7 @@ fn inline_generic_enum() { Parent::decl(), "type Parent = { \ e: MyEnum, \ - e1: { \"VariantA\": number } | { \"VariantB\": SomeType }, \ + e1: { \"VariantA\": number } | { \"VariantB\": SomeType }, \ }" ); -} \ No newline at end of file +} From a252de5ac35caa30818a677abcbdce18a28432d1 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 14 Feb 2024 10:54:28 -0300 Subject: [PATCH 3/4] Get rid of unwrap --- ts-rs/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 8fdeed3e8..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().split_once('<').unwrap().0, 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 }`. From 0c1971dbc1f2b36d53d21cb778c72739b9667c42 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 15 Feb 2024 09:07:16 -0300 Subject: [PATCH 4/4] This also fixes type aliases! --- ts-rs/tests/type_alias.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ts-rs/tests/type_alias.rs 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, }" + ); +} +