From 3fa1d4bec1be2c52e2db65ebdeffc55d0ffa7256 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 15:47:17 +0100 Subject: [PATCH 1/7] implement POC --- macros/src/deps.rs | 41 +++++++++++-- macros/src/lib.rs | 10 ++++ ts-rs/src/lib.rs | 134 ++++++++++++++++++++++++++++++++++-------- ts-rs/src/typelist.rs | 47 +++++++++++++++ ts-rs/tests/test.rs | 42 +++++++++++++ 5 files changed, 243 insertions(+), 31 deletions(-) create mode 100644 ts-rs/src/typelist.rs create mode 100644 ts-rs/tests/test.rs diff --git a/macros/src/deps.rs b/macros/src/deps.rs index b28ce8b5f..f3958bd97 100644 --- a/macros/src/deps.rs +++ b/macros/src/deps.rs @@ -2,20 +2,33 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::Type; -#[derive(Default)] -pub struct Dependencies(Vec); +pub struct Dependencies { + v0: Vec, + v1: Vec +} + +impl Default for Dependencies { + fn default() -> Self { + Dependencies { + v0: Vec::new(), + v1: vec![quote![()]] + } + } +} impl Dependencies { /// Adds all dependencies from the given type pub fn append_from(&mut self, ty: &Type) { - self.0 + self.v1.push(quote![.extend(<#ty as ts_rs::TS>::dependency_types())]); + self.v0 .push(quote!(dependencies.append(&mut <#ty as ts_rs::TS>::dependencies());)); } /// Adds the given type if it's *not* transparent. /// If it is, all it's child dependencies are added instead. pub fn push_or_append_from(&mut self, ty: &Type) { - self.0.push(quote! { + self.v1.push(quote![.push::<#ty>()]); + self.v0.push(quote! { if <#ty as ts_rs::TS>::transparent() { dependencies.append(&mut <#ty as ts_rs::TS>::dependencies()); } else { @@ -27,15 +40,31 @@ impl Dependencies { } pub fn append(&mut self, other: Dependencies) { - self.0.push(quote! { + let other_v2 = other.to_tokens_v2(); + self.v1.push(quote![.extend(#other_v2)]); + self.v0.push(quote! { dependencies.append(&mut #other); }) } + + pub fn to_tokens_v2(&self) -> impl ToTokens + '_ { + struct ToTokensV2<'a>(&'a Dependencies); + impl<'a> ToTokens for ToTokensV2<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let lines = &self.0.v1; + tokens.extend(quote![{ + use ts_rs::typelist::TypeList; + #(#lines)* + }]) + } + } + ToTokensV2(self) + } } impl ToTokens for Dependencies { fn to_tokens(&self, tokens: &mut TokenStream) { - let dependencies = &self.0; + let dependencies = &self.v0; tokens.extend(quote! { { let mut dependencies = Vec::new(); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9b6639e96..e318ca838 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -81,6 +81,7 @@ impl DerivedTS { .unwrap_or_else(TokenStream::new); let impl_start = generate_impl(&rust_ty, &generics); + let dependencies_v2 = dependencies.to_tokens_v2(); quote! { #impl_start { const EXPORT_TO: Option<&'static str> = Some(#export_to); @@ -95,12 +96,21 @@ impl DerivedTS { #inline } #inline_flattened + fn dependencies() -> Vec where Self: 'static, { #dependencies } + + fn dependency_types() -> impl ts_rs::typelist::TypeList + where + Self: 'static, + { + #dependencies_v2 + } + fn transparent() -> bool { false } diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index d3a1f41ab..ac9897111 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -172,6 +172,7 @@ pub use crate::export::ExportError; #[cfg(feature = "chrono-impl")] mod chrono; mod export; +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. @@ -289,11 +290,34 @@ pub trait TS { panic!("{} cannot be flattened", Self::name()) } - /// Information about types this type depends on. - /// This is used for resolving imports when exporting to a file. + fn dependency_types() -> impl TypeList + where + Self: 'static { () } + fn dependencies() -> Vec - where - Self: 'static; + where + Self: 'static; + + //fn dependencies() -> Vec where Self: 'static { + // use crate::typelist::{TypeList, TypeVisitor}; + // + // let mut deps: Vec = vec![]; + // struct Visit<'a>(&'a mut Vec); + // impl<'a> TypeVisitor for Visit<'a> { + // fn visit(&mut self) { + // if let Some(dep) = Dependency::from_ty::() { + // self.0.push(dep); + // } + // } + // } + // Self::dependency_types().for_each(&mut Visit(&mut deps)); + // + // deps + //} + + fn _push_to(l: impl TypeList) -> impl TypeList where Self: 'static { + l.push::() + } /// `true` if this is a transparent type, e.g tuples or a list. /// This is used for resolving imports when using the `export!` macro. @@ -384,14 +408,20 @@ macro_rules! impl_tuples { fn inline() -> String { format!("[{}]", [$($i::inline()),*].join(", ")) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ()$(.push::<$i>())* + } fn dependencies() -> Vec where Self: 'static { [$( Dependency::from_ty::<$i>() ),*] - .into_iter() - .flatten() - .collect() + .into_iter() + .flatten() + .collect() } fn transparent() -> bool { true } } @@ -414,6 +444,12 @@ macro_rules! impl_wrapper { } fn inline() -> String { T::inline() } fn inline_flattened() -> String { T::inline_flattened() } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + T::dependency_types() + } fn dependencies() -> Vec where Self: 'static @@ -433,7 +469,13 @@ macro_rules! impl_shadow { fn name_with_type_args(args: Vec) -> String { <$s>::name_with_type_args(args) } fn inline() -> String { <$s>::inline() } fn inline_flattened() -> String { <$s>::inline_flattened() } - fn dependencies() -> Vec<$crate::Dependency> + fn dependency_types() -> impl TypeList + where + Self: 'static + { + <$s>::dependency_types() + } + fn dependencies() -> Vec where Self: 'static { @@ -463,11 +505,17 @@ impl TS for Option { format!("{} | null", T::inline()) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { - [Dependency::from_ty::()].into_iter().flatten().collect() + [Dependency::from_ty::()].into_iter().flatten().collect() } fn transparent() -> bool { @@ -482,9 +530,15 @@ impl TS for Result { fn inline() -> String { format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline()) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::(), Dependency::from_ty::()] .into_iter() @@ -505,13 +559,18 @@ impl TS for Vec { format!("Array<{}>", T::inline()) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::()].into_iter().flatten().collect() } - fn transparent() -> bool { true } @@ -557,9 +616,15 @@ impl TS for [T; N] { ) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::()].into_iter().flatten().collect() } @@ -588,9 +653,15 @@ impl TS for HashMap { format!("Record<{}, {}>", K::inline(), V::inline()) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::(), Dependency::from_ty::()] .into_iter() @@ -618,9 +689,15 @@ impl TS for Range { format!("{{ start: {}, end: {}, }}", &args[0], &args[0]) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::()].into_iter().flatten().collect() } @@ -645,9 +722,15 @@ impl TS for RangeInclusive { format!("{{ start: {}, end: {}, }}", &args[0], &args[0]) } + fn dependency_types() -> impl TypeList + where + Self: 'static + { + ().push::() + } fn dependencies() -> Vec - where - Self: 'static, + where + Self: 'static { [Dependency::from_ty::()].into_iter().flatten().collect() } @@ -657,7 +740,7 @@ impl TS for RangeInclusive { } } -impl_shadow!(as T: impl<'a, T: TS + ?Sized> TS for &T); +impl_shadow!(as T: impl TS for &T); impl_shadow!(as Vec: impl TS for HashSet); impl_shadow!(as Vec: impl TS for BTreeSet); impl_shadow!(as HashMap: impl TS for BTreeMap); @@ -727,3 +810,4 @@ impl_primitives! { } #[rustfmt::skip] pub(crate) use impl_primitives; +use crate::typelist::TypeList; diff --git a/ts-rs/src/typelist.rs b/ts-rs/src/typelist.rs new file mode 100644 index 000000000..1ad21286b --- /dev/null +++ b/ts-rs/src/typelist.rs @@ -0,0 +1,47 @@ +use std::any::TypeId; +use std::marker::PhantomData; + +use crate::TS; + +pub trait TypeVisitor: Sized { + fn visit(&mut self); +} + + +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); + } +} \ No newline at end of file diff --git a/ts-rs/tests/test.rs b/ts-rs/tests/test.rs new file mode 100644 index 000000000..f860ce7ed --- /dev/null +++ b/ts-rs/tests/test.rs @@ -0,0 +1,42 @@ +use std::collections::{BTreeSet, HashMap}; +use ts_rs::{Dependency, TS}; +use ts_rs::typelist::TypeList; +use ts_rs::typelist::TypeVisitor; + +fn get_deps() -> BTreeSet { + use ts_rs::typelist::{TypeList, TypeVisitor}; + + let mut deps = BTreeSet::new(); + struct Visit<'a>(&'a mut BTreeSet); + impl<'a> TypeVisitor for Visit<'a> { + fn visit(&mut self) { + if let Some(dep) = Dependency::from_ty::() { + self.0.insert(dep); + } + } + } + T::dependency_types().for_each(&mut Visit(&mut deps)); + + deps +} + +#[allow(dead_code)] +#[test] +fn test() { + #[derive(TS)] + struct A {} + + #[derive(TS)] + struct B {} + + #[derive(TS)] + struct X { + x: i32, + a: A, + a2: A, + map: HashMap, + } + + println!("{:?}", X::dependencies().into_iter().collect::>()); + println!("{:?}", get_deps::()); +} From 3e2b8cf095ae19ca11d8fc04f4a30e383b946c87 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 15:59:51 +0100 Subject: [PATCH 2/7] fix missing import --- ts-rs/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index ac9897111..bc5edb99b 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -469,13 +469,13 @@ macro_rules! impl_shadow { fn name_with_type_args(args: Vec) -> String { <$s>::name_with_type_args(args) } fn inline() -> String { <$s>::inline() } fn inline_flattened() -> String { <$s>::inline_flattened() } - fn dependency_types() -> impl TypeList + fn dependency_types() -> impl $crate::typelist::TypeList where Self: 'static { <$s>::dependency_types() } - fn dependencies() -> Vec + fn dependencies() -> Vec<$crate::Dependency> where Self: 'static { From 2375fbf50ab760eb41ee264f8efe35543a833293 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 17:03:56 +0100 Subject: [PATCH 3/7] remove old dependency implementation, fix warnings --- macros/src/deps.rs | 63 ++++----------------- macros/src/lib.rs | 11 +--- ts-rs/src/chrono.rs | 8 --- ts-rs/src/lib.rs | 121 ++++++++-------------------------------- ts-rs/tests/generics.rs | 1 + ts-rs/tests/ranges.rs | 4 +- ts-rs/tests/test.rs | 42 -------------- 7 files changed, 37 insertions(+), 213 deletions(-) delete mode 100644 ts-rs/tests/test.rs diff --git a/macros/src/deps.rs b/macros/src/deps.rs index f3958bd97..366ad824c 100644 --- a/macros/src/deps.rs +++ b/macros/src/deps.rs @@ -2,75 +2,32 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::Type; -pub struct Dependencies { - v0: Vec, - v1: Vec -} - -impl Default for Dependencies { - fn default() -> Self { - Dependencies { - v0: Vec::new(), - v1: vec![quote![()]] - } - } -} +#[derive(Default)] +pub struct Dependencies(Vec); impl Dependencies { /// Adds all dependencies from the given type pub fn append_from(&mut self, ty: &Type) { - self.v1.push(quote![.extend(<#ty as ts_rs::TS>::dependency_types())]); - self.v0 - .push(quote!(dependencies.append(&mut <#ty as ts_rs::TS>::dependencies());)); + self.0.push(quote![.extend(<#ty as ts_rs::TS>::dependency_types())]); } /// Adds the given type if it's *not* transparent. /// If it is, all it's child dependencies are added instead. pub fn push_or_append_from(&mut self, ty: &Type) { - self.v1.push(quote![.push::<#ty>()]); - self.v0.push(quote! { - if <#ty as ts_rs::TS>::transparent() { - dependencies.append(&mut <#ty as ts_rs::TS>::dependencies()); - } else { - if let Some(dep) = ts_rs::Dependency::from_ty::<#ty>() { - dependencies.push(dep); - } - } - }); + self.0.push(quote![.push::<#ty>()]); } pub fn append(&mut self, other: Dependencies) { - let other_v2 = other.to_tokens_v2(); - self.v1.push(quote![.extend(#other_v2)]); - self.v0.push(quote! { - dependencies.append(&mut #other); - }) - } - - pub fn to_tokens_v2(&self) -> impl ToTokens + '_ { - struct ToTokensV2<'a>(&'a Dependencies); - impl<'a> ToTokens for ToTokensV2<'a> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let lines = &self.0.v1; - tokens.extend(quote![{ - use ts_rs::typelist::TypeList; - #(#lines)* - }]) - } - } - ToTokensV2(self) + self.0.push(quote![.extend(#other)]); } } impl ToTokens for Dependencies { fn to_tokens(&self, tokens: &mut TokenStream) { - let dependencies = &self.v0; - tokens.extend(quote! { - { - let mut dependencies = Vec::new(); - #( #dependencies )* - dependencies - } - }) + let lines = &self.0; + tokens.extend(quote![{ + use ts_rs::typelist::TypeList; + ()#(#lines)* + }]) } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index e318ca838..452d9922e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -81,7 +81,6 @@ impl DerivedTS { .unwrap_or_else(TokenStream::new); let impl_start = generate_impl(&rust_ty, &generics); - let dependencies_v2 = dependencies.to_tokens_v2(); quote! { #impl_start { const EXPORT_TO: Option<&'static str> = Some(#export_to); @@ -97,18 +96,12 @@ impl DerivedTS { } #inline_flattened - fn dependencies() -> Vec - where - Self: 'static, - { - #dependencies - } - + #[allow(clippy::unused_unit)] fn dependency_types() -> impl ts_rs::typelist::TypeList where Self: 'static, { - #dependencies_v2 + #dependencies } fn transparent() -> bool { diff --git a/ts-rs/src/chrono.rs b/ts-rs/src/chrono.rs index 092626e03..7aee2a9b2 100644 --- a/ts-rs/src/chrono.rs +++ b/ts-rs/src/chrono.rs @@ -7,14 +7,12 @@ use chrono::{ }; use super::{impl_primitives, TS}; -use crate::Dependency; macro_rules! impl_dummy { ($($t:ty),*) => {$( impl TS for $t { fn name() -> String { String::new() } fn inline() -> String { String::new() } - fn dependencies() -> Vec { vec![] } fn transparent() -> bool { false } } )*}; @@ -33,9 +31,6 @@ impl TS for DateTime { fn inline() -> String { "string".to_owned() } - fn dependencies() -> Vec { - vec![] - } fn transparent() -> bool { false } @@ -51,9 +46,6 @@ impl TS for Date { fn inline() -> String { "string".to_owned() } - fn dependencies() -> Vec { - vec![] - } fn transparent() -> bool { false } diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index bc5edb99b..98b2be810 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -165,6 +165,8 @@ use std::{ path::{Path, PathBuf}, }; +use crate::typelist::TypeList; + pub use ts_rs_macros::TS; pub use crate::export::ExportError; @@ -292,34 +294,26 @@ pub trait TS { fn dependency_types() -> impl TypeList where - Self: 'static { () } + Self: 'static {} + + fn dependencies() -> Vec where Self: 'static { + use crate::typelist::TypeVisitor; + + let mut deps: Vec = vec![]; + struct Visit<'a>(&'a mut Vec); + impl<'a> TypeVisitor for Visit<'a> { + fn visit(&mut self) { + if let Some(dep) = Dependency::from_ty::() { + self.0.push(dep); + } + } + } + Self::dependency_types().for_each(&mut Visit(&mut deps)); - fn dependencies() -> Vec - where - Self: 'static; - - //fn dependencies() -> Vec where Self: 'static { - // use crate::typelist::{TypeList, TypeVisitor}; - // - // let mut deps: Vec = vec![]; - // struct Visit<'a>(&'a mut Vec); - // impl<'a> TypeVisitor for Visit<'a> { - // fn visit(&mut self) { - // if let Some(dep) = Dependency::from_ty::() { - // self.0.push(dep); - // } - // } - // } - // Self::dependency_types().for_each(&mut Visit(&mut deps)); - // - // deps - //} - - fn _push_to(l: impl TypeList) -> impl TypeList where Self: 'static { - l.push::() - } - - /// `true` if this is a transparent type, e.g tuples or a list. + deps + } + + /// `true` if this is a transparent type, e.g tuples or a list. /// This is used for resolving imports when using the `export!` macro. fn transparent() -> bool; @@ -393,7 +387,6 @@ macro_rules! impl_primitives { $l.to_owned() } fn inline() -> String { $l.to_owned() } - fn dependencies() -> Vec { vec![] } fn transparent() -> bool { false } } )*)* }; @@ -414,15 +407,6 @@ macro_rules! impl_tuples { { ()$(.push::<$i>())* } - fn dependencies() -> Vec - where - Self: 'static - { - [$( Dependency::from_ty::<$i>() ),*] - .into_iter() - .flatten() - .collect() - } fn transparent() -> bool { true } } }; @@ -450,12 +434,6 @@ macro_rules! impl_wrapper { { T::dependency_types() } - fn dependencies() -> Vec - where - Self: 'static - { - T::dependencies() - } fn transparent() -> bool { T::transparent() } } }; @@ -475,12 +453,6 @@ macro_rules! impl_shadow { { <$s>::dependency_types() } - fn dependencies() -> Vec<$crate::Dependency> - where - Self: 'static - { - <$s>::dependencies() - } fn transparent() -> bool { <$s>::transparent() } } }; @@ -511,12 +483,6 @@ impl TS for Option { { ().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::()].into_iter().flatten().collect() - } fn transparent() -> bool { true @@ -536,15 +502,6 @@ impl TS for Result { { ().push::().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::(), Dependency::from_ty::()] - .into_iter() - .flatten() - .collect() - } fn transparent() -> bool { true } @@ -565,12 +522,6 @@ impl TS for Vec { { ().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::()].into_iter().flatten().collect() - } fn transparent() -> bool { true } @@ -622,12 +573,6 @@ impl TS for [T; N] { { ().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::()].into_iter().flatten().collect() - } fn transparent() -> bool { true @@ -659,15 +604,6 @@ impl TS for HashMap { { ().push::().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::(), Dependency::from_ty::()] - .into_iter() - .flatten() - .collect() - } fn transparent() -> bool { true @@ -695,12 +631,6 @@ impl TS for Range { { ().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::()].into_iter().flatten().collect() - } fn transparent() -> bool { true @@ -728,12 +658,6 @@ impl TS for RangeInclusive { { ().push::() } - fn dependencies() -> Vec - where - Self: 'static - { - [Dependency::from_ty::()].into_iter().flatten().collect() - } fn transparent() -> bool { true @@ -809,5 +733,4 @@ impl_primitives! { () => "null" } #[rustfmt::skip] -pub(crate) use impl_primitives; -use crate::typelist::TypeList; +pub(crate) use impl_primitives; \ No newline at end of file diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 6141005ed..578d93b65 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -171,6 +171,7 @@ 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"); diff --git a/ts-rs/tests/ranges.rs b/ts-rs/tests/ranges.rs index 10cdcd659..be491caa4 100644 --- a/ts-rs/tests/ranges.rs +++ b/ts-rs/tests/ranges.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::ops::{Range, RangeInclusive}; use ts_rs::{Dependency, TS}; @@ -22,10 +23,9 @@ fn range() { "type RangeTest = { a: { start: number, end: number, }, b: { start: string, end: string, }, c: { start: { start: number, end: number, }, end: { start: number, end: number, }, }, d: { start: number, end: number, }, e: { start: Inner, end: Inner, }, }" ); assert_eq!( - RangeTest::dependencies(), + RangeTest::dependencies().into_iter().collect::>().into_iter().collect::>(), vec![ Dependency::from_ty::().unwrap(), - Dependency::from_ty::().unwrap() ] ); } diff --git a/ts-rs/tests/test.rs b/ts-rs/tests/test.rs deleted file mode 100644 index f860ce7ed..000000000 --- a/ts-rs/tests/test.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::collections::{BTreeSet, HashMap}; -use ts_rs::{Dependency, TS}; -use ts_rs::typelist::TypeList; -use ts_rs::typelist::TypeVisitor; - -fn get_deps() -> BTreeSet { - use ts_rs::typelist::{TypeList, TypeVisitor}; - - let mut deps = BTreeSet::new(); - struct Visit<'a>(&'a mut BTreeSet); - impl<'a> TypeVisitor for Visit<'a> { - fn visit(&mut self) { - if let Some(dep) = Dependency::from_ty::() { - self.0.insert(dep); - } - } - } - T::dependency_types().for_each(&mut Visit(&mut deps)); - - deps -} - -#[allow(dead_code)] -#[test] -fn test() { - #[derive(TS)] - struct A {} - - #[derive(TS)] - struct B {} - - #[derive(TS)] - struct X { - x: i32, - a: A, - a2: A, - map: HashMap, - } - - println!("{:?}", X::dependencies().into_iter().collect::>()); - println!("{:?}", get_deps::()); -} From ab569e44470c57402a01fd4864e5d82b0b292338 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 17:58:45 +0100 Subject: [PATCH 4/7] recursively export all dependencies --- ts-rs/src/export.rs | 55 ++++++++++++++++++++++++++++++++++++++++++--- ts-rs/src/lib.rs | 2 +- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index fdaf85164..82d65091a 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::{ any::TypeId, collections::BTreeMap, @@ -6,8 +7,10 @@ use std::{ }; use thiserror::Error; + use ExportError::*; +use crate::typelist::{TypeList, TypeVisitor}; use crate::TS; const NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n"; @@ -16,7 +19,7 @@ const NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Ale #[derive(Error, Debug)] pub enum ExportError { #[error("this type cannot be exported")] - CannotBeExported, + CannotBeExported(&'static str), #[cfg(feature = "format")] #[error("an error occurred while formatting the generated typescript output")] Formatting(String), @@ -26,6 +29,51 @@ pub enum ExportError { ManifestDirNotSet, } +/// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute. +/// Additionally, all dependencies of `T` will be exported as well. +/// TODO: This might cause a race condition: +/// If two types `A` and `B` are `#[ts(export)]` and depend on type `C`, +/// then both tests for exporting `A` and `B` will try to write `C` to `C.ts`. +/// Since rust, by default, executes tests in paralell, this might cause `C.ts` to be corrupted. +pub(crate) fn export_type_with_dependencies() -> Result<(), ExportError> { + // exports T, then recursively calls itself with all of its dependencies + fn export_recursive( + seen: &mut HashSet, + ) -> Result<(), ExportError> { + struct Visit<'a> { + seen: &'a mut HashSet, + error: Option, + } + impl<'a> TypeVisitor for Visit<'a> { + fn visit(&mut self) { + // if an error occurred previously, or the type cannot be exported (its a primitive), + // we return + if self.error.is_some() || T::EXPORT_TO.is_none() { + return; + } + self.error = export_recursive::(self.seen).err(); + } + } + + if seen.insert(TypeId::of::()) { + export_type::()?; + let mut visitor = Visit { seen, error: None }; + T::dependency_types().for_each(&mut visitor); + + if let Some(e) = visitor.error { + Err(e) + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + let mut seen = HashSet::new(); + export_recursive::(&mut seen) +} + /// Export `T` to the file specified by the `#[ts(export_to = ..)]` attribute pub(crate) fn export_type() -> Result<(), ExportError> { let path = output_path::()?; @@ -72,7 +120,7 @@ pub(crate) fn export_type_to_string() -> Result() -> Result { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| ManifestDirNotSet)?; let manifest_dir = Path::new(&manifest_dir); - let path = PathBuf::from(T::EXPORT_TO.ok_or(CannotBeExported)?); + let path = PathBuf::from(T::EXPORT_TO.ok_or(CannotBeExported(std::any::type_name::()))?); Ok(manifest_dir.join(path)) } @@ -84,7 +132,8 @@ fn generate_decl(out: &mut String) { /// Push an import statement for all dependencies of `T` fn generate_imports(out: &mut String) -> Result<(), ExportError> { - let path = Path::new(T::EXPORT_TO.ok_or(ExportError::CannotBeExported)?); + let path = + Path::new(T::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::()))?); let deps = T::dependencies(); let deduplicated_deps = deps diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 98b2be810..d1fa0baec 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -328,7 +328,7 @@ pub trait TS { where Self: 'static, { - export::export_type::() + export::export_type_with_dependencies::() } /// Manually export this type to a file with a file with the specified path. This From 646ccd9520e46d009a76377ea54e2bedc930fa03 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 18:52:33 +0100 Subject: [PATCH 5/7] use stupidly large types in E2E test - maybe it fails on CI? --- e2e/workspace/crate1/src/lib.rs | 2 +- e2e/workspace/crate2/src/lib.rs | 2 +- e2e/workspace/parent/src/main.rs | 84 ++++++++++++++++++++++++++++++++ ts-rs/src/export.rs | 3 +- 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/e2e/workspace/crate1/src/lib.rs b/e2e/workspace/crate1/src/lib.rs index 650b721c7..afa054635 100644 --- a/e2e/workspace/crate1/src/lib.rs +++ b/e2e/workspace/crate1/src/lib.rs @@ -2,5 +2,5 @@ use ts_rs::TS; #[derive(TS)] pub struct Crate1 { - pub x: i32 + pub x: [[[i32; 128]; 128]; 128], } \ No newline at end of file diff --git a/e2e/workspace/crate2/src/lib.rs b/e2e/workspace/crate2/src/lib.rs index c82468175..9617cfbf1 100644 --- a/e2e/workspace/crate2/src/lib.rs +++ b/e2e/workspace/crate2/src/lib.rs @@ -2,5 +2,5 @@ use ts_rs::TS; #[derive(TS)] pub struct Crate2 { - pub x: i32 + pub x: [[[i32; 128]; 128]; 128], } diff --git a/e2e/workspace/parent/src/main.rs b/e2e/workspace/parent/src/main.rs index 5f05ae79d..12c9f4e13 100644 --- a/e2e/workspace/parent/src/main.rs +++ b/e2e/workspace/parent/src/main.rs @@ -12,3 +12,87 @@ pub struct Parent { pub crate1: Crate1, pub crate2: Crate2, } +#[derive(TS)] +#[ts(export)] +pub struct Parent2 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent3 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent4 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent5 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent6 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent7 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent8 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent9 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent10 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent11 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent12 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent13 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent14 { + pub crate1: Crate1, + pub crate2: Crate2, +} +#[derive(TS)] +#[ts(export)] +pub struct Parent15 { + pub crate1: Crate1, + pub crate2: Crate2, +} diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 82d65091a..94957a127 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -31,6 +31,7 @@ pub enum ExportError { /// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute. /// Additionally, all dependencies of `T` will be exported as well. +/// /// TODO: This might cause a race condition: /// If two types `A` and `B` are `#[ts(export)]` and depend on type `C`, /// then both tests for exporting `A` and `B` will try to write `C` to `C.ts`. @@ -133,7 +134,7 @@ fn generate_decl(out: &mut String) { /// Push an import statement for all dependencies of `T` fn generate_imports(out: &mut String) -> Result<(), ExportError> { let path = - Path::new(T::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::()))?); + Path::new(T::EXPORT_TO.ok_or(CannotBeExported(std::any::type_name::()))?); let deps = T::dependencies(); let deduplicated_deps = deps From 3de39ab39aa1fe6541f64eca93e3af025d19aa6c Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 1 Feb 2024 15:07:55 -0300 Subject: [PATCH 6/7] Separate recursive exporting into mod --- macros/src/attr/field.rs | 4 +- macros/src/deps.rs | 3 +- macros/src/types/enum.rs | 8 +++- macros/src/types/generics.rs | 2 +- ts-rs/src/export.rs | 81 ++++++++++++++++++++--------------- ts-rs/src/lib.rs | 48 ++++++++++++--------- ts-rs/src/typelist.rs | 18 +++++--- ts-rs/tests/arrays.rs | 5 ++- ts-rs/tests/generics.rs | 20 ++++++--- ts-rs/tests/optional_field.rs | 14 ++++-- ts-rs/tests/ranges.rs | 10 +++-- 11 files changed, 134 insertions(+), 79 deletions(-) diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 3625224e6..5897dae82 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -1,5 +1,5 @@ -use syn::{Attribute, Ident, Result}; use syn::spanned::Spanned; +use syn::{Attribute, Ident, Result}; use crate::utils::parse_attrs; @@ -60,7 +60,7 @@ impl FieldAttr { self.skip = self.skip || skip; self.optional = Optional { optional: self.optional.optional || optional, - nullable: self.optional.nullable || nullable + nullable: self.optional.nullable || nullable, }; self.flatten |= flatten; } diff --git a/macros/src/deps.rs b/macros/src/deps.rs index 366ad824c..b99605656 100644 --- a/macros/src/deps.rs +++ b/macros/src/deps.rs @@ -8,7 +8,8 @@ pub struct Dependencies(Vec); impl Dependencies { /// Adds all dependencies from the given type pub fn append_from(&mut self, ty: &Type) { - self.0.push(quote![.extend(<#ty as ts_rs::TS>::dependency_types())]); + self.0 + .push(quote![.extend(<#ty as ts_rs::TS>::dependency_types())]); } /// Adds the given type if it's *not* transparent. diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index 8978fa52d..a4738223e 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -115,7 +115,9 @@ fn format_variant( let ty = match (type_override, type_as) { (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"), (Some(type_override), None) => quote! { #type_override }, - (None, Some(type_as)) => format_type(&syn::parse_str::(&type_as)?, dependencies, generics), + (None, Some(type_as)) => { + format_type(&syn::parse_str::(&type_as)?, dependencies, generics) + } (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics), }; @@ -159,7 +161,9 @@ fn format_variant( let ty = match (type_override, type_as) { (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"), (Some(type_override), None) => quote! { #type_override }, - (None, Some(type_as)) => format_type(&syn::parse_str::(&type_as)?, dependencies, generics), + (None, Some(type_as)) => { + format_type(&syn::parse_str::(&type_as)?, dependencies, generics) + } (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics), }; diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs index 307542ff7..7da2c48ba 100644 --- a/macros/src/types/generics.rs +++ b/macros/src/types/generics.rs @@ -83,7 +83,7 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi // be cause the T in `[T; N]` is technically not a generic Type::Array(type_array) => { let formatted = format_type(&type_array.elem, dependencies, generics); - return quote!(<#type_array>::name_with_type_args(vec![#formatted])) + return quote!(<#type_array>::name_with_type_args(vec![#formatted])); } // The field is a slice (`[T]`) so it technically doesn't have a // generic argument. Therefore, we handle it explicitly here like a `Vec` diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 82d65091a..afb1cd5c3 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::{ any::TypeId, collections::BTreeMap, @@ -10,7 +9,6 @@ use thiserror::Error; use ExportError::*; -use crate::typelist::{TypeList, TypeVisitor}; use crate::TS; const NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n"; @@ -29,49 +27,64 @@ pub enum ExportError { ManifestDirNotSet, } -/// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute. -/// Additionally, all dependencies of `T` will be exported as well. -/// TODO: This might cause a race condition: -/// If two types `A` and `B` are `#[ts(export)]` and depend on type `C`, -/// then both tests for exporting `A` and `B` will try to write `C` to `C.ts`. -/// Since rust, by default, executes tests in paralell, this might cause `C.ts` to be corrupted. -pub(crate) fn export_type_with_dependencies() -> Result<(), ExportError> { +pub(crate) use recursive_export::export_type_with_dependencies; +mod recursive_export { + use std::{any::TypeId, collections::HashSet}; + + use super::export_type; + use crate::{ + typelist::{TypeList, TypeVisitor}, + ExportError, TS, + }; + + struct Visit<'a> { + seen: &'a mut HashSet, + error: Option, + } + + impl<'a> TypeVisitor for Visit<'a> { + fn visit(&mut self) { + // if an error occurred previously, or the type cannot be exported (it's a primitive), + // we return + if self.error.is_some() || T::EXPORT_TO.is_none() { + return; + } + + self.error = export_recursive::(self.seen).err(); + } + } + + /// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute. + /// Additionally, all dependencies of `T` will be exported as well. + /// TODO: This might cause a race condition: + /// If two types `A` and `B` are `#[ts(export)]` and depend on type `C`, + /// then both tests for exporting `A` and `B` will try to write `C` to `C.ts`. + /// Since rust, by default, executes tests in paralell, this might cause `C.ts` to be corrupted. + pub(crate) fn export_type_with_dependencies( + ) -> Result<(), ExportError> { + let mut seen = HashSet::new(); + export_recursive::(&mut seen) + } + // exports T, then recursively calls itself with all of its dependencies fn export_recursive( seen: &mut HashSet, ) -> Result<(), ExportError> { - struct Visit<'a> { - seen: &'a mut HashSet, - error: Option, - } - impl<'a> TypeVisitor for Visit<'a> { - fn visit(&mut self) { - // if an error occurred previously, or the type cannot be exported (its a primitive), - // we return - if self.error.is_some() || T::EXPORT_TO.is_none() { - return; - } - self.error = export_recursive::(self.seen).err(); - } + if !seen.insert(TypeId::of::()) { + return Ok(()); } - if seen.insert(TypeId::of::()) { - export_type::()?; - let mut visitor = Visit { seen, error: None }; - T::dependency_types().for_each(&mut visitor); + export_type::()?; - if let Some(e) = visitor.error { - Err(e) - } else { - Ok(()) - } + let mut visitor = Visit { seen, error: None }; + T::dependency_types().for_each(&mut visitor); + + if let Some(e) = visitor.error { + Err(e) } else { Ok(()) } } - - let mut seen = HashSet::new(); - export_recursive::(&mut seen) } /// Export `T` to the file specified by the `#[ts(export_to = ..)]` attribute diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index d1fa0baec..2fb3cb755 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -293,10 +293,15 @@ pub trait TS { } fn dependency_types() -> impl TypeList - where - Self: 'static {} + where + Self: 'static, + { + } - fn dependencies() -> Vec where Self: 'static { + fn dependencies() -> Vec + where + Self: 'static, + { use crate::typelist::TypeVisitor; let mut deps: Vec = vec![]; @@ -478,8 +483,8 @@ impl TS for Option { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::() } @@ -497,8 +502,8 @@ impl TS for Result { format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline()) } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::().push::() } @@ -517,8 +522,8 @@ impl TS for Vec { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::() } @@ -532,7 +537,7 @@ const ARRAY_TUPLE_LIMIT: usize = 128; impl TS for [T; N] { fn name() -> String { if N > ARRAY_TUPLE_LIMIT { - return Vec::::name() + return Vec::::name(); } "[]".to_owned() @@ -552,7 +557,10 @@ impl TS for [T; N] { format!( "[{}]", - (0..N).map(|_| args[0].clone()).collect::>().join(", ") + (0..N) + .map(|_| args[0].clone()) + .collect::>() + .join(", ") ) } @@ -568,8 +576,8 @@ impl TS for [T; N] { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::() } @@ -599,8 +607,8 @@ impl TS for HashMap { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::().push::() } @@ -626,8 +634,8 @@ impl TS for Range { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::() } @@ -653,8 +661,8 @@ impl TS for RangeInclusive { } fn dependency_types() -> impl TypeList - where - Self: 'static + where + Self: 'static, { ().push::() } @@ -733,4 +741,4 @@ impl_primitives! { () => "null" } #[rustfmt::skip] -pub(crate) use impl_primitives; \ No newline at end of file +pub(crate) use impl_primitives; diff --git a/ts-rs/src/typelist.rs b/ts-rs/src/typelist.rs index 1ad21286b..edb46f6e8 100644 --- a/ts-rs/src/typelist.rs +++ b/ts-rs/src/typelist.rs @@ -7,7 +7,6 @@ pub trait TypeVisitor: Sized { fn visit(&mut self); } - pub trait TypeList: Copy + Clone { fn push(self) -> impl TypeList { (self, (PhantomData::,)) @@ -21,11 +20,16 @@ pub trait TypeList: Copy + Clone { } impl TypeList for () { - fn contains(self) -> bool { false } + fn contains(self) -> bool { + false + } fn for_each(self, _: &mut impl TypeVisitor) {} } -impl TypeList for (PhantomData,) where T: TS + 'static + ?Sized { +impl TypeList for (PhantomData,) +where + T: TS + 'static + ?Sized, +{ fn contains(self) -> bool { TypeId::of::() == TypeId::of::() } @@ -35,7 +39,11 @@ impl TypeList for (PhantomData,) where T: TS + 'static + ?Sized { } } -impl TypeList for (A, B) where A: TypeList, B: TypeList { +impl TypeList for (A, B) +where + A: TypeList, + B: TypeList, +{ fn contains(self) -> bool { self.0.contains::() || self.1.contains::() } @@ -44,4 +52,4 @@ impl TypeList for (A, B) where A: TypeList, B: TypeList { self.0.for_each(v); self.1.for_each(v); } -} \ No newline at end of file +} diff --git a/ts-rs/tests/arrays.rs b/ts-rs/tests/arrays.rs index f974b3830..4753b7eb7 100644 --- a/ts-rs/tests/arrays.rs +++ b/ts-rs/tests/arrays.rs @@ -13,7 +13,10 @@ fn interface() { a: [i32; 4], } - assert_eq!(Interface::inline(), "{ a: [number, number, number, number], }") + assert_eq!( + Interface::inline(), + "{ a: [number, number, number, number], }" + ) } #[test] diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 578d93b65..7397caaed 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -191,12 +191,14 @@ fn inline_with_bounds() { t: Generic, } - assert_eq!(Generic::<&'static str>::decl(), "type Generic = { t: T, }"); + assert_eq!( + Generic::<&'static str>::decl(), + "type Generic = { t: T, }" + ); // ^^^^^^^^^^^^ Replace with something else 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, }" // Actual output: { g: Generic, gi: { t: T, }, t: T, } ); } @@ -210,7 +212,7 @@ fn inline_with_default() { #[derive(TS)] struct Container { g: Generic, - + #[ts(inline)] gi: Generic, @@ -218,7 +220,10 @@ fn inline_with_default() { t: Generic, } - assert_eq!(Generic::<()>::decl(), "type Generic = { t: T, }"); + assert_eq!( + Generic::<()>::decl(), + "type Generic = { t: T, }" + ); assert_eq!( Container::decl(), "type Container = { g: Generic, gi: { t: string, }, t: number, }" @@ -283,6 +288,9 @@ fn trait_bounds() { t: [T; N], } - let ty = format!("type D = {{ t: [{}], }}", "T, ".repeat(41).trim_end_matches(", ")); + let ty = format!( + "type D = {{ t: [{}], }}", + "T, ".repeat(41).trim_end_matches(", ") + ); assert_eq!(D::<&str, 41>::decl(), ty) } diff --git a/ts-rs/tests/optional_field.rs b/ts-rs/tests/optional_field.rs index 7f10496d1..1c7174a0b 100644 --- a/ts-rs/tests/optional_field.rs +++ b/ts-rs/tests/optional_field.rs @@ -24,11 +24,19 @@ fn in_struct() { fn in_enum() { #[derive(Serialize, TS)] enum Optional { - A { #[ts(optional)] a: Option }, - B { b: Option, } + A { + #[ts(optional)] + a: Option, + }, + B { + b: Option, + }, } - assert_eq!(Optional::inline(), r#"{ "A": { a?: number, } } | { "B": { b: string | null, } }"#); + assert_eq!( + Optional::inline(), + r#"{ "A": { a?: number, } } | { "B": { b: string | null, } }"# + ); } #[test] diff --git a/ts-rs/tests/ranges.rs b/ts-rs/tests/ranges.rs index be491caa4..10fd8ed83 100644 --- a/ts-rs/tests/ranges.rs +++ b/ts-rs/tests/ranges.rs @@ -23,9 +23,11 @@ fn range() { "type RangeTest = { a: { start: number, end: number, }, b: { start: string, end: string, }, c: { start: { start: number, end: number, }, end: { start: number, end: number, }, }, d: { start: number, end: number, }, e: { start: Inner, end: Inner, }, }" ); assert_eq!( - RangeTest::dependencies().into_iter().collect::>().into_iter().collect::>(), - vec![ - Dependency::from_ty::().unwrap(), - ] + RangeTest::dependencies() + .into_iter() + .collect::>() + .into_iter() + .collect::>(), + vec![Dependency::from_ty::().unwrap(),] ); } From dcec4d89dd579c647e35934439b4c9ed1caa639d Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Thu, 1 Feb 2024 20:48:06 +0100 Subject: [PATCH 7/7] introduce mutex for file exports, change array tuple limit to 64 --- ts-rs/src/export.rs | 8 ++++++++ ts-rs/src/lib.rs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 2e711cb73..626fed9f4 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -4,6 +4,7 @@ use std::{ fmt::Write, path::{Component, Path, PathBuf}, }; +use std::sync::Mutex; use thiserror::Error; @@ -98,6 +99,11 @@ pub(crate) fn export_type() -> Result<(), ExportError> pub(crate) fn export_type_to>( path: P, ) -> Result<(), ExportError> { + // Lock to make sure only one file will be written at a time. + // In the future, it might make sense to replace this with something more clever to only prevent + // two threads from writing the **same** file concurrently. + static FILE_LOCK: Mutex<()> = Mutex::new(()); + #[allow(unused_mut)] let mut buffer = export_type_to_string::()?; @@ -117,7 +123,9 @@ pub(crate) fn export_type_to>( if let Some(parent) = path.as_ref().parent() { std::fs::create_dir_all(parent)?; } + let lock = FILE_LOCK.lock().unwrap(); std::fs::write(path.as_ref(), buffer)?; + drop(lock); Ok(()) } diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 2fb3cb755..5a3d3a205 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -532,8 +532,8 @@ impl TS for Vec { } } -// Arrays longer than this limit will be emmited as Array -const ARRAY_TUPLE_LIMIT: usize = 128; +// Arrays longer than this limit will be emitted as Array +const ARRAY_TUPLE_LIMIT: usize = 64; impl TS for [T; N] { fn name() -> String { if N > ARRAY_TUPLE_LIMIT {