Skip to content

Commit

Permalink
Merge pull request #209 from escritorio-gustavo/array_as_ts_tuple
Browse files Browse the repository at this point in the history
Convert fixed size Rust array to fixed size TS tuple
  • Loading branch information
escritorio-gustavo authored Jan 30, 2024
2 parents a0a3e13 + fbead2c commit 50f74b5
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 19 deletions.
12 changes: 9 additions & 3 deletions macros/src/types/generics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeArray, TypeGroup,
GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup,
TypeReference, TypeSlice, TypeTuple,
};

Expand Down Expand Up @@ -79,9 +79,15 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi

// special treatment for arrays and tuples
match ty {
// The field is an array (`[T; n]`) or a slice (`[T]`) so it technically doesn't have a
// 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 a slice (`[T]`) so it technically doesn't have a
// generic argument. Therefore, we handle it explicitly here like a `Vec<T>`
Type::Array(TypeArray { ref elem, .. }) | Type::Slice(TypeSlice { ref elem, .. }) => {
Type::Slice(TypeSlice { ref elem, .. }) => {
let inner_ty = elem;
let vec_ty = syn::parse2::<Type>(quote!(Vec::<#inner_ty>)).unwrap();
return format_type(&vec_ty, dependencies, generics);
Expand Down
49 changes: 45 additions & 4 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,18 +501,60 @@ impl<T: TS> TS for Vec<T> {
"Array".to_owned()
}

fn inline() -> String {
format!("Array<{}>", T::inline())
}

fn dependencies() -> Vec<Dependency>
where
Self: 'static,
{
[Dependency::from_ty::<T>()].into_iter().flatten().collect()
}

fn transparent() -> bool {
true
}
}

// Arrays longer than this limit will be emmited as Array<T>
const ARRAY_TUPLE_LIMIT: usize = 128;
impl<T: TS, const N: usize> TS for [T; N] {
fn name() -> String {
if N > ARRAY_TUPLE_LIMIT {
return Vec::<T>::name()
}

"[]".to_owned()
}

fn name_with_type_args(args: Vec<String>) -> String {
if N > ARRAY_TUPLE_LIMIT {
return Vec::<T>::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::<Box<[_]>>().join(", ")
)
}

fn inline() -> String {
format!("Array<{}>", T::inline())
if N > ARRAY_TUPLE_LIMIT {
return Vec::<T>::inline();
}

format!(
"[{}]",
(0..N).map(|_| T::inline()).collect::<Box<[_]>>().join(", ")
)
}

fn dependencies() -> Vec<Dependency>
Expand Down Expand Up @@ -619,7 +661,6 @@ impl_shadow!(as T: impl<'a, T: TS + ?Sized> TS for &T);
impl_shadow!(as Vec<T>: impl<T: TS, H> TS for HashSet<T, H>);
impl_shadow!(as Vec<T>: impl<T: TS> TS for BTreeSet<T>);
impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for BTreeMap<K, V>);
impl_shadow!(as Vec<T>: impl<T: TS, const N: usize> TS for [T; N]);
impl_shadow!(as Vec<T>: impl<T: TS> TS for [T]);

impl_wrapper!(impl<T: TS + ?Sized> TS for Box<T>);
Expand Down
10 changes: 5 additions & 5 deletions ts-rs/tests/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ use ts_rs::TS;

#[test]
fn free() {
assert_eq!(<[String; 10]>::inline(), "Array<string>")
assert_eq!(<[String; 4]>::inline(), "[string, string, string, string]")
}

#[test]
fn interface() {
#[derive(TS)]
struct Interface {
#[allow(dead_code)]
a: [i32; 10],
a: [i32; 4],
}

assert_eq!(Interface::inline(), "{ a: Array<number>, }")
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<number>")
assert_eq!(Newtype::inline(), "[number, number, number, number]")
}
8 changes: 4 additions & 4 deletions ts-rs/tests/generic_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn named() {
}
assert_eq!(
Struct::inline(),
"{ a: Array<string>, b: [Array<string>, Array<string>], c: Array<Array<string>>, }"
"{ a: Array<string>, b: [Array<string>, Array<string>], c: [Array<string>, Array<string>, Array<string>], }"
);
}

Expand All @@ -52,7 +52,7 @@ fn named_nested() {
b: (Vec<Vec<String>>, Vec<Vec<String>>),
c: [Vec<Vec<String>>; 3],
}
assert_eq!(Struct::inline(), "{ a: Array<Array<string>>, b: [Array<Array<string>>, Array<Array<string>>], c: Array<Array<Array<string>>>, }");
assert_eq!(Struct::inline(), "{ a: Array<Array<string>>, b: [Array<Array<string>>, Array<Array<string>>], c: [Array<Array<string>>, Array<Array<string>>, Array<Array<string>>], }");
}

#[test]
Expand All @@ -61,7 +61,7 @@ fn tuple() {
struct Tuple(Vec<i32>, (Vec<i32>, Vec<i32>), [Vec<i32>; 3]);
assert_eq!(
Tuple::inline(),
"[Array<number>, [Array<number>, Array<number>], Array<Array<number>>]"
"[Array<number>, [Array<number>, Array<number>], [Array<number>, Array<number>, Array<number>]]"
);
}

Expand All @@ -75,6 +75,6 @@ fn tuple_nested() {
);
assert_eq!(
Tuple::inline(),
"[Array<Array<number>>, [Array<Array<number>>, Array<Array<number>>], Array<Array<Array<number>>>]"
"[Array<Array<number>>, [Array<Array<number>>, Array<Array<number>>], [Array<Array<number>>, Array<Array<number>>, Array<Array<number>>]]"
);
}
7 changes: 4 additions & 3 deletions ts-rs/tests/generics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![allow(clippy::box_collection)]
#![allow(clippy::box_collection, clippy::enum_variant_names, dead_code)]
#![allow(dead_code)]

use std::{
Expand Down Expand Up @@ -142,7 +142,7 @@ fn generic_struct() {

assert_eq!(
Struct::<()>::decl(),
"type Struct<T> = { a: T, b: [T, T], c: [T, [T, T]], d: Array<T>, e: Array<[T, T]>, f: Array<T>, g: Array<Array<T>>, h: Array<Array<[T, T]>>, }"
"type Struct<T> = { a: T, b: [T, T], c: [T, [T, T]], d: [T, T, T], e: [[T, T], [T, T], [T, T]], f: Array<T>, g: Array<Array<T>>, h: Array<[[T, T], [T, T], [T, T]]>, }"
)
}

Expand Down Expand Up @@ -282,5 +282,6 @@ fn trait_bounds() {
t: [T; N],
}

assert_eq!(D::<&str, 41>::decl(), "type D<T> = { t: Array<T>, }")
let ty = format!("type D<T> = {{ t: [{}], }}", "T, ".repeat(41).trim_end_matches(", "));
assert_eq!(D::<&str, 41>::decl(), ty)
}

0 comments on commit 50f74b5

Please sign in to comment.