Skip to content

Commit

Permalink
wip: rewrite specta-typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Jan 20, 2025
1 parent cb678cf commit 76e82e4
Show file tree
Hide file tree
Showing 26 changed files with 718 additions and 1,186 deletions.
67 changes: 33 additions & 34 deletions specta-jsdoc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
html_favicon_url = "https://github.com/oscartbeaumont/specta/raw/main/.github/logo-128.png"
)]

use std::{borrow::Cow, path::Path};
use std::borrow::Cow;

use specta::{Language, TypeCollection};
use specta_typescript::{BigIntExportBehavior, CommentFormatterFn, FormatterFn};

// TODO: Ensure this is up to our `Typescript` exporters standards.
Expand Down Expand Up @@ -68,35 +67,35 @@ impl JSDoc {
}
}

impl Language for JSDoc {
type Error = specta_typescript::ExportError; // TODO: Custom error type

// TODO: Make this properly export JSDoc
fn export(&self, _types: &TypeCollection) -> Result<String, Self::Error> {
todo!("Coming soon...");
// let mut out = self.0.header.to_string();
// if !self.0.remove_default_header {
// out += "// This file has been generated by Specta. DO NOT EDIT.\n\n";
// }

// if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&types).into_iter().next() {
// return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
// }

// for (_, ty) in types.iter() {
// is_valid_ty(&ty.inner, &types)?;

// out += &export_named_datatype(&self.0, ty, &types)?;
// out += "\n\n";
// }

// Ok(out)
}

fn format(&self, path: &Path) -> Result<(), Self::Error> {
if let Some(formatter) = self.0.formatter {
formatter(path)?;
}
Ok(())
}
}
// impl Language for JSDoc {
// type Error = specta_typescript::ExportError; // TODO: Custom error type

// // TODO: Make this properly export JSDoc
// fn export(&self, _types: &TypeCollection) -> Result<String, Self::Error> {
// todo!("Coming soon...");
// // let mut out = self.0.header.to_string();
// // if !self.0.remove_default_header {
// // out += "// This file has been generated by Specta. DO NOT EDIT.\n\n";
// // }

// // if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&types).into_iter().next() {
// // return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
// // }

// // for (_, ty) in types.iter() {
// // is_valid_ty(&ty.inner, &types)?;

// // out += &export_named_datatype(&self.0, ty, &types)?;
// // out += "\n\n";
// // }

// // Ok(out)
// }

