Repository: Aleph-Alpha/ts-rs
Pull request #306 (2024-04-11 15:17:21)

Recusion Limit - Round 2
## Goal

Determine when recusion limit errors still happen, reproduce and possibly fix them

## Changes
- Remove `TypeList`, move `TypeVisitor` to `lib.rs`
  - `dependency_types() -> TypeList` got changed to `visit_dependencies(v: &mut impl TypeVisitor)`
  - `generics() -> TypeList` got changed to `visit_generics(v: &mut impl TypeVisitor)`
  - codegen was adjusted accordingly

This is again a *technically* breaking change for anyone interacting with the `TS` trait directly.

## Checklist

- [x] I have followed the steps listed in the [Contributing guide](https://github.com/Aleph-Alpha/ts-rs/blob/main/CONTRIBUTING.md).
- [x] If necessary, I have added documentation related to the changes made.
- [x] I have added or updated the tests related to the changes made.


NyxCode: The "chain" of types `struct T000(T001)`, `struct T001(T002)`, ..., `struct T082(T083)`, `struct T083` manages to still run into the error.  

If there are only 83 instead of 84 types, then everything works, so that seems to be the limit currently.

```
error: reached the recursion limit while instantiating `<((), (PhantomData<T083>,)) as TypeList>::for_each::<...>`                                                                                                                                     
   --> C:\Users\morit\Coding\ts-rs\ts-rs\src\typelist.rs:102:9
    |
102 |         self.0.for_each(v);
    |         ^^^^^^^^^^^^^^^^^^
    |
note: `<(A, B) as TypeList>::for_each` defined here
   --> C:\Users\morit\Coding\ts-rs\ts-rs\src\typelist.rs:101:5
    |
101 |     fn for_each(self, v: &mut impl TypeVisitor) {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: the full type name has been written to 'C:\Users\morit\Coding\ts-rs\target\debug\deps\recursion_limit-57ec16690c4b1d02.long-type.txt'

error: could not compile `ts-rs` (test "recursion_limit") due to 1 previous error
```
NyxCode: New codegen for 
```rs
#[derive(TS)]
#[ts(export, export_to = "generics/")]
struct Generic<T>
where
    T: TS,
{
    value: T,
    values: Vec<T>,
}
```
looks like this:
```rs
fn visit_generics(v: &mut impl  TypeVisitor) where Self: 'static, {
    v.visit::<T>();
    <T as TS>::visit_generics(v);
}
fn visit_dependencies(v: &mut impl  TypeVisitor) where Self: 'static, {
    <T as  TS>::visit_generics(v);
    <Vec<T> as  TS>::visit_generics(v);
    v.visit::<T>();
    v.visit::<Vec<T>>();
}
```
NyxCode: Originally, I thought that having a `TypeList` would be nice. But it turned out that the only thing we ever do with it is to call `.for_each`.  
These changes basically put the `for_each` method directly into `TS`. `T::visit_dependencies(v)` is the same as `T::dependency_types().for_each(v)`, and `T::visit_generics(v)` is the same as `T::generics().for_each(v)`.
NyxCode: The `Generic` struct above nicely illustrates why it was easier to just get rid of `TypeList`.  
With the old `generics`, I wanted to generate
```rs
fn generics() -> impl TypeList where Self: 'static {
    struct List;
    impl TypeList for List {
        fn for_each(&self, v: &mut impl TypeVisitor) {
            v.visit::<T>();
            T::generics().for_each(v);
        }
    }
    List
}
```

Here, the `v.visit::<T>()` fails to compile, since the type parameter `T` can't be used within the `impl TypeList`. The workaround for this would be kinda complicated.
escritorio-gustavo: Due to this PR removing trait functions that return `impl Trait`, if we make `parse_attrs` and `parse_serde_attrs` `pub(crate)` instead of just `pub`, it can lower the MSRV all the way down to 1.63.0!
escritorio-gustavo: https://github.com/Aleph-Alpha/ts-rs/blob/d1223739632db2382eb419dc67e8cdac95d6430b/macros/src/utils.rs#L100
```suggestion
pub(crate) fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result<A> 
```

https://github.com/Aleph-Alpha/ts-rs/blob/d1223739632db2382eb419dc67e8cdac95d6430b/macros/src/utils.rs#L116
```suggestion
pub(crate) fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute]) -> Serde<A> 
```

https://github.com/Aleph-Alpha/ts-rs/blob/d1223739632db2382eb419dc67e8cdac95d6430b/ts-rs/Cargo.toml#L18
```suggestion
rust-version = "1.63.0" 
```
NyxCode: Merging this now. Not yet sure if we should publish a 9.0, or wait for the CLI. 
I guess publishing more often doesn't hurt, right?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb01191..1bc7f5e 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 623ca0b..6d019ae 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 e38c96c..ef61084 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 9e1f40a..845d2af 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 51511d3..e4f7b1b 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<A>
+pub(crate) fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result<A>
 where
     A: TryFrom<&'a Attribute, Error = Error> + Attr,
 {
@@ -113,7 +113,7 @@ where
 /// Parse all `#[serde(..)]` attributes from the given slice.
 #[cfg(feature = "serde-compat")]
 #[allow(unused)]
-pub fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute]) -> Serde<A>
+pub(crate) fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute]) -> Serde<A>
 where
     A: Attr,
     Serde<A>: TryFrom<&'a Attribute, Error = Error>,
diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml
index 27a6661..ae3eba8 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 07d35df..5043418 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 d1da7c8..a422c59 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<Dependency> = vec![];
         struct Visit<'a>(&'a mut Vec<Dependency>);
         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<T: TS + 'static + ?Sized>(&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)]
@@ -630,11 +633,14 @@ macro_rules! impl_tuples {
             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>,), T::generics())
+                T::visit_generics(v);
+                v.visit::<T>();
             }
             fn decl() -> String { panic!("wrapper type cannot be declared") }
             fn decl_concrete() -> String { panic!("wrapper type cannot be declared") }
@@ -683,17 +691,17 @@ macro_rules! impl_shadow {
             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 dependency_types() -> impl $crate::typelist::TypeList
+            fn visit_dependencies(v: &mut impl $crate::TypeVisitor)
             where
-                Self: 'static
+                Self: 'static,
             {
-                <$s as $crate::TS>::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 as $crate::TS>::generics()
+                <$s as $crate::TS>::visit_generics(v);
             }
             fn decl() -> String { <$s as $crate::TS>::decl() }
             fn decl_concrete() -> String { <$s as $crate::TS>::decl_concrete() }
@@ -713,18 +721,19 @@ impl<T: TS> TS for Option<T> {
         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>()
+        T::visit_generics(v);
+        v.visit::<T>();
     }
 
     fn decl() -> String {
@@ -751,18 +760,22 @@ impl<T: TS, E: TS> TS for Result<T, E> {
         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::<T>().extend(E::generics()).push::<E>()
+        T::visit_generics(v);
+        v.visit::<T>();
+        E::visit_generics(v);
+        v.visit::<E>();
     }
 
     fn decl() -> String {
@@ -793,18 +806,19 @@ impl<T: TS> TS for Vec<T> {
         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>()
+        T::visit_generics(v);
+        v.visit::<T>();
     }
 
     fn decl() -> String {
@@ -846,18 +860,19 @@ impl<T: TS, const N: usize> 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>()
+        T::visit_generics(v);
+        v.visit::<T>();
     }
 
     fn decl() -> String {
@@ -888,18 +903,22 @@ impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
         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::<K>().extend(V::generics()).push::<V>()
+        K::visit_generics(v);
+        v.visit::<K>();
+        V::visit_generics(v);
+        v.visit::<V>();
     }
 
     fn decl() -> String {
@@ -921,18 +940,19 @@ impl<I: TS> TS for Range<I> {
         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>()
+        I::visit_generics(v);
+        v.visit::<I>();
     }
 
     fn decl() -> String {
diff --git a/ts-rs/src/typelist.rs b/ts-rs/src/typelist.rs
deleted file mode 100644
index 123bf52..0000000
--- 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<T: TS + 'static + ?Sized>(&mut self) {
-///       println!("{}", T::name());
-///   }
-/// }
-///
-/// # fn primitives() -> impl TypeList {
-/// #     let signed = ().push::<i8>().push::<i16>().push::<i32>().push::<i64>();
-/// #     let unsigned = ().push::<u8>().push::<u16>().push::<u32>().push::<u64>();
-/// #     ().push::<char>()
-/// #       .push::<bool>()
-/// #       .extend(signed)
-/// #       .extend(unsigned)
-/// # }
-/// // fn primitives() -> impl TypeList { ... }
-/// primitives().for_each(&mut Visit);
-/// ```
-pub trait TypeVisitor: Sized {
-    fn visit<T: TS + 'static + ?Sized>(&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::<i8>().push::<i16>().push::<i32>().push::<i64>();
-///     let unsigned = ().push::<u8>().push::<u16>().push::<u32>().push::<u64>();
-///     ().push::<char>()
-///       .push::<bool>()
-///       .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<T>,)` is a [`TypeList`]
-/// - For every two [`TypeList`]s `A` and `B`, `(A, B)` is a [`TypeList`]
-pub trait TypeList: Copy + Clone {
-    fn push<T: TS + 'static + ?Sized>(self) -> impl TypeList {
-        (self, (PhantomData::<T>,))
-    }
-    fn extend(self, l: impl TypeList) -> impl TypeList {
-        (self, l)
-    }
-
-    fn contains<C: Sized + 'static>(self) -> bool;
-    fn for_each(self, v: &mut impl TypeVisitor);
-}
-
-impl TypeList for () {
-    fn contains<C: Sized>(self) -> bool {
-        false
-    }
-    fn for_each(self, _: &mut impl TypeVisitor) {}
-}
-
-impl<T> TypeList for (PhantomData<T>,)
-where
-    T: TS + 'static + ?Sized,
-{
-    fn contains<C: Sized + 'static>(self) -> bool {
-        TypeId::of::<C>() == TypeId::of::<T>()
-    }
-
-    fn for_each(self, v: &mut impl TypeVisitor) {
-        v.visit::<T>();
-    }
-}
-
-impl<A, B> TypeList for (A, B)
-where
-    A: TypeList,
-    B: TypeList,
-{
-    fn contains<C: Sized + 'static>(self) -> bool {
-        self.0.contains::<C>() || self.1.contains::<C>()
-    }
-
-    fn for_each(self, v: &mut impl TypeVisitor) {
-        self.0.for_each(v);
-        self.1.for_each(v);
-    }
-}


Test patch:
diff --git a/ts-rs/tests/integration/issue_308.rs b/ts-rs/tests/integration/issue_308.rs
index 6e2faf5..41fc5e7 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<Dependency> { 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<Path>) -> 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 0000000..bc286ba
--- /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 62bbeab..6eeef9f 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 5406120..16e1a2d 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 88ecef6..9d85c91 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<T> {
+    a: (T, T),
+    b: (T, T),
+}
+
+#[derive(TS)]
+#[ts(export, export_to = "tuple/")]
+struct TupleWithDependencies(Dep1, Dep2, Dep4<Dep3>);
+
+#[test]
+fn tuple_with_dependencies() {
+    assert_eq!(
+        "type TupleWithDependencies = [Dep1, Dep2, Dep4<Dep3>];",
+        TupleWithDependencies::decl()
+    );
+}
+
+#[derive(TS)]
+#[ts(export, export_to = "tuple/")]
+struct StructWithTuples {
+    a: (Dep1, Dep1),
+    b: (Dep2, Dep2),
+    c: (Dep4<Dep3>, Dep4<Dep3>),
+}
+
+#[test]
+fn struct_with_tuples() {
+    assert_eq!(
+        "type StructWithTuples = { \
+            a: [Dep1, Dep1], \
+            b: [Dep2, Dep2], \
+            c: [Dep4<Dep3>, Dep4<Dep3>], \
+        };",
+        StructWithTuples::decl()
+    );
+}