Skip to content

Commit

Permalink
[T; N] as Typescript tuple
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Nov 22, 2023
1 parent 0470a6a commit 7039fba
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 32 deletions.
19 changes: 19 additions & 0 deletions src/datatype/list.rs
Original file line number Diff line number Diff line change
@@ -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<DataType>,
// Length is set for `[Type; N]` arrays.
pub(crate) length: Option<usize>,
}

impl List {
pub fn ty(&self) -> &DataType {
&self.ty
}

pub fn length(&self) -> Option<usize> {
self.length
}
}
4 changes: 3 additions & 1 deletion src/datatype/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use std::{

mod r#enum;
mod fields;
mod list;
mod literal;
mod named;
mod primitive;
mod r#struct;
mod tuple;

pub use fields::*;
pub use list::*;
pub use literal::*;
pub use named::*;
pub use primitive::*;
Expand Down Expand Up @@ -47,7 +49,7 @@ pub enum DataType {
Primitive(PrimitiveType),
Literal(LiteralType),
/// Either a `Set` or a `Vec`
List(Box<DataType>),
List(List),
Nullable(Box<DataType>),
Map(Box<(DataType, DataType)>),
// Anonymous Reference types
Expand Down
19 changes: 16 additions & 3 deletions src/lang/ts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,26 @@ pub(crate) fn datatype_inner(ctx: ExportContext, typ: &DataType, type_map: &Type
}
// We use `T[]` instead of `Array<T>` 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("|")))

Check warning on line 223 in src/lang/ts/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

single-character string constant used as pattern

warning: single-character string constant used as pattern --> src/lang/ts/mod.rs:223:74 | 223 | || (dt.contains(' ') && (dt.contains("&") || dt.contains("|"))) | ^^^ help: try using a `char` instead: `'|'` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern

Check warning on line 223 in src/lang/ts/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

single-character string constant used as pattern

warning: single-character string constant used as pattern --> src/lang/ts/mod.rs:223:54 | 223 | || (dt.contains(' ') && (dt.contains("&") || dt.contains("|"))) | ^^^ help: try using a `char` instead: `'&'` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern = note: `#[warn(clippy::single_char_pattern)]` implied by `#[warn(clippy::all)]`
{
format!("({dt})[]")
format!("({dt})")
} else {
format!("{dt}")

Check warning on line 227 in src/lang/ts/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

useless use of `format!`

warning: useless use of `format!` --> src/lang/ts/mod.rs:227:17 | 227 | format!("{dt}") | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `dt.to_string()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_format = note: `#[warn(clippy::useless_format)]` implied by `#[warn(clippy::all)]`
};

if let Some(length) = def.length {
format!(
"[{}]",
(0..length)
.into_iter()

Check warning on line 234 in src/lang/ts/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

useless conversion to the same type: `std::ops::Range<usize>`

warning: useless conversion to the same type: `std::ops::Range<usize>` --> src/lang/ts/mod.rs:233:21 | 233 | / (0..length) 234 | | .into_iter() | |____________________________________^ help: consider removing `.into_iter()`: `(0..length)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion = note: `#[warn(clippy::useless_conversion)]` implied by `#[warn(clippy::all)]`
.map(|_| dt.clone())
.collect::<Vec<_>>()
.join(", ")
)
} else {
format!("{dt}[]")
}
Expand Down
7 changes: 5 additions & 2 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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;
Expand Down
40 changes: 39 additions & 1 deletion src/type/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,45 @@ impl<'a, T: Type> Type for &'a [T] {

impl<const N: usize, T: Type> Type for [T; N] {
fn inline(opts: DefOpts, generics: &[DataType]) -> DataType {
<Vec<T>>::inline(opts, generics)
DataType::List(List {
ty: Box::new(
// TODO: This is cursed. Fix it properly!!!
match Vec::<T>::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::<T>::reference(
DefOpts {
parent_inline: opts.parent_inline,
type_map: opts.type_map,
},
generics,
)
.inner
{
DataType::List(List { ty, .. }) => *ty,
_ => unreachable!(),
},
),
length: Some(N),
}),
}
}
}

Expand Down
23 changes: 13 additions & 10 deletions src/type/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ macro_rules! impl_containers {
inner: generics.get(0).cloned().unwrap_or_else(
|| T::reference(opts, generics).inner,
),
_priv: (),
}
}
}
Expand Down Expand Up @@ -100,18 +99,23 @@ macro_rules! impl_for_list {
($($ty:path as $name:expr)+) => {$(
impl<T: Type> 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,
}),
}
}
}
Expand Down Expand Up @@ -168,7 +172,6 @@ macro_rules! impl_for_map {
.inner
}),
))),
_priv: (),
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Type + ?Sized>(opts: DefOpts, generics: &[DataType]) -> Reference {
Reference {
inner: T::inline(opts, generics),
_priv: (),
}
}

Expand All @@ -115,7 +114,6 @@ pub mod reference {

Reference {
inner: DataType::Reference(reference),
_priv: (),
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions tests/const_types.rs
Original file line number Diff line number Diff line change
@@ -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], "[]");
}
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod advanced_types;
mod bigints;
mod comments;
mod const_types;
mod datatype;
mod deprecated;
mod duplicate_ty_name;
Expand Down
7 changes: 5 additions & 2 deletions tests/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fn typescript_types() {

assert_ts!(Vec<i32>, "number[]");
assert_ts!(&[i32], "number[]");
assert_ts!(&[i32; 5], "number[]");
assert_ts!(&[i32; 3], "[number, number, number]");

assert_ts!(Option<i32>, "number | null");

Expand Down Expand Up @@ -177,7 +177,10 @@ fn typescript_types() {
// assert_ts!(() => ..=5, r#"{ end: 5 }"#);

// https://github.com/oscartbeaumont/specta/issues/66
assert_ts!([Option<u8>; 16], r#"(number | null)[]"#);
assert_ts!(
[Option<u8>; 3],
r#"[(number | null), (number | null), (number | null)]"#
);

// https://github.com/oscartbeaumont/specta/issues/65
assert_ts!(HashMap<BasicEnum, ()>, r#"{ [key in "A" | "B"]: null }"#);
Expand Down
10 changes: 5 additions & 5 deletions tests/ts_rs/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use specta::Type;

#[test]
fn free() {
assert_ts!([String; 10], "string[]")
assert_ts!([String; 3], "[string, string, string]")
}

#[test]
Expand All @@ -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]")
}
11 changes: 7 additions & 4 deletions tests/ts_rs/generic_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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[]] }"
);
}

Expand All @@ -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[][]] }"
);
}

Expand All @@ -68,7 +68,10 @@ fn tuple() {
#[derive(Type)]
#[specta(export = false)]
struct Tuple1(Vec<i32>, (Vec<i32>, Vec<i32>), [Vec<i32>; 3]);
assert_ts!(Tuple1, "[number[], [number[], number[]], number[][]]");
assert_ts!(
Tuple1,
"[number[], [number[], number[]], [number[], number[], number[]]]"
);
}

#[test]
Expand All @@ -82,6 +85,6 @@ fn tuple_nested() {
);
assert_ts!(
Tuple2,
"[number[][], [number[][], number[][]], number[][][]]"
"[number[][], [number[][], number[][]], [number[][], number[][], number[][]]]"
);
}
2 changes: 1 addition & 1 deletion tests/ts_rs/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn generic_struct() {

assert_ts_export!(
GenericStruct2::<()>,
"export type GenericStruct2<T> = { a: T; b: [T, T]; c: [T, [T, T]]; d: T[]; e: ([T, T])[]; f: T[]; g: T[][]; h: (([T, T])[])[] }"
"export type GenericStruct2<T> = { 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])])[] }"
)
}

Expand Down

0 comments on commit 7039fba

Please sign in to comment.