// fn format(&self, path: &Path) -> Result<(), Self::Error> {
// if let Some(formatter) = self.0.formatter {
// formatter(path)?;
// }
// Ok(())
// }
// }
1 change: 1 addition & 0 deletions specta-macros/src/type/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub fn parse_enum(
Ok(construct_field(crate_ref, container_attrs, FieldAttr {
rename: field_attrs.rename,
r#type: field_attrs.r#type,
// TOOD: Should we check container too?
inline: container_attrs.inline || field_attrs.inline || attrs.inline,
skip: field_attrs.skip || attrs.skip,
optional: field_attrs.optional,
Expand Down
1 change: 1 addition & 0 deletions specta-macros/src/type/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub fn construct_field(crate_ref: &TokenStream, container_attrs: &ContainerAttr,
let deprecated = attrs.common.deprecated_as_tokens(crate_ref);
let optional = attrs.optional;
let doc = attrs.common.doc;
// TODO: Check container inline?
let inline = container_attrs.inline || attrs.inline;

// Skip must be handled by the macro so that we don't try and constrain the inner type to `Type` or `Flatten` traits.
Expand Down
85 changes: 40 additions & 45 deletions specta-macros/src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,54 +118,41 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
quote!(#(#g),*)
};

// When inlining we don't implement `NamedType` and we don't register into the type collection.
let inner = (!container_attrs.inline).then(|| {
let generic_placeholders = generics.params.iter().filter_map(|param| match param {
GenericParam::Lifetime(_) | GenericParam::Const(_) => None,
GenericParam::Type(t) => {
let ident = format_ident!("PLACEHOLDER_{}", t.ident);
let ident_str = t.ident.to_string();
Some(quote!(
pub struct #ident;
impl #crate_ref::datatype::GenericPlaceholder for #ident {
const PLACEHOLDER: &'static str = #ident_str;
}
))
},
});

let export = (cfg!(feature = "DO_NOT_USE_export") && container_attrs.export.unwrap_or(true))
.then(|| {
let export_fn_name = format_ident!("__push_specta_type_{}", raw_ident);

let generic_params = generics
.params
.iter()
.filter(|param| matches!(param, syn::GenericParam::Type(_)))
.map(|_| quote! { () });

quote! {
#[allow(non_snake_case)]
#[#crate_ref::export::internal::ctor]
fn #export_fn_name() {
#crate_ref::export::internal::register::<#ident<#(#generic_params),*>>();
}
let generic_placeholders = generics.params.iter().filter_map(|param| match param {
GenericParam::Lifetime(_) | GenericParam::Const(_) => None,
GenericParam::Type(t) => {
let ident = format_ident!("PLACEHOLDER_{}", t.ident);
let ident_str = t.ident.to_string();
Some(quote!(
pub struct #ident;
impl #crate_ref::datatype::GenericPlaceholder for #ident {
const PLACEHOLDER: &'static str = #ident_str;
}
});

quote!(
#(#generic_placeholders)*
))
},
});

#[automatically_derived]
impl #bounds #crate_ref::NamedType for #ident #type_args #where_bound {
const ID: #crate_ref::SpectaID = SID;
let export = (cfg!(feature = "DO_NOT_USE_export") && container_attrs.export.unwrap_or(true))
.then(|| {
let export_fn_name = format_ident!("__push_specta_type_{}", raw_ident);

let generic_params = generics
.params
.iter()
.filter(|param| matches!(param, syn::GenericParam::Type(_)))
.map(|_| quote! { () });

quote! {
#[allow(non_snake_case)]
#[#crate_ref::export::internal::ctor]
fn #export_fn_name() {
#crate_ref::export::internal::register::<#ident<#(#generic_params),*>>();
}
}

#export
)
}).unwrap_or_default();
});

let comments = &container_attrs.common.doc;
let inline = container_attrs.inline;
let deprecated = container_attrs.common.deprecated_as_tokens(&crate_ref);
let impl_location = quote!(#crate_ref::internal::construct::impl_location(concat!(file!(), ":", line!(), ":", column!())));
let definition = (container_attrs.inline || container_attrs.transparent).then(|| quote!(
Expand All @@ -183,10 +170,11 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt

quote!(
let dt = Self::___specta_definition___(types);
specta::datatype::reference::Reference::construct(SID, vec![#(#reference_generics),*], dt).into()
specta::datatype::reference::Reference::construct(SID, vec![#(#reference_generics),*], dt, #inline).into()
)
});


Ok(quote! {
#[allow(non_camel_case_types)]
const _: () = {
Expand All @@ -195,6 +183,8 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
// This is equivalent to `<Self as #crate_ref::NamedType>::ID` but it's shorter so we use it instead.
const SID: #crate_ref::SpectaID = #crate_ref::internal::construct::sid(#name, concat!("::", module_path!(), ":", line!(), ":", column!()));

#(#generic_placeholders)*

// TODO: We should make this a standalone function but that caused issues resolving lifetimes.
#[automatically_derived]
impl #bounds #ident #type_args #where_bound {
Expand All @@ -220,9 +210,14 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
}
}

#inner
#[automatically_derived]
impl #bounds #crate_ref::NamedType for #ident #type_args #where_bound {
const ID: #crate_ref::SpectaID = SID;
}

#flatten_impl

#export
};

}.into())
Expand Down
37 changes: 21 additions & 16 deletions specta-macros/src/type/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,27 @@ pub fn parse_struct(
let (field_ty, field_attrs) = fields.into_iter().next().expect("fields.len() != 1");
let field_ty = field_attrs.r#type.as_ref().unwrap_or(&field_ty);

if container_attrs.inline || field_attrs.inline {
// TODO: Duplicate of code in `field.rs` we should refactor out into helper.
let generics = generics.params.iter().filter_map(|p| match p {
GenericParam::Const(..) | GenericParam::Lifetime(..) => None,
GenericParam::Type(p) => {
let ident = &p.ident;
let ident_str = p.ident.to_string();

quote!((std::borrow::Cow::Borrowed(#ident_str).into(), <#ident as #crate_ref::Type>::definition(types))).into()
}
});

quote!(#crate_ref::datatype::inline::<#field_ty>(types))
} else {
quote!(<#field_ty as #crate_ref::Type>::definition(types))
}
// TODO: Should we check container too?
// if container_attrs.inline || field_attrs.inline {
// // TODO: Duplicate of code in `field.rs` we should refactor out into helper.
// // let generics = generics.params.iter().filter_map(|p| match p {
// // GenericParam::Const(..) | GenericParam::Lifetime(..) => None,
// // GenericParam::Type(p) => {
// // let ident = &p.ident;
// // let ident_str = p.ident.to_string();

// // quote!((std::borrow::Cow::Borrowed(#ident_str).into(), <#ident as #crate_ref::Type>::definition(types))).into()
// // }
// // });

// // quote!(#crate_ref::datatype::inline::<#field_ty>(types))
// todo!();
// } else {
// quote!(<#field_ty as #crate_ref::Type>::definition(types))
// }

// TODO: How can we passthrough the inline to this reference?
quote!(<#field_ty as #crate_ref::Type>::definition(types))
} else {
let fields = match &data.fields {
Fields::Named(_) => {
Expand Down
10 changes: 2 additions & 8 deletions specta-macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use proc_macro2::Span;
use quote::ToTokens;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
Expand Down Expand Up @@ -289,9 +289,3 @@ impl Inflection {
pub fn format_fn_wrapper(function: &Ident) -> Ident {
quote::format_ident!("__specta__fn__{}", function)
}

pub fn then_option(condition: bool, inner: TokenStream) -> TokenStream {
condition
.then(|| quote!(None))
.unwrap_or_else(|| quote!(Some(#inner)))
}
91 changes: 70 additions & 21 deletions specta-typescript/examples/debug.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,89 @@
use std::collections::HashMap;

use specta::Type;
use specta::{Type, NamedType, TypeCollection};

#[derive(Type)]
#[specta(export = false, transparent)]
pub struct MaybeValidKey<T>(T);
// #[derive(Type)]
// #[specta(export = false, transparent)]
// pub struct MaybeValidKey<T>(T);

// #[derive(Type)]
// #[specta(export = false, transparent)]
// pub struct ValidMaybeValidKeyNested(HashMap<MaybeValidKey<MaybeValidKey<String>>, ()>);

// #[derive(Type)]
// #[specta(export = false)]
// pub struct Todo<T> {
// pub field: T,
// }

#[derive(Type)]
#[specta(export = false, transparent)]
pub struct ValidMaybeValidKeyNested(HashMap<MaybeValidKey<MaybeValidKey<String>>, ()>);

// #[derive(Type)]
// #[specta(export = false)]
// pub struct Todo2<A, B, C> {
// pub a: A,
// pub b: B,
// pub c: C,
// }

// #[derive(Type)]
// #[specta(export = false)]
// pub struct Test {
// // #[specta(inline)]
// pub root: Todo<Todo<String>>,
// }

#[derive(Type)]
#[specta(export = false)]
pub struct Todo<T> {
pub field: T,
pub struct Demo {
pub a: String,
}

#[derive(Type)]
#[specta(inline)]
pub struct MeNeedInline {
#[specta(inline)]
pub a: Demo
}

#[derive(Type)]
#[specta(export = false)]
pub struct Todo2<A, B, C> {
pub a: A,
pub b: B,
pub c: C,
pub struct Generic<T, U> {
pub a: T,
pub b: U
}

#[derive(Type)]
#[specta(export = false)]
pub struct Test {
// #[specta(inline)]
pub root: Todo<Todo<String>>,
pub enum Hello {
A,
B(String),
C {
a: String,
}
}

fn main() {
println!("{:?}\n\n", specta_typescript::inline::<Todo<String>>(&Default::default()));
println!("{:?}\n\n", specta_typescript::export::<Todo<String>>(&Default::default()));
// println!("{:?}\n\n", specta_typescript::inline::<MeNeedInline>(&Default::default()));
// println!("{:?}\n\n", specta_typescript::export::<MeNeedInline>(&Default::default()));

println!("Debug");

let i = std::time::Instant::now();
let mut types = TypeCollection::default();
let dt = MeNeedInline::definition(&mut types);
println!("A {:?}", i.elapsed());
println!("{:?}", specta_typescript::primitives::inline(&Default::default(), &types, &dt));

let i = std::time::Instant::now();
println!("{:?}", specta_typescript::primitives::export(&Default::default(), &types, types.get(MeNeedInline::ID).unwrap()));
println!("B {:?}", i.elapsed());

Generic::<String, i32>::definition(&mut types);
println!("{:?}", specta_typescript::primitives::export(&Default::default(), &types, types.get(Generic::<String, i32>::ID).unwrap()));

Hello::definition(&mut types);
println!("{:?}", specta_typescript::primitives::export(&Default::default(), &types, types.get(Hello::ID).unwrap()));


// println!("{:?}\n\n", specta_typescript::inline::<Todo<String>>(&Default::default()));
// println!("{:?}\n\n", specta_typescript::export::<Todo<String>>(&Default::default()));

// println!("{:?}\n\n", specta_typescript::inline::<Test>(&Default::default()));
// println!("{:?}\n\n", specta_typescript::export::<Test>(&Default::default()));
Expand Down
Loading

0 comments on commit 76e82e4

Please sign in to comment.