Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Nov 22, 2023
1 parent 5407716 commit 8397d51
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 32 deletions.
17 changes: 13 additions & 4 deletions macros/src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
});

let comments = &container_attrs.common.doc;
let should_export = match container_attrs.export {
Some(export) => quote!(Some(#export)),
None => quote!(None),
};
let deprecated = container_attrs.common.deprecated_as_tokens(&crate_ref);

let sid = quote!(#crate_ref::internal::construct::sid(#name, concat!("::", module_path!(), ":", line!(), ":", column!())));
Expand All @@ -138,6 +134,16 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
#[automatically_derived]
#type_impl_heading {
fn inline(opts: #crate_ref::DefOpts, generics: &[#crate_ref::DataType]) -> #crate_ref::DataType {
println!("IMPL INLINE {:?} {:?}", SID, opts.type_map.get(&SID));
// if opts.type_map.get(&SID).is_some() {
// todo!();
// }

// if let Some(None) = opts.type_map.get(&SID) {
// todo!();
// }
// opts.type_map.insert(SID, None);

#inlines
}

Expand All @@ -146,6 +152,7 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
}

fn reference(opts: #crate_ref::DefOpts, generics: &[#crate_ref::DataType]) -> #crate_ref::reference::Reference {
println!("IMPL REFERENCE");
#reference
}
}
Expand All @@ -156,6 +163,8 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenSt
const IMPL_LOCATION: #crate_ref::ImplLocation = IMPL_LOCATION;

fn named_data_type(opts: #crate_ref::DefOpts, generics: &[#crate_ref::DataType]) -> #crate_ref::NamedDataType {
println!("NAMED DATA TYPE");

#crate_ref::internal::construct::named_data_type(
#name.into(),
#comments.into(),
Expand Down
10 changes: 5 additions & 5 deletions macros/src/type/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,14 @@ pub fn parse_struct(
).map(|ty| {
let ty = if field_attrs.flatten {
quote! {
#[allow(warnings)]
{
#ty
}

fn validate_flatten<T: #crate_ref::Flatten>() {}
validate_flatten::<#field_ty>();

// if let Some(None) = opts.type_map.get(&SID) {
// todo!();
// }
// opts.type_map.insert(SID, None);

let mut ty = <#field_ty as #crate_ref::Type>::inline(#crate_ref::DefOpts {
parent_inline: #parent_inline,
type_map: opts.type_map
Expand Down
49 changes: 32 additions & 17 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::collections::HashSet;

use thiserror::Error;

use crate::{
internal::{skip_fields, skip_fields_named},
DataType, EnumRepr, EnumType, EnumVariants, GenericType, List, LiteralType, PrimitiveType,
StructFields, TypeMap,
SpectaID, StructFields, TypeMap,
};

// TODO: The error should show a path to the type causing the issue like the BigInt error reporting.
Expand All @@ -22,22 +24,32 @@ pub enum SerdeError {
///
/// This can be used by exporters which wanna do export-time checks that all types are compatible with Serde formats.
pub(crate) fn is_valid_ty(dt: &DataType, type_map: &TypeMap) -> Result<(), SerdeError> {
is_valid_ty_internal(dt, type_map, &mut Default::default())
}

fn is_valid_ty_internal(
dt: &DataType,
type_map: &TypeMap,
checked_references: &mut HashSet<SpectaID>,
) -> Result<(), SerdeError> {
println!("IS VALID {dt:?}");

match dt {
DataType::Nullable(ty) => is_valid_ty(ty, type_map)?,
DataType::Map(ty) => {
is_valid_map_key(&ty.0, type_map)?;
is_valid_ty(&ty.1, type_map)?;
is_valid_ty_internal(&ty.1, type_map, checked_references)?;
}
DataType::Struct(ty) => match ty.fields() {
StructFields::Unit => {}
StructFields::Unnamed(ty) => {
for (_, ty) in skip_fields(ty.fields()) {
is_valid_ty(ty, type_map)?;
is_valid_ty_internal(ty, type_map, checked_references)?;
}
}
StructFields::Named(ty) => {
for (_, (_, ty)) in skip_fields_named(ty.fields()) {
is_valid_ty(ty, type_map)?;
is_valid_ty_internal(ty, type_map, checked_references)?;
}
}
},
Expand All @@ -49,39 +61,42 @@ pub(crate) fn is_valid_ty(dt: &DataType, type_map: &TypeMap) -> Result<(), Serde
EnumVariants::Unit => {}
EnumVariants::Named(variant) => {
for (_, (_, ty)) in skip_fields_named(variant.fields()) {
is_valid_ty(ty, type_map)?;
is_valid_ty_internal(ty, type_map, checked_references)?;
}
}
EnumVariants::Unnamed(variant) => {
for (_, ty) in skip_fields(variant.fields()) {
is_valid_ty(ty, type_map)?;
is_valid_ty_internal(ty, type_map, checked_references)?;
}
}
}
}
}
DataType::Tuple(ty) => {
for ty in ty.elements() {
is_valid_ty(ty, type_map)?;
is_valid_ty_internal(ty, type_map, checked_references)?;
}
}
DataType::Result(ty) => {
is_valid_ty(&ty.0, type_map)?;
is_valid_ty(&ty.1, type_map)?;
is_valid_ty_internal(&ty.0, type_map, checked_references)?;
is_valid_ty_internal(&ty.1, type_map, checked_references)?;
}
DataType::Reference(ty) => {
for (_, generic) in ty.generics() {
is_valid_ty(generic, type_map)?;
is_valid_ty_internal(generic, type_map, checked_references)?;
}

let ty = type_map
.get(&ty.sid)
.as_ref()
.unwrap_or_else(|| panic!("Reference type not found for: {}", ty.sid.type_name))
.as_ref()
.unwrap_or_else(|| panic!("Type '{}' was never populated.", ty.sid.type_name)); // TODO: Error properly
if !checked_references.contains(&ty.sid) {
checked_references.insert(ty.sid);
let ty = type_map
.get(&ty.sid)
.as_ref()
.unwrap_or_else(|| panic!("Reference type not found for: {}", ty.sid.type_name))

Check warning on line 94 in src/serde.rs

View workflow job for this annotation

GitHub Actions / clippy

`panic` should not be present in production code

warning: `panic` should not be present in production code --> src/serde.rs:94:40 | 94 | .unwrap_or_else(|| panic!("Reference type not found for: {}", ty.sid.type_name)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#panic note: the lint level is defined here --> src/lib.rs:3:43 | 3 | #![warn(clippy::all, clippy::unwrap_used, clippy::panic)] // TODO: missing_docs | ^^^^^^^^^^^^^
.as_ref()
.unwrap_or_else(|| panic!("Type '{}' was never populated.", ty.sid.type_name)); // TODO: Error properly

Check warning on line 96 in src/serde.rs

View workflow job for this annotation

GitHub Actions / clippy

`panic` should not be present in production code

warning: `panic` should not be present in production code --> src/serde.rs:96:40 | 96 | .unwrap_or_else(|| panic!("Type '{}' was never populated.", ty.sid.type_name)); // TODO: Error properly | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#panic

is_valid_ty(&ty.inner, type_map)?;
is_valid_ty_internal(&ty.inner, type_map, checked_references)?;
}
}
_ => {}
}
Expand Down
14 changes: 10 additions & 4 deletions src/type/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,18 @@ const _: () = {
impl_for_map!(serde_json::Map<K, V> as "Map");
impl<K: Type, V: Type> Flatten for serde_json::Map<K, V> {}

impl Type for serde_json::Value {
fn inline(_: DefOpts, _: &[DataType]) -> DataType {
DataType::Any
}
#[derive(Type)]
#[specta(remote = serde_json::Value, crate = crate, untagged)]
pub enum JsonValue {
Null(()),
Bool(bool),
Number(serde_json::Number),
String(String),
Array(Vec<serde_json::Value>),
Object(serde_json::Map<String, serde_json::Value>),
}

// TODO: Using remote impl
impl Type for serde_json::Number {
fn inline(_: DefOpts, _: &[DataType]) -> DataType {
DataType::Enum(EnumType {
Expand Down
8 changes: 8 additions & 0 deletions src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub trait NamedType: Type {

/// this is equivalent to [Type::definition] but returns a [NamedDataType] instead.
fn definition_named_data_type(opts: DefOpts) -> NamedDataType {
println!("DEFINITION NAMED DATA TYPE");

Self::named_data_type(
opts,
&Self::definition_generics()
Expand All @@ -89,12 +91,14 @@ pub mod reference {
}

pub fn inline<T: Type + ?Sized>(opts: DefOpts, generics: &[DataType]) -> Reference {
println!("REFERENCE INLINE");
Reference {
inner: T::inline(opts, generics),
}
}

pub fn reference<T: NamedType>(opts: DefOpts, reference: DataTypeReference) -> Reference {
println!("REFERENCE {:?} {:?}", T::SID, opts.type_map.get(&T::SID));
if opts.type_map.get(&T::SID).is_none() {
// It's important we don't put `None` into the map here. By putting a *real* value we ensure that we don't stack overflow for recursive types when calling `named_data_type`.
opts.type_map.entry(T::SID).or_insert(Some(NamedDataType {
Expand All @@ -110,6 +114,10 @@ pub mod reference {
type_map: opts.type_map,
});
opts.type_map.insert(T::SID, Some(dt));
} else {
// let v = opts.type_map.get(&T::SID).unwrap().as_ref().unwrap(); // TODO: Fix this
// println!("V: {:?}", v);
// panic!("{:#?}", backtrace::Backtrace::new());
}

Reference {
Expand Down
2 changes: 1 addition & 1 deletion src/type/post_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn detect_duplicate_type_names(
}
}
}
None => unreachable!(),
None => {} // TODO: unreachable!()},
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/type/specta_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::cmp::Ordering;
/// - `&'a T::SID == &'b T::SID` (unlike std::any::TypeId which forces a static lifetime)
/// - `Box<T> == Arc<T> == Rc<T>` (unlike std::any::TypeId)
///
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Hash)]

Check warning on line 13 in src/type/specta_id.rs

View workflow job for this annotation

GitHub Actions / clippy

you are deriving `Hash` but have implemented `PartialEq` explicitly

warning: you are deriving `Hash` but have implemented `PartialEq` explicitly --> src/type/specta_id.rs:13:30 | 13 | #[derive(Debug, Clone, Copy, Hash)] | ^^^^ | note: `PartialEq` implemented here --> src/type/specta_id.rs:39:1 | 39 | impl PartialEq<Self> for SpectaID { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derived_hash_with_manual_eq = note: `#[warn(clippy::derived_hash_with_manual_eq)]` implied by `#[warn(clippy::all)]` = note: this warning originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
pub struct SpectaID {
pub(crate) type_name: &'static str,
pub(crate) hash: u64,
Expand Down
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod functions;
mod macro_decls;
mod map_keys;
mod optional;
mod recursive;
mod remote_impls;
mod rename;
mod reserved_keywords;
Expand Down
71 changes: 71 additions & 0 deletions tests/recursive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::collections::HashMap;

use specta::{ts::ExportError, SerdeError, Type};

use crate::ts::{assert_ts, assert_ts_export};

#[derive(Type)]
#[specta(export = false)]
pub struct Recursive {
demo: Box<Recursive>,
}

#[derive(Type)]
#[specta(transparent, export = false)]
pub struct RecursiveMapKeyTrick(RecursiveMapKey);

#[derive(Type)]
#[specta(export = false)]
pub struct RecursiveMapKey {
demo: HashMap<RecursiveMapKeyTrick, String>,
}

#[derive(Type)]
#[specta(export = false)]
pub struct RecursiveMapValue {
demo: HashMap<String, RecursiveMapValue>,
}

#[derive(Type)]
#[specta(export = false)]
pub struct RecursiveInline {
#[specta(flatten)]
demo: Box<RecursiveInline>,
}

// #[derive(Type)]
// #[specta(transparent, export = false)]
// pub struct RecursiveTransparent(Box<RecursiveInline>);

// #[derive(Type)]
// #[specta(export = false)]
// pub struct RecursiveMapKey(HashMap<RecursiveMapKey, ()>);

#[test]
fn test_recursive_types() {
assert_ts!(Recursive, "{ demo: Recursive }");
assert_ts_export!(Recursive, "export type Recursive = { demo: Recursive }");

// Just check it doesn't overflow while doing this check
assert_ts!(error; RecursiveMapKey, ExportError::Serde(SerdeError::InvalidMapKey));
assert_ts_export!(
error;
RecursiveMapKey,
ExportError::Serde(SerdeError::InvalidMapKey)
);

assert_ts!(
RecursiveMapValue,
"{ demo: { [key in string]: RecursiveMapValue } }"
);
assert_ts_export!(
RecursiveMapValue,
"export type RecursiveMapValue = { demo: { [key in string]: RecursiveMapValue } }"
);

// assert_ts!(RecursiveInline, "");
// assert_ts_export!(RecursiveInline, "");

// assert_ts!(RecursiveTransparent, "");
// assert_ts_export!(RecursiveTransparent, "");
}
28 changes: 28 additions & 0 deletions tests/serde/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use specta::ts::{BigIntExportBehavior, ExportConfig};

use crate::ts::{assert_ts, assert_ts_export};

#[test]
fn serde_json() {
assert_eq!(
specta::ts::inline::<serde_json::Number>(
&ExportConfig::default().bigint(BigIntExportBehavior::Number)
),
Ok("number".into())
);
assert_ts!(serde_json::Map<String, String>, "{ [key in string]: string }");

assert_eq!(
specta::ts::inline::<serde_json::Value>(
&ExportConfig::default().bigint(BigIntExportBehavior::Number)
),
Ok(
// TODO: Can we have `#[specta(inline = false)]` for this so it's `JsonValue`???
"null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue }"
.into()
)
);

// assert_ts!(serde_json::Value, ""); // TODO: This literally can't work
// assert_ts_export!(serde_json::Value, "");
}
1 change: 1 addition & 0 deletions tests/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ mod empty_enum;
mod empty_struct;
mod externally_tagged;
mod internally_tagged;
mod json;
mod skip;
mod untagged;
10 changes: 10 additions & 0 deletions tests/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,13 @@ pub enum MyEnum {
A(String),
B(u32),
}

#[derive(Type)]
#[specta(transparent, export = false)]
pub struct A(String);

#[derive(Type)]
#[specta(export = false)]
pub struct ExportFalseBroken {
a: A,
}

0 comments on commit 8397d51

Please sign in to comment.