Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce TypeList, always export Dependencies as well #221

Merged
merged 8 commits into from
Feb 1, 2024
41 changes: 35 additions & 6 deletions macros/src/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@ use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::Type;

#[derive(Default)]
pub struct Dependencies(Vec<TokenStream>);
pub struct Dependencies {
v0: Vec<TokenStream>,
v1: Vec<TokenStream>
}

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 {
Expand All @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -95,12 +96,21 @@ impl DerivedTS {
#inline
}
#inline_flattened

fn dependencies() -> Vec<ts_rs::Dependency>
where
Self: 'static,
{
#dependencies
}

fn dependency_types() -> impl ts_rs::typelist::TypeList
where
Self: 'static,
{
#dependencies_v2
}

fn transparent() -> bool {
false
}
Expand Down
134 changes: 109 additions & 25 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
#[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.
Expand Down Expand Up @@ -289,11 +290,34 @@
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<Dependency>
where
Self: 'static;
where
Self: 'static;

//fn dependencies() -> Vec<Dependency> where Self: 'static {
// use crate::typelist::{TypeList, TypeVisitor};
//
// let mut deps: Vec<Dependency> = vec![];
// struct Visit<'a>(&'a mut Vec<Dependency>);
// impl<'a> TypeVisitor for Visit<'a> {
// fn visit<T: TS + 'static + ?Sized>(&mut self) {
// if let Some(dep) = Dependency::from_ty::<T>() {
// self.0.push(dep);
// }
// }
// }
// Self::dependency_types().for_each(&mut Visit(&mut deps));
//
// deps
//}
NyxCode marked this conversation as resolved.
Show resolved Hide resolved

fn _push_to(l: impl TypeList) -> impl TypeList where Self: 'static {
l.push::<Self>()
}

/// `true` if this is a transparent type, e.g tuples or a list.
/// This is used for resolving imports when using the `export!` macro.
Expand Down Expand Up @@ -384,14 +408,20 @@
fn inline() -> String {
format!("[{}]", [$($i::inline()),*].join(", "))
}
fn dependency_types() -> impl TypeList
where
Self: 'static
{
()$(.push::<$i>())*
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static
{
[$( Dependency::from_ty::<$i>() ),*]
.into_iter()
.flatten()
.collect()
.into_iter()
.flatten()
.collect()
}
fn transparent() -> bool { true }
}
Expand All @@ -414,6 +444,12 @@
}
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<Dependency>
where
Self: 'static
Expand All @@ -433,7 +469,13 @@
fn name_with_type_args(args: Vec<String>) -> 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

Check failure on line 472 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find trait `TypeList` in this scope

Check failure on line 472 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find trait `TypeList` in this scope

Check failure on line 472 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find trait `TypeList` in this scope

Check failure on line 472 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find trait `TypeList` in this scope
where
Self: 'static
{
<$s>::dependency_types()
}
fn dependencies() -> Vec<Dependency>

Check failure on line 478 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find type `Dependency` in this scope

Check failure on line 478 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find type `Dependency` in this scope

Check failure on line 478 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find type `Dependency` in this scope

Check failure on line 478 in ts-rs/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test ts-rs

cannot find type `Dependency` in this scope
where
Self: 'static
{
Expand Down Expand Up @@ -463,11 +505,17 @@
format!("{} | null", T::inline())
}

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

fn transparent() -> bool {
Expand All @@ -482,9 +530,15 @@
fn inline() -> String {
format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline())
}
fn dependency_types() -> impl TypeList
where
Self: 'static
{
().push::<T>().push::<E>()
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
where
Self: 'static
{
[Dependency::from_ty::<T>(), Dependency::from_ty::<E>()]
.into_iter()
Expand All @@ -505,13 +559,18 @@
format!("Array<{}>", T::inline())
}

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

fn transparent() -> bool {
true
}
Expand Down Expand Up @@ -557,9 +616,15 @@
)
}

fn dependency_types() -> impl TypeList
where
Self: 'static
{
().push::<T>()
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
where
Self: 'static
{
[Dependency::from_ty::<T>()].into_iter().flatten().collect()
}
Expand Down Expand Up @@ -588,9 +653,15 @@
format!("Record<{}, {}>", K::inline(), V::inline())
}

fn dependency_types() -> impl TypeList
where
Self: 'static
{
().push::<K>().push::<V>()
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
where
Self: 'static
{
[Dependency::from_ty::<K>(), Dependency::from_ty::<V>()]
.into_iter()
Expand Down Expand Up @@ -618,9 +689,15 @@
format!("{{ start: {}, end: {}, }}", &args[0], &args[0])
}

fn dependency_types() -> impl TypeList
where
Self: 'static
{
().push::<I>()
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
where
Self: 'static
{
[Dependency::from_ty::<I>()].into_iter().flatten().collect()
}
Expand All @@ -645,9 +722,15 @@
format!("{{ start: {}, end: {}, }}", &args[0], &args[0])
}

fn dependency_types() -> impl TypeList
where
Self: 'static
{
().push::<I>()
}
fn dependencies() -> Vec<Dependency>
where
Self: 'static,
where
Self: 'static
{
[Dependency::from_ty::<I>()].into_iter().flatten().collect()
}
Expand All @@ -657,7 +740,7 @@
}
}

impl_shadow!(as T: impl<'a, T: TS + ?Sized> TS for &T);
impl_shadow!(as T: impl<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>);
Expand Down Expand Up @@ -727,3 +810,4 @@
}
#[rustfmt::skip]
pub(crate) use impl_primitives;
use crate::typelist::TypeList;
47 changes: 47 additions & 0 deletions ts-rs/src/typelist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::any::TypeId;
use std::marker::PhantomData;

use crate::TS;

pub trait TypeVisitor: Sized {
fn visit<T: TS + 'static + ?Sized>(&mut self);
}


pub trait TypeList: Copy + Clone {
fn push<T: TS + 'static + ?Sized>(self) -> impl TypeList {
(self, (PhantomData::<T>,))
}
fn extend(self, l: impl TypeList) -> impl TypeList {
(self, l)
}

fn contains<C: Sized + 'static>(self) -> bool;
fn for_each(self, v: &mut impl TypeVisitor);
}

impl TypeList for () {
fn contains<C: Sized>(self) -> bool { false }
fn for_each(self, _: &mut impl TypeVisitor) {}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty TypeList is just (). That's why TS::dependency_types() -> impl TypeList can have a default implementation with an empty body, since that evaluates to ()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


impl<T> TypeList for (PhantomData<T>,) where T: TS + 'static + ?Sized {
fn contains<C: Sized + 'static>(self) -> bool {
TypeId::of::<C>() == TypeId::of::<T>()
}

fn for_each(self, v: &mut impl TypeVisitor) {
v.visit::<T>();
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TypeList with just one element is represented by the tuple (PhantomData<T>,).


impl<A, B> TypeList for (A, B) where A: TypeList, B: TypeList {
fn contains<C: Sized + 'static>(self) -> bool {
self.0.contains::<C>() || self.1.contains::<C>()
}

fn for_each(self, v: &mut impl TypeVisitor) {
self.0.for_each(v);
self.1.for_each(v);
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally, here is where the induction / recursion is happening:
For any two TypeLists A and B, (A, B) is a TypeList as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's honestly the most clever use of the type system I've seen! Especially given how Rust usually doesn't like recursive data structures

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 I think I've done much more insane things. For example, you can do real arithmetic in in the type system alone

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading
Loading