From 7039fba7972935b3a64325250a3f46370ca034fa Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 22 Nov 2023 17:46:25 +0900 Subject: [PATCH] `[T; N]` as Typescript tuple --- src/datatype/list.rs | 19 +++++++++++++++++ src/datatype/mod.rs | 4 +++- src/lang/ts/mod.rs | 19 ++++++++++++++--- src/serde.rs | 7 ++++-- src/type/impls.rs | 40 ++++++++++++++++++++++++++++++++++- src/type/macros.rs | 23 +++++++++++--------- src/type/mod.rs | 4 +--- tests/const_types.rs | 8 +++++++ tests/lib.rs | 1 + tests/ts.rs | 7 ++++-- tests/ts_rs/arrays.rs | 10 ++++----- tests/ts_rs/generic_fields.rs | 11 ++++++---- tests/ts_rs/generics.rs | 2 +- 13 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 src/datatype/list.rs create mode 100644 tests/const_types.rs diff --git a/src/datatype/list.rs b/src/datatype/list.rs new file mode 100644 index 00000000..eca4cf5e --- /dev/null +++ b/src/datatype/list.rs @@ -0,0 +1,19 @@ +use crate::DataType; + +#[derive(Debug, Clone, PartialEq)] +pub struct List { + // The type of the elements in the list. + pub(crate) ty: Box, + // Length is set for `[Type; N]` arrays. + pub(crate) length: Option, +} + +impl List { + pub fn ty(&self) -> &DataType { + &self.ty + } + + pub fn length(&self) -> Option { + self.length + } +} diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index a5522ee8..32adaee1 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -6,6 +6,7 @@ use std::{ mod r#enum; mod fields; +mod list; mod literal; mod named; mod primitive; @@ -13,6 +14,7 @@ mod r#struct; mod tuple; pub use fields::*; +pub use list::*; pub use literal::*; pub use named::*; pub use primitive::*; @@ -47,7 +49,7 @@ pub enum DataType { Primitive(PrimitiveType), Literal(LiteralType), /// Either a `Set` or a `Vec` - List(Box), + List(List), Nullable(Box), Map(Box<(DataType, DataType)>), // Anonymous Reference types diff --git a/src/lang/ts/mod.rs b/src/lang/ts/mod.rs index 5ba1c640..1e3810e7 100644 --- a/src/lang/ts/mod.rs +++ b/src/lang/ts/mod.rs @@ -216,13 +216,26 @@ pub(crate) fn datatype_inner(ctx: ExportContext, typ: &DataType, type_map: &Type } // We use `T[]` instead of `Array` to avoid issues with circular references. DataType::List(def) => { - let dt = datatype_inner(ctx, def, type_map)?; - if (dt.contains(' ') && !dt.ends_with('}')) + let dt = datatype_inner(ctx, &def.ty, type_map)?; + let dt = if (dt.contains(' ') && !dt.ends_with('}')) // This is to do with maintaining order of operations. // Eg `{} | {}` must be wrapped in parens like `({} | {})[]` but `{}` doesn't cause `{}[]` is valid || (dt.contains(' ') && (dt.contains("&") || dt.contains("|"))) { - format!("({dt})[]") + format!("({dt})") + } else { + format!("{dt}") + }; + + if let Some(length) = def.length { + format!( + "[{}]", + (0..length) + .into_iter() + .map(|_| dt.clone()) + .collect::>() + .join(", ") + ) } else { format!("{dt}[]") } diff --git a/src/serde.rs b/src/serde.rs index e3c48e2b..c77b1a91 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -2,7 +2,7 @@ use thiserror::Error; use crate::{ internal::{skip_fields, skip_fields_named}, - DataType, EnumRepr, EnumType, EnumVariants, GenericType, LiteralType, PrimitiveType, + DataType, EnumRepr, EnumType, EnumVariants, GenericType, List, LiteralType, PrimitiveType, StructFields, TypeMap, }; @@ -247,7 +247,10 @@ fn validate_internally_tag_enum_datatype( fn resolve_generics(mut dt: DataType, generics: &Vec<(GenericType, DataType)>) -> DataType { match dt { DataType::Primitive(_) | DataType::Literal(_) | DataType::Any | DataType::Unknown => dt, - DataType::List(v) => DataType::List(Box::new(resolve_generics(*v, generics))), + DataType::List(v) => DataType::List(List { + ty: Box::new(resolve_generics(*v.ty, generics)), + length: v.length, + }), DataType::Nullable(v) => DataType::Nullable(Box::new(resolve_generics(*v, generics))), DataType::Map(v) => DataType::Map(Box::new({ let (k, v) = *v; diff --git a/src/type/impls.rs b/src/type/impls.rs index 8954efc4..f924b541 100644 --- a/src/type/impls.rs +++ b/src/type/impls.rs @@ -122,7 +122,45 @@ impl<'a, T: Type> Type for &'a [T] { impl Type for [T; N] { fn inline(opts: DefOpts, generics: &[DataType]) -> DataType { - >::inline(opts, generics) + DataType::List(List { + ty: Box::new( + // TODO: This is cursed. Fix it properly!!! + match Vec::::inline( + DefOpts { + parent_inline: opts.parent_inline, + type_map: opts.type_map, + }, + generics, + ) { + DataType::List(List { ty, .. }) => *ty, + _ => unreachable!(), + }, + ), + length: Some(N), + }) + } + + fn reference(opts: DefOpts, generics: &[DataType]) -> Reference { + Reference { + inner: DataType::List(List { + ty: Box::new( + // TODO: This is cursed. Fix it properly!!! + match Vec::::reference( + DefOpts { + parent_inline: opts.parent_inline, + type_map: opts.type_map, + }, + generics, + ) + .inner + { + DataType::List(List { ty, .. }) => *ty, + _ => unreachable!(), + }, + ), + length: Some(N), + }), + } } } diff --git a/src/type/macros.rs b/src/type/macros.rs index e53895ee..cc68d433 100644 --- a/src/type/macros.rs +++ b/src/type/macros.rs @@ -60,7 +60,6 @@ macro_rules! impl_containers { inner: generics.get(0).cloned().unwrap_or_else( || T::reference(opts, generics).inner, ), - _priv: (), } } } @@ -100,18 +99,23 @@ macro_rules! impl_for_list { ($($ty:path as $name:expr)+) => {$( impl Type for $ty { fn inline(opts: DefOpts, generics: &[DataType]) -> DataType { - DataType::List(Box::new(generics.get(0).cloned().unwrap_or_else(|| T::inline( - opts, - generics, - )))) + DataType::List(List { + ty: Box::new(generics.get(0).cloned().unwrap_or_else(|| T::inline( + opts, + generics, + ))), + length: None, + }) } fn reference(opts: DefOpts, generics: &[DataType]) -> Reference { Reference { - inner: DataType::List(Box::new(generics.get(0).cloned().unwrap_or_else( - || T::reference(opts, generics).inner, - ))), - _priv: (), + inner: DataType::List(List { + ty: Box::new(generics.get(0).cloned().unwrap_or_else( + || T::reference(opts, generics).inner, + )), + length: None, + }), } } } @@ -168,7 +172,6 @@ macro_rules! impl_for_map { .inner }), ))), - _priv: (), } } } diff --git a/src/type/mod.rs b/src/type/mod.rs index 1937fd82..a476cedd 100644 --- a/src/type/mod.rs +++ b/src/type/mod.rs @@ -83,15 +83,14 @@ pub mod reference { /// A reference datatype. /// // This type exists to force the user to use [reference::inline] or [reference::reference] which provides some extra safety. + #[non_exhaustive] pub struct Reference { pub inner: DataType, - pub(crate) _priv: (), } pub fn inline(opts: DefOpts, generics: &[DataType]) -> Reference { Reference { inner: T::inline(opts, generics), - _priv: (), } } @@ -115,7 +114,6 @@ pub mod reference { Reference { inner: DataType::Reference(reference), - _priv: (), } } } diff --git a/tests/const_types.rs b/tests/const_types.rs new file mode 100644 index 00000000..9fa6f68d --- /dev/null +++ b/tests/const_types.rs @@ -0,0 +1,8 @@ +use crate::ts::assert_ts; + +#[test] +fn const_types() { + assert_ts!((String, String), "[string, string]"); + assert_ts!([String; 5], "[string, string, string, string, string]"); + assert_ts!([String; 0], "[]"); +} diff --git a/tests/lib.rs b/tests/lib.rs index cc146a5b..ede0db92 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -3,6 +3,7 @@ mod advanced_types; mod bigints; mod comments; +mod const_types; mod datatype; mod deprecated; mod duplicate_ty_name; diff --git a/tests/ts.rs b/tests/ts.rs index 3cc1b7d5..d564d5d2 100644 --- a/tests/ts.rs +++ b/tests/ts.rs @@ -97,7 +97,7 @@ fn typescript_types() { assert_ts!(Vec, "number[]"); assert_ts!(&[i32], "number[]"); - assert_ts!(&[i32; 5], "number[]"); + assert_ts!(&[i32; 3], "[number, number, number]"); assert_ts!(Option, "number | null"); @@ -177,7 +177,10 @@ fn typescript_types() { // assert_ts!(() => ..=5, r#"{ end: 5 }"#); // https://github.com/oscartbeaumont/specta/issues/66 - assert_ts!([Option; 16], r#"(number | null)[]"#); + assert_ts!( + [Option; 3], + r#"[(number | null), (number | null), (number | null)]"# + ); // https://github.com/oscartbeaumont/specta/issues/65 assert_ts!(HashMap, r#"{ [key in "A" | "B"]: null }"#); diff --git a/tests/ts_rs/arrays.rs b/tests/ts_rs/arrays.rs index 779393d5..025f06cd 100644 --- a/tests/ts_rs/arrays.rs +++ b/tests/ts_rs/arrays.rs @@ -3,7 +3,7 @@ use specta::Type; #[test] fn free() { - assert_ts!([String; 10], "string[]") + assert_ts!([String; 3], "[string, string, string]") } #[test] @@ -12,17 +12,17 @@ fn interface() { #[specta(export = false)] struct Interface { #[allow(dead_code)] - a: [i32; 10], + a: [i32; 3], } - assert_ts!(Interface, "{ a: number[] }") + assert_ts!(Interface, "{ a: [number, number, number] }") } #[test] fn newtype() { #[derive(Type)] #[specta(export = false)] - struct Newtype(#[allow(dead_code)] [i32; 10]); + struct Newtype(#[allow(dead_code)] [i32; 3]); - assert_ts!(Newtype, "number[]") + assert_ts!(Newtype, "[number, number, number]") } diff --git a/tests/ts_rs/generic_fields.rs b/tests/ts_rs/generic_fields.rs index 04508758..4566794b 100644 --- a/tests/ts_rs/generic_fields.rs +++ b/tests/ts_rs/generic_fields.rs @@ -44,7 +44,7 @@ fn named() { } assert_ts!( Struct1, - "{ a: string[]; b: [string[], string[]]; c: string[][] }" + "{ a: string[]; b: [string[], string[]]; c: [string[], string[], string[]] }" ); } @@ -59,7 +59,7 @@ fn named_nested() { } assert_ts!( Struct2, - "{ a: string[][]; b: [string[][], string[][]]; c: string[][][] }" + "{ a: string[][]; b: [string[][], string[][]]; c: [string[][], string[][], string[][]] }" ); } @@ -68,7 +68,10 @@ fn tuple() { #[derive(Type)] #[specta(export = false)] struct Tuple1(Vec, (Vec, Vec), [Vec; 3]); - assert_ts!(Tuple1, "[number[], [number[], number[]], number[][]]"); + assert_ts!( + Tuple1, + "[number[], [number[], number[]], [number[], number[], number[]]]" + ); } #[test] @@ -82,6 +85,6 @@ fn tuple_nested() { ); assert_ts!( Tuple2, - "[number[][], [number[][], number[][]], number[][][]]" + "[number[][], [number[][], number[][]], [number[][], number[][], number[][]]]" ); } diff --git a/tests/ts_rs/generics.rs b/tests/ts_rs/generics.rs index 416d1b70..19990819 100644 --- a/tests/ts_rs/generics.rs +++ b/tests/ts_rs/generics.rs @@ -116,7 +116,7 @@ fn generic_struct() { assert_ts_export!( GenericStruct2::<()>, - "export type GenericStruct2 = { a: T; b: [T, T]; c: [T, [T, T]]; d: T[]; e: ([T, T])[]; f: T[]; g: T[][]; h: (([T, T])[])[] }" + "export type GenericStruct2 = { a: T; b: [T, T]; c: [T, [T, T]]; d: [T, T, T]; e: [([T, T]), ([T, T]), ([T, T])]; f: T[]; g: T[][]; h: ([([T, T]), ([T, T]), ([T, T])])[] }" ) }