From 1d72f6504adfca27015d50e4d977d3bae2375bc9 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 26 Jan 2024 10:23:49 -0300 Subject: [PATCH 1/5] Convert Array to TS Tuple --- macros/src/types/generics.rs | 16 +++++++++++++++- ts-rs/src/lib.rs | 29 +++++++++++++++++++++++++++-- ts-rs/tests/arrays.rs | 10 +++++----- ts-rs/tests/generic_fields.rs | 8 ++++---- ts-rs/tests/generics.rs | 2 +- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs index 4766de970..9c3ca6d42 100644 --- a/macros/src/types/generics.rs +++ b/macros/src/types/generics.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeArray, TypeGroup, - TypeReference, TypeSlice, TypeTuple, + TypeReference, TypeSlice, TypeTuple, Expr, ExprLit, Lit, }; use crate::{attr::StructAttr, deps::Dependencies}; @@ -62,6 +62,20 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi // special treatment for arrays and tuples match ty { + // When possible, convert a Rust array into a fixed size TS tuple + // this is only possible when the length of the array is a numeric literal, + // rather than the name of a constant + Type::Array(TypeArray { + ref elem, + len: Expr::Lit(ExprLit { lit: Lit::Int(lit_int), .. }), + .. + }) => { + let inner_ty = elem; + let len = lit_int.base10_parse::().unwrap(); + let item_ty = (0..len).map(|_| quote!(#inner_ty)); + let tuple_ty = syn::parse2::(quote!{ (#(#item_ty),*) }).unwrap(); + return format_type(&tuple_ty, dependencies, generics) + } // The field is an array (`[T; n]`) or a slice (`[T]`) so it technically doesn't have a // generic argument. Therefore, we handle it explicitly here like a `Vec` Type::Array(TypeArray { ref elem, .. }) | Type::Slice(TypeSlice { ref elem, .. }) => { diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 464e44cb5..72f125f59 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -495,6 +495,30 @@ impl TS for Vec { "Array".to_owned() } + fn inline() -> String { + format!("Array<{}>", T::inline()) + } + + fn dependencies() -> Vec + where + Self: 'static, + { + [Dependency::from_ty::()].into_iter().flatten().collect() + } + + fn transparent() -> bool { + true + } +} + +impl TS for [T; N] { + fn name() -> String { + format!( + "[{}]", + (0..N).map(|_| T::name()).collect::>().join(", ") + ) + } + fn name_with_type_args(args: Vec) -> String { assert_eq!( args.len(), @@ -506,7 +530,8 @@ impl TS for Vec { } fn inline() -> String { - format!("Array<{}>", T::inline()) + Self::name() + // format!("Array<{}>", T::inline()) } fn dependencies() -> Vec @@ -613,7 +638,7 @@ impl_shadow!(as T: impl<'a, T: TS + ?Sized> 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); -impl_shadow!(as Vec: impl TS for [T; N]); +// impl_shadow!(as Vec: impl TS for [T; N]); impl_shadow!(as Vec: impl TS for [T]); impl_wrapper!(impl TS for Box); diff --git a/ts-rs/tests/arrays.rs b/ts-rs/tests/arrays.rs index fe37a33d3..f974b3830 100644 --- a/ts-rs/tests/arrays.rs +++ b/ts-rs/tests/arrays.rs @@ -2,7 +2,7 @@ use ts_rs::TS; #[test] fn free() { - assert_eq!(<[String; 10]>::inline(), "Array") + assert_eq!(<[String; 4]>::inline(), "[string, string, string, string]") } #[test] @@ -10,16 +10,16 @@ fn interface() { #[derive(TS)] struct Interface { #[allow(dead_code)] - a: [i32; 10], + a: [i32; 4], } - assert_eq!(Interface::inline(), "{ a: Array, }") + assert_eq!(Interface::inline(), "{ a: [number, number, number, number], }") } #[test] fn newtype() { #[derive(TS)] - struct Newtype(#[allow(dead_code)] [i32; 10]); + struct Newtype(#[allow(dead_code)] [i32; 4]); - assert_eq!(Newtype::inline(), "Array") + assert_eq!(Newtype::inline(), "[number, number, number, number]") } diff --git a/ts-rs/tests/generic_fields.rs b/ts-rs/tests/generic_fields.rs index a6f362d54..e2100fab9 100644 --- a/ts-rs/tests/generic_fields.rs +++ b/ts-rs/tests/generic_fields.rs @@ -40,7 +40,7 @@ fn named() { } assert_eq!( Struct::inline(), - "{ a: Array, b: [Array, Array], c: Array>, }" + "{ a: Array, b: [Array, Array], c: [Array, Array, Array], }" ); } @@ -52,7 +52,7 @@ fn named_nested() { b: (Vec>, Vec>), c: [Vec>; 3], } - assert_eq!(Struct::inline(), "{ a: Array>, b: [Array>, Array>], c: Array>>, }"); + assert_eq!(Struct::inline(), "{ a: Array>, b: [Array>, Array>], c: [Array>, Array>, Array>], }"); } #[test] @@ -61,7 +61,7 @@ fn tuple() { struct Tuple(Vec, (Vec, Vec), [Vec; 3]); assert_eq!( Tuple::inline(), - "[Array, [Array, Array], Array>]" + "[Array, [Array, Array], [Array, Array, Array]]" ); } @@ -75,6 +75,6 @@ fn tuple_nested() { ); assert_eq!( Tuple::inline(), - "[Array>, [Array>, Array>], Array>>]" + "[Array>, [Array>, Array>], [Array>, Array>, Array>]]" ); } diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index cd8cd6875..184d50eb7 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -142,7 +142,7 @@ fn generic_struct() { assert_eq!( Struct::<()>::decl(), - "type Struct = { a: T, b: [T, T], c: [T, [T, T]], d: Array, e: Array<[T, T]>, f: Array, g: Array>, h: Array>, }" + "type Struct = { a: T, b: [T, T], c: [T, [T, T]], d: [T, T, T], e: [[T, T], [T, T], [T, T]], f: Array, g: Array>, h: Array<[[T, T], [T, T], [T, T]]>, }" ) } From 613fef9d479c98e22f82b483b434271c28735b76 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 26 Jan 2024 10:35:58 -0300 Subject: [PATCH 2/5] Delete commented code --- ts-rs/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 72f125f59..286f84bf3 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -531,7 +531,6 @@ impl TS for [T; N] { fn inline() -> String { Self::name() - // format!("Array<{}>", T::inline()) } fn dependencies() -> Vec From e7215abd877a764bdd4c5ef20f67d4987839add5 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 30 Jan 2024 09:39:54 -0300 Subject: [PATCH 3/5] Add limit to ts tuple size and simplify array case in generics.rs --- macros/src/types/generics.rs | 26 +++++++++----------------- ts-rs/src/lib.rs | 33 +++++++++++++++++++++++++-------- ts-rs/tests/generics.rs | 5 +++-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs index 9c3ca6d42..c035f9fee 100644 --- a/macros/src/types/generics.rs +++ b/macros/src/types/generics.rs @@ -1,8 +1,8 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ - GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeArray, TypeGroup, - TypeReference, TypeSlice, TypeTuple, Expr, ExprLit, Lit, + GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup, + TypeReference, TypeSlice, TypeTuple, }; use crate::{attr::StructAttr, deps::Dependencies}; @@ -62,23 +62,15 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi // special treatment for arrays and tuples match ty { - // When possible, convert a Rust array into a fixed size TS tuple - // this is only possible when the length of the array is a numeric literal, - // rather than the name of a constant - Type::Array(TypeArray { - ref elem, - len: Expr::Lit(ExprLit { lit: Lit::Int(lit_int), .. }), - .. - }) => { - let inner_ty = elem; - let len = lit_int.base10_parse::().unwrap(); - let item_ty = (0..len).map(|_| quote!(#inner_ty)); - let tuple_ty = syn::parse2::(quote!{ (#(#item_ty),*) }).unwrap(); - return format_type(&tuple_ty, dependencies, generics) + // Arrays have their own implementation that needs to be handle separetly + // 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])) } - // The field is an array (`[T; n]`) or a slice (`[T]`) so it technically doesn't have a + // The field is a slice (`[T]`) so it technically doesn't have a // generic argument. Therefore, we handle it explicitly here like a `Vec` - Type::Array(TypeArray { ref elem, .. }) | Type::Slice(TypeSlice { ref elem, .. }) => { + Type::Slice(TypeSlice { ref elem, .. }) => { let inner_ty = elem; let vec_ty = syn::parse2::(quote!(Vec::<#inner_ty>)).unwrap(); return format_type(&vec_ty, dependencies, generics); diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 286f84bf3..497c94823 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -511,26 +511,44 @@ impl TS for Vec { } } +// Arrays longer than this limit will be emmited as Array +const ARRAY_TUPLE_LIMIT: usize = 100; // Temp value impl TS for [T; N] { fn name() -> String { - format!( - "[{}]", - (0..N).map(|_| T::name()).collect::>().join(", ") - ) + if N > ARRAY_TUPLE_LIMIT { + return Vec::::name() + } + + "[]".to_owned() } fn name_with_type_args(args: Vec) -> String { + if N > ARRAY_TUPLE_LIMIT { + return Vec::::name_with_type_args(args); + } + assert_eq!( args.len(), 1, - "called Vec::name_with_type_args with {} args", + "called [T; N]::name_with_type_args with {} args", args.len() ); - format!("Array<{}>", args[0]) + + format!( + "[{}]", + (0..N).map(|_| args[0].clone()).collect::>().join(", ") + ) } fn inline() -> String { - Self::name() + if N > ARRAY_TUPLE_LIMIT { + return Vec::::inline(); + } + + format!( + "[{}]", + (0..N).map(|_| T::inline()).collect::>().join(", ") + ) } fn dependencies() -> Vec @@ -637,7 +655,6 @@ impl_shadow!(as T: impl<'a, T: TS + ?Sized> 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); -// impl_shadow!(as Vec: impl TS for [T; N]); impl_shadow!(as Vec: impl TS for [T]); impl_wrapper!(impl TS for Box); diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs index 184d50eb7..9c9bcbedc 100644 --- a/ts-rs/tests/generics.rs +++ b/ts-rs/tests/generics.rs @@ -1,4 +1,4 @@ -#![allow(clippy::box_collection)] +#![allow(clippy::box_collection, clippy::enum_variant_names, dead_code)] #![allow(dead_code)] use std::{ @@ -232,5 +232,6 @@ fn trait_bounds() { t: [T; N], } - assert_eq!(D::<&str, 41>::decl(), "type D = { t: Array, }") + let ty = format!("type D = {{ t: [{}], }}", "T, ".repeat(41).trim_end_matches(", ")); + assert_eq!(D::<&str, 41>::decl(), ty) } From a9ef2b1b3a8c870a816a6354f942c4f3b85a003b Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 30 Jan 2024 15:10:47 -0300 Subject: [PATCH 4/5] Make limit 128 --- ts-rs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 8d742bd4b..2203256a3 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -515,7 +515,7 @@ impl TS for Vec { } // Arrays longer than this limit will be emmited as Array -const ARRAY_TUPLE_LIMIT: usize = 100; // Temp value +const ARRAY_TUPLE_LIMIT: usize = 128; // Temp value impl TS for [T; N] { fn name() -> String { if N > ARRAY_TUPLE_LIMIT { From fbead2ca56d9752d000135e5ea05859599796a45 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 30 Jan 2024 15:11:27 -0300 Subject: [PATCH 5/5] Remove Temp value comment --- ts-rs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 2203256a3..58c193950 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -515,7 +515,7 @@ impl TS for Vec { } // Arrays longer than this limit will be emmited as Array -const ARRAY_TUPLE_LIMIT: usize = 128; // Temp value +const ARRAY_TUPLE_LIMIT: usize = 128; impl TS for [T; N] { fn name() -> String { if N > ARRAY_TUPLE_LIMIT {