diff --git a/CHANGELOG.md b/CHANGELOG.md index fb011913..1bc7f5ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - `#[serde(with = "...")]` requires the use of `#[ts(as = "...")]` or `#[ts(type = "...")]` ([#280](https://github.com/Aleph-Alpha/ts-rs/pull/280)) - Fix incompatibility with serde for `snake_case`, `kebab-case` and `SCREAMING_SNAKE_CASE` ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298)) - `#[ts(rename_all = "...")]` no longer accepts variations in the string's casing, dashes and underscores to make behavior consistent with serde ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298)) +- Remove `TypeList`, and replace `TS::dependency_types`/`TS::generics` with `TS::visit_dependencies`/`TS::visit_generics`. + This finally resolves "overflow evaluating the requirement", "reached the recursion limit" errors. + Also, compile times should benefit. This is a technically breaking change for those interacting with the `TS` trait + directly. For those just using `#[derive(TS)]` and `#[ts(...)]`, nothing changes! ### Features diff --git a/README.md b/README.md index 623ca0b2..6d019aee 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,6 @@ Feel free to open an issue, discuss using GitHub discussions or open a PR. [See CONTRIBUTING.md](https://github.com/Aleph-Alpha/ts-rs/blob/main/CONTRIBUTING.md) ### MSRV -The Minimum Supported Rust Version for this crate is 1.75.0 +The Minimum Supported Rust Version for this crate is 1.63.0 License: MIT diff --git a/macros/src/deps.rs b/macros/src/deps.rs index e38c96c5..ef61084a 100644 --- a/macros/src/deps.rs +++ b/macros/src/deps.rs @@ -79,13 +79,11 @@ impl Dependencies { impl ToTokens for Dependencies { fn to_tokens(&self, tokens: &mut TokenStream) { - let crate_rename = &self.crate_rename; let lines = self.dependencies.iter(); - tokens.extend(quote![{ - use #crate_rename::typelist::TypeList; - ()#(#lines)* - }]); + tokens.extend(quote![ + #(#lines;)* + ]); } } @@ -93,12 +91,12 @@ impl ToTokens for Dependency { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(match self { Dependency::Transitive { crate_rename, ty } => { - quote![.extend(<#ty as #crate_rename::TS>::dependency_types())] + quote![<#ty as #crate_rename::TS>::visit_dependencies(v)] } Dependency::Generics { crate_rename, ty } => { - quote![.extend(<#ty as #crate_rename::TS>::generics())] + quote![<#ty as #crate_rename::TS>::visit_generics(v)] } - Dependency::Type(ty) => quote![.push::<#ty>()], + Dependency::Type(ty) => quote![v.visit::<#ty>()], }); } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9e1f40aa..845d2af3 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -92,8 +92,7 @@ impl DerivedTS { #generics_fn #output_path_fn - #[allow(clippy::unused_unit)] - fn dependency_types() -> impl #crate_rename::typelist::TypeList + fn visit_dependencies(v: &mut impl #crate_rename::TypeVisitor) where Self: 'static, { @@ -195,15 +194,18 @@ impl DerivedTS { let generics = generics .type_params() .filter(|ty| !self.concrete.contains_key(&ty.ident)) - .map(|TypeParam { ident, .. }| quote![.push::<#ident>().extend(<#ident as #crate_rename::TS>::generics())]); + .map(|TypeParam { ident, .. }| { + quote![ + v.visit::<#ident>(); + <#ident as #crate_rename::TS>::visit_generics(v); + ] + }); quote! { - #[allow(clippy::unused_unit)] - fn generics() -> impl #crate_rename::typelist::TypeList + fn visit_generics(v: &mut impl #crate_rename::TypeVisitor) where Self: 'static, { - use #crate_rename::typelist::TypeList; - ()#(#generics)* + #(#generics)* } } } diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 93dfbd58..07ce47a2 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -97,7 +97,7 @@ pub fn raw_name_to_ts_field(value: String) -> String { } /// Parse all `#[ts(..)]` attributes from the given slice. -pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result +pub(crate) fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result where A: TryFrom<&'a Attribute, Error = Error> + Attr, { diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml index 27a66611..ae3eba83 100644 --- a/ts-rs/Cargo.toml +++ b/ts-rs/Cargo.toml @@ -15,7 +15,7 @@ categories = [ "web-programming", ] readme = "../README.md" -rust-version = "1.75.0" +rust-version = "1.63.0" [features] chrono-impl = ["chrono"] diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 07d35dfc..50434187 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -35,10 +35,7 @@ mod recursive_export { use std::{any::TypeId, collections::HashSet, path::Path}; use super::export_into; - use crate::{ - typelist::{TypeList, TypeVisitor}, - ExportError, TS, - }; + use crate::{ExportError, TypeVisitor, TS}; /// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute within the given /// base directory. @@ -85,7 +82,7 @@ mod recursive_export { out_dir, error: None, }; - T::dependency_types().for_each(&mut visitor); + T::visit_dependencies(&mut visitor); if let Some(e) = visitor.error { Err(e) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 5eb7d0e3..a422c594 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -114,7 +114,7 @@ //! [See CONTRIBUTING.md](https://github.com/Aleph-Alpha/ts-rs/blob/main/CONTRIBUTING.md) //! //! ## MSRV -//! The Minimum Supported Rust Version for this crate is 1.75.0 +//! The Minimum Supported Rust Version for this crate is 1.63.0 use std::{ any::TypeId, @@ -131,14 +131,12 @@ use std::{ pub use ts_rs_macros::TS; pub use crate::export::ExportError; -use crate::typelist::TypeList; #[cfg(feature = "chrono-impl")] mod chrono; mod export; #[cfg(feature = "serde-json-impl")] mod serde_json; -pub mod typelist; /// A type which can be represented in TypeScript. /// Most of the time, you'd want to derive this trait instead of implementing it manually. @@ -418,20 +416,19 @@ pub trait TS { /// This function will panic if the type cannot be inlined. fn inline() -> String; - /// Flatten an type declaration. + /// Flatten a type declaration. /// This function will panic if the type cannot be flattened. fn inline_flattened() -> String; - /// Returns a [`TypeList`] of all types on which this type depends. - fn dependency_types() -> impl TypeList + /// Iterates over all dependency of this type. + fn visit_dependencies(_: &mut impl TypeVisitor) where Self: 'static, { } - /// Returns a [`TypeList`] containing all generic parameters of this type. - /// If this type is not generic, this will return an empty [`TypeList`]. - fn generics() -> impl TypeList + /// Iterates over all type parameters of this type. + fn visit_generics(_: &mut impl TypeVisitor) where Self: 'static, { @@ -442,8 +439,6 @@ pub trait TS { where Self: 'static, { - use crate::typelist::TypeVisitor; - let mut deps: Vec = vec![]; struct Visit<'a>(&'a mut Vec); impl<'a> TypeVisitor for Visit<'a> { @@ -453,7 +448,7 @@ pub trait TS { } } } - Self::dependency_types().for_each(&mut Visit(&mut deps)); + Self::visit_dependencies(&mut Visit(&mut deps)); deps } @@ -530,7 +525,7 @@ pub trait TS { } /// Manually generate bindings for this type, returning a [`String`]. - /// This function does not format the output, even if the `format` feature is enabled. TODO + /// This function does not format the output, even if the `format` feature is enabled. /// /// # Automatic Exporting /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be @@ -578,6 +573,14 @@ pub trait TS { } } +/// A visitor used to iterate over all dependencies or generics of a type. +/// When an instance of [`TypeVisitor`] is passed to [`TS::visit_dependencies`] or +/// [`TS::visit_generics`], the [`TypeVisitor::visit`] method will be invoked for every dependency +/// or generic parameter respectively. +pub trait TypeVisitor: Sized { + fn visit(&mut self); +} + /// A typescript type which is depended upon by other types. /// This information is required for generating the correct import statements. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -625,16 +628,19 @@ macro_rules! impl_tuples { impl<$($i: TS),*> TS for ($($i,)*) { type WithoutGenerics = (Dummy, ); fn name() -> String { - format!("[{}]", [$($i::name()),*].join(", ")) + format!("[{}]", [$(<$i as $crate::TS>::name()),*].join(", ")) } fn inline() -> String { panic!("tuple cannot be inlined!"); } - fn dependency_types() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static { - ()$(.push::<$i>())* + $( + v.visit::<$i>(); + <$i>::visit_generics(v); + )* } fn inline_flattened() -> String { panic!("tuple cannot be flattened") } fn decl() -> String { panic!("tuple cannot be declared") } @@ -656,17 +662,19 @@ macro_rules! impl_wrapper { fn name() -> String { T::name() } fn inline() -> String { T::inline() } fn inline_flattened() -> String { T::inline_flattened() } - fn dependency_types() -> impl $crate::typelist::TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where - Self: 'static + Self: 'static, { - T::dependency_types() + T::visit_dependencies(v); } - fn generics() -> impl $crate::typelist::TypeList + + fn visit_generics(v: &mut impl TypeVisitor) where - Self: 'static + Self: 'static, { - ((std::marker::PhantomData::,), T::generics()) + T::visit_generics(v); + v.visit::(); } fn decl() -> String { panic!("wrapper type cannot be declared") } fn decl_concrete() -> String { panic!("wrapper type cannot be declared") } @@ -678,26 +686,26 @@ macro_rules! impl_wrapper { macro_rules! impl_shadow { (as $s:ty: $($impl:tt)*) => { $($impl)* { - type WithoutGenerics = <$s as TS>::WithoutGenerics; - fn ident() -> String { <$s>::ident() } - fn name() -> String { <$s>::name() } - fn inline() -> String { <$s>::inline() } - fn inline_flattened() -> String { <$s>::inline_flattened() } - fn dependency_types() -> impl $crate::typelist::TypeList + type WithoutGenerics = <$s as $crate::TS>::WithoutGenerics; + fn ident() -> String { <$s as $crate::TS>::ident() } + fn name() -> String { <$s as $crate::TS>::name() } + fn inline() -> String { <$s as $crate::TS>::inline() } + fn inline_flattened() -> String { <$s as $crate::TS>::inline_flattened() } + fn visit_dependencies(v: &mut impl $crate::TypeVisitor) where - Self: 'static + Self: 'static, { - <$s>::dependency_types() + <$s as $crate::TS>::visit_dependencies(v); } - fn generics() -> impl $crate::typelist::TypeList + fn visit_generics(v: &mut impl $crate::TypeVisitor) where - Self: 'static + Self: 'static, { - <$s>::generics() + <$s as $crate::TS>::visit_generics(v); } - fn decl() -> String { <$s>::decl() } - fn decl_concrete() -> String { <$s>::decl_concrete() } - fn output_path() -> Option<&'static std::path::Path> { <$s>::output_path() } + fn decl() -> String { <$s as $crate::TS>::decl() } + fn decl_concrete() -> String { <$s as $crate::TS>::decl_concrete() } + fn output_path() -> Option<&'static std::path::Path> { <$s as $crate::TS>::output_path() } } }; } @@ -713,18 +721,19 @@ impl TS for Option { format!("{} | null", T::inline()) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - T::dependency_types() + T::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - T::generics().push::() + T::visit_generics(v); + v.visit::(); } fn decl() -> String { @@ -751,18 +760,22 @@ impl TS for Result { format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline()) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - T::dependency_types().extend(E::dependency_types()) + T::visit_dependencies(v); + E::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - T::generics().push::().extend(E::generics()).push::() + T::visit_generics(v); + v.visit::(); + E::visit_generics(v); + v.visit::(); } fn decl() -> String { @@ -793,18 +806,19 @@ impl TS for Vec { format!("Array<{}>", T::inline()) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - T::dependency_types() + T::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - T::generics().push::() + T::visit_generics(v); + v.visit::(); } fn decl() -> String { @@ -846,18 +860,19 @@ impl TS for [T; N] { ) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - T::dependency_types() + T::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - T::generics().push::() + T::visit_generics(v); + v.visit::(); } fn decl() -> String { @@ -888,18 +903,22 @@ impl TS for HashMap { format!("{{ [key: {}]: {} }}", K::inline(), V::inline()) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - K::dependency_types().extend(V::dependency_types()) + K::visit_dependencies(v); + V::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - K::generics().push::().extend(V::generics()).push::() + K::visit_generics(v); + v.visit::(); + V::visit_generics(v); + v.visit::(); } fn decl() -> String { @@ -921,18 +940,19 @@ impl TS for Range { format!("{{ start: {}, end: {}, }}", I::name(), I::name()) } - fn dependency_types() -> impl TypeList + fn visit_dependencies(v: &mut impl TypeVisitor) where Self: 'static, { - I::dependency_types() + I::visit_dependencies(v); } - fn generics() -> impl TypeList + fn visit_generics(v: &mut impl TypeVisitor) where Self: 'static, { - I::generics().push::() + I::visit_generics(v); + v.visit::(); } fn decl() -> String { diff --git a/ts-rs/src/typelist.rs b/ts-rs/src/typelist.rs deleted file mode 100644 index 123bf521..00000000 --- a/ts-rs/src/typelist.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! A simple zero-sized collection of types. - -use std::{any::TypeId, marker::PhantomData}; - -use crate::TS; - -/// A visitor used to iterate over a [`TypeList`]. -/// -/// Example: -/// ``` -/// # use ts_rs::TS; -/// # use ts_rs::typelist::{TypeList, TypeVisitor}; -/// struct Visit; -/// impl TypeVisitor for Visit { -/// fn visit(&mut self) { -/// println!("{}", T::name()); -/// } -/// } -/// -/// # fn primitives() -> impl TypeList { -/// # let signed = ().push::().push::().push::().push::(); -/// # let unsigned = ().push::().push::().push::().push::(); -/// # ().push::() -/// # .push::() -/// # .extend(signed) -/// # .extend(unsigned) -/// # } -/// // fn primitives() -> impl TypeList { ... } -/// primitives().for_each(&mut Visit); -/// ``` -pub trait TypeVisitor: Sized { - fn visit(&mut self); -} - -/// A list containing types implementing `TS + 'static + ?Sized`. -/// -/// To construct a [`TypeList`], start with the empty list, which is the unit type `()`, and -/// repeatedly call [`TypeList::push`] or [`TypeList::extend`] on it. -/// -/// Example: -/// ``` -/// # use ts_rs::typelist::TypeList; -/// fn primitives() -> impl TypeList { -/// let signed = ().push::().push::().push::().push::(); -/// let unsigned = ().push::().push::().push::().push::(); -/// ().push::() -/// .push::() -/// .extend(signed) -/// .extend(unsigned) -/// } -/// ``` -/// -/// The only way to get access to the types contained in a [`TypeList`] is to iterate over it by -/// creating a visitor implementing [`TypeVisitor`] and calling [`TypeList::for_each`]. -/// -/// Under the hood, [`TypeList`] is recursively defined as follows: -/// - The unit type `()` is the empty [`TypeList`] -/// - For every `T: TS`, `(PhantomData,)` is a [`TypeList`] -/// - For every two [`TypeList`]s `A` and `B`, `(A, B)` is a [`TypeList`] -pub trait TypeList: Copy + Clone { - fn push(self) -> impl TypeList { - (self, (PhantomData::,)) - } - fn extend(self, l: impl TypeList) -> impl TypeList { - (self, l) - } - - fn contains(self) -> bool; - fn for_each(self, v: &mut impl TypeVisitor); -} - -impl TypeList for () { - fn contains(self) -> bool { - false - } - fn for_each(self, _: &mut impl TypeVisitor) {} -} - -impl TypeList for (PhantomData,) -where - T: TS + 'static + ?Sized, -{ - fn contains(self) -> bool { - TypeId::of::() == TypeId::of::() - } - - fn for_each(self, v: &mut impl TypeVisitor) { - v.visit::(); - } -} - -impl TypeList for (A, B) -where - A: TypeList, - B: TypeList, -{ - fn contains(self) -> bool { - self.0.contains::() || self.1.contains::() - } - - fn for_each(self, v: &mut impl TypeVisitor) { - self.0.for_each(v); - self.1.for_each(v); - } -} diff --git a/ts-rs/tests/integration/issue_308.rs b/ts-rs/tests/integration/issue_308.rs index 6e2faf5b..41fc5e79 100644 --- a/ts-rs/tests/integration/issue_308.rs +++ b/ts-rs/tests/integration/issue_308.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use ts_rs::{typelist::TypeList, Dependency, ExportError, TS}; +use ts_rs::{Dependency, ExportError, TypeVisitor, TS}; #[rustfmt::skip] trait Malicious { @@ -13,9 +13,9 @@ trait Malicious { fn name() -> String { unimplemented!() } fn inline() -> String { unimplemented!() } fn inline_flattened() -> String { unimplemented!() } - fn dependency_types() -> impl TypeList {} - fn generics() -> impl TypeList {} fn dependencies() -> Vec { unimplemented!() } + fn visit_dependencies(_: &mut impl TypeVisitor) { unimplemented!() } + fn visit_generics(_: &mut impl TypeVisitor) { unimplemented!() } fn export() -> Result<(), ExportError> { unimplemented!() } fn export_all() -> Result<(), ExportError> { unimplemented!() } fn export_all_to(out_dir: impl AsRef) -> Result<(), ExportError> { unimplemented!() } diff --git a/ts-rs/tests/integration/issue_317.rs b/ts-rs/tests/integration/issue_317.rs new file mode 100644 index 00000000..bc286baa --- /dev/null +++ b/ts-rs/tests/integration/issue_317.rs @@ -0,0 +1,18 @@ +use ts_rs::TS; + +#[derive(TS)] +#[ts(export_to = "issue_317/")] +struct VariantId(u32); + +#[derive(TS)] +#[ts(export_to = "issue_317/")] +struct VariantOverview { + id: u32, + name: String +} + +#[derive(TS)] +#[ts(export, export_to = "issue_317/")] +struct Container { + variants: Vec<(VariantId, VariantOverview)>, +} diff --git a/ts-rs/tests/integration/main.rs b/ts-rs/tests/integration/main.rs index 62bbeab0..6eeef9f8 100644 --- a/ts-rs/tests/integration/main.rs +++ b/ts-rs/tests/integration/main.rs @@ -59,3 +59,4 @@ mod union_with_data; mod union_with_internal_tag; mod unit; mod r#unsized; +mod issue_317; diff --git a/ts-rs/tests/integration/recursion_limit.rs b/ts-rs/tests/integration/recursion_limit.rs index 5406120c..16e1a2d2 100644 --- a/ts-rs/tests/integration/recursion_limit.rs +++ b/ts-rs/tests/integration/recursion_limit.rs @@ -1,9 +1,6 @@ use std::any::TypeId; -use ts_rs::{ - typelist::{TypeList, TypeVisitor}, - TS, -}; +use ts_rs::{TypeVisitor, TS}; #[rustfmt::skip] #[allow(clippy::all)] @@ -77,7 +74,36 @@ fn very_big_enum() { } let mut visitor = Visitor(false); - VeryBigEnum::dependency_types().for_each(&mut visitor); + VeryBigEnum::visit_dependencies(&mut visitor); assert!(visitor.0, "there must be at least one dependency"); } + +macro_rules! generate_types { + ($a:ident, $b:ident $($t:tt)*) => { + #[derive(TS)] + #[ts(export, export_to = "very_big_types/")] + struct $a($b); + generate_types!($b $($t)*); + }; + ($a:ident) => { + #[derive(TS)] + #[ts(export, export_to = "very_big_types/")] + struct $a; + } +} + +// This generates +// `#[derive(TS)] struct T000(T001)` +// `#[derive(TS)] struct T001(T002)` +// ... +// `#[derive(TS)] struct T082(T083)` +// `#[derive(TS)] struct T083;` +generate_types!( + T000, T001, T002, T003, T004, T005, T006, T007, T008, T009, T010, T011, T012, T013, T014, T015, + T016, T017, T018, T019, T020, T021, T022, T023, T024, T025, T026, T027, T028, T029, T030, T031, + T032, T033, T034, T035, T036, T037, T038, T039, T040, T041, T042, T043, T044, T045, T046, T047, + T048, T049, T050, T051, T052, T053, T054, T055, T056, T057, T058, T059, T060, T061, T062, T063, + T064, T065, T066, T067, T068, T069, T070, T071, T072, T073, T074, T075, T076, T077, T078, T079, + T080, T081, T082, T083 +); diff --git a/ts-rs/tests/integration/tuple.rs b/ts-rs/tests/integration/tuple.rs index 88ecef61..9d85c919 100644 --- a/ts-rs/tests/integration/tuple.rs +++ b/ts-rs/tests/integration/tuple.rs @@ -23,12 +23,65 @@ fn test_newtype() { assert_eq!("type NewType = string;", NewType::decl()); } +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct TupleNewType(String, i32, (i32, i32)); + #[test] fn test_tuple_newtype() { - #[derive(TS)] - struct TupleNewType(String, i32, (i32, i32)); assert_eq!( "type TupleNewType = [string, number, [number, number]];", TupleNewType::decl() ) } + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct Dep1; + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct Dep2; + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct Dep3; + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct Dep4 { + a: (T, T), + b: (T, T), +} + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct TupleWithDependencies(Dep1, Dep2, Dep4); + +#[test] +fn tuple_with_dependencies() { + assert_eq!( + "type TupleWithDependencies = [Dep1, Dep2, Dep4];", + TupleWithDependencies::decl() + ); +} + +#[derive(TS)] +#[ts(export, export_to = "tuple/")] +struct StructWithTuples { + a: (Dep1, Dep1), + b: (Dep2, Dep2), + c: (Dep4, Dep4), +} + +#[test] +fn struct_with_tuples() { + assert_eq!( + "type StructWithTuples = { \ + a: [Dep1, Dep1], \ + b: [Dep2, Dep2], \ + c: [Dep4, Dep4], \ + };", + StructWithTuples::decl() + ); +}