From 6d23517c22ef17961a1bd41fdf8532c9471e1285 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 4 Apr 2024 15:25:38 -0300 Subject: [PATCH 01/10] Infer type in as attribute --- macros/src/attr/struct.rs | 2 +- macros/src/types/mod.rs | 51 ++++++++++++++++++++++++++++++++++++- macros/src/types/named.rs | 7 ++++- macros/src/types/newtype.rs | 7 ++++- macros/src/types/tuple.rs | 7 ++++- ts-rs/Cargo.toml | 1 + ts-rs/tests/infer_as.rs | 48 ++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 ts-rs/tests/infer_as.rs diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index bb8d186b..937c817c 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -35,7 +35,7 @@ impl StructAttr { let docs = parse_docs(attrs)?; result.docs = docs; - + Ok(result) } diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index caebefc0..44ff78b1 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -1,4 +1,8 @@ -use syn::{Fields, Ident, ItemStruct, Result}; +use quote::{quote, ToTokens}; +use syn::{ + Fields, Ident, ItemStruct, Result, Type, TypeArray, TypeParen, TypeReference, TypeSlice, + TypeTuple, +}; use crate::{ attr::{Attr, StructAttr}, @@ -46,3 +50,48 @@ fn type_def(attr: &StructAttr, ident: &Ident, fields: &Fields) -> Result unit::null(attr, &name), } } + +#[allow(unused)] +pub(super) fn type_as_infer(type_as: &Type, original_type: &Type) -> Result { + syn::parse2( + type_as + .to_token_stream() + .into_iter() + .map(|x| { + let ty = syn::parse2::(x.to_token_stream()); + Ok(match ty { + Ok(Type::Infer(_)) => original_type.to_token_stream(), + Ok(Type::Reference(TypeReference { + elem, + lifetime, + and_token, + mutability, + })) => { + let elem = type_as_infer(&elem, original_type)?; + quote!(#and_token #lifetime #mutability #elem) + } + Ok(Type::Array(TypeArray { elem, len, .. })) => { + let elem = type_as_infer(&elem, original_type)?; + quote!([#elem; #len]) + } + Ok(Type::Tuple(TypeTuple { elems, .. })) => { + let elems = elems + .iter() + .map(|x| type_as_infer(x, original_type)) + .collect::>>()?; + quote![(#(#elems),*)] + } + Ok(Type::Slice(TypeSlice { elem, .. })) => { + let elem = type_as_infer(&elem, original_type)?; + quote!([#elem]) + } + Ok(Type::Paren(TypeParen { elem, .. })) => { + let elem = type_as_infer(&elem, original_type)?; + quote![(elem)] + } + y => x.to_token_stream(), + }) + }) + .collect::>()?, + ) +} diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index a5db8992..b882287a 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -4,6 +4,7 @@ use syn::{ spanned::Spanned, Field, FieldsNamed, GenericArgument, Path, PathArguments, Result, Type, }; +use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, Inflection, Optional, StructAttr}, deps::Dependencies, @@ -107,7 +108,11 @@ fn format_field( return Ok(()); } - let parsed_ty = type_as.as_ref().unwrap_or(&field.ty).clone(); + let parsed_ty = type_as + .as_ref() + .map(|ty_as| type_as_infer(ty_as, &field.ty)) + .transpose()? + .unwrap_or_else(|| field.ty.clone()); let (ty, optional_annotation) = match optional { Optional { diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index 9589d05b..601b1d65 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -1,6 +1,7 @@ use quote::quote; use syn::{FieldsUnnamed, Result}; +use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, @@ -27,7 +28,11 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) -> return super::unit::null(attr, name); } - let inner_ty = type_as.as_ref().unwrap_or(&inner.ty).clone(); + let inner_ty = type_as + .as_ref() + .map(|ty_as| type_as_infer(ty_as, &inner.ty)) + .transpose()? + .unwrap_or_else(|| inner.ty.clone()); let mut dependencies = Dependencies::new(crate_rename.clone()); diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index 2b3f44a1..996a95d3 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -2,6 +2,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Field, FieldsUnnamed, Path, Result}; +use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, @@ -64,7 +65,11 @@ fn format_field( return Ok(()); } - let ty = type_as.as_ref().unwrap_or(&field.ty).clone(); + let ty = type_as + .as_ref() + .map(|ty_as| type_as_infer(ty_as, &field.ty)) + .transpose()? + .unwrap_or_else(|| field.ty.clone()); formatted_fields.push(match type_override { Some(ref o) => quote!(#o.to_owned()), diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml index 9f36e22f..a6bd854a 100644 --- a/ts-rs/Cargo.toml +++ b/ts-rs/Cargo.toml @@ -36,6 +36,7 @@ import-esm = [] [dev-dependencies] serde = { version = "1.0", features = ["derive"] } +serde_json = "1" chrono = { version = "0.4", features = ["serde"] } [dependencies] diff --git a/ts-rs/tests/infer_as.rs b/ts-rs/tests/infer_as.rs new file mode 100644 index 00000000..ed62ac57 --- /dev/null +++ b/ts-rs/tests/infer_as.rs @@ -0,0 +1,48 @@ +#![allow(dead_code)] + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(TS, Serialize, Deserialize, PartialEq, Debug)] +#[ts(export)] +struct Foo { + #[ts(optional, as = "Option<_>")] + #[serde(skip_serializing_if = "std::ops::Not::not", default)] + my_optional_bool: bool, + + #[ts(as = "std::collections::HashMap")] + a: i32, +} + +#[test] +fn test() { + let falsy = Foo { + my_optional_bool: false, + a: 0, + }; + let truthy = Foo { + my_optional_bool: true, + a: 0, + }; + + // Type definition + assert_eq!(Foo::inline(), "{ my_optional_bool?: boolean, }"); + + // Serializing + assert_eq!(serde_json::to_string(&falsy).unwrap(), "{}"); // `false` is not serialized + assert_eq!( + serde_json::to_string(&truthy).unwrap(), + r#"{"my_optional_bool":true}"# + ); + + // Deserializing + assert_eq!( + serde_json::from_str::(r#"{"my_optional_bool":true}"#).unwrap(), + truthy + ); + assert_eq!( + serde_json::from_str::(r#"{"my_optional_bool":false}"#).unwrap(), + falsy + ); + assert_eq!(serde_json::from_str::("{}").unwrap(), falsy); // `undefined` is deserialized into `false` +} From 89196a46868a42da12cad0f8b840da744120eae4 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 4 Apr 2024 18:00:56 -0300 Subject: [PATCH 02/10] Make type parsing more robust --- macros/src/types/mod.rs | 167 ++++++++++++++++++++++++++---------- macros/src/types/named.rs | 1 - macros/src/types/newtype.rs | 1 - macros/src/types/tuple.rs | 1 - 4 files changed, 123 insertions(+), 47 deletions(-) diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index 44ff78b1..abf2042f 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -1,6 +1,7 @@ -use quote::{quote, ToTokens}; use syn::{ - Fields, Ident, ItemStruct, Result, Type, TypeArray, TypeParen, TypeReference, TypeSlice, + parse_quote, AngleBracketedGenericArguments, AssocType, Fields, GenericArgument, Ident, + ItemStruct, ParenthesizedGenericArguments, Path, PathArguments, PathSegment, Result, + ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, }; @@ -51,47 +52,125 @@ fn type_def(attr: &StructAttr, ident: &Ident, fields: &Fields) -> Result Result { - syn::parse2( - type_as - .to_token_stream() - .into_iter() - .map(|x| { - let ty = syn::parse2::(x.to_token_stream()); - Ok(match ty { - Ok(Type::Infer(_)) => original_type.to_token_stream(), - Ok(Type::Reference(TypeReference { - elem, - lifetime, - and_token, - mutability, - })) => { - let elem = type_as_infer(&elem, original_type)?; - quote!(#and_token #lifetime #mutability #elem) - } - Ok(Type::Array(TypeArray { elem, len, .. })) => { - let elem = type_as_infer(&elem, original_type)?; - quote!([#elem; #len]) - } - Ok(Type::Tuple(TypeTuple { elems, .. })) => { - let elems = elems - .iter() - .map(|x| type_as_infer(x, original_type)) - .collect::>>()?; - quote![(#(#elems),*)] - } - Ok(Type::Slice(TypeSlice { elem, .. })) => { - let elem = type_as_infer(&elem, original_type)?; - quote!([#elem]) - } - Ok(Type::Paren(TypeParen { elem, .. })) => { - let elem = type_as_infer(&elem, original_type)?; - quote![(elem)] - } - y => x.to_token_stream(), - }) +pub(super) fn type_as_infer(type_as: &Type, original_type: &Type) -> Type { + use PathArguments as P; + + let recurse = |ty: &Type| -> Type { type_as_infer(ty, original_type) }; + + match type_as { + Type::Infer(_) => original_type.clone(), + Type::Array(TypeArray { elem, len, .. }) => { + let elem = recurse(elem); + parse_quote!([#elem; #len]) + } + Type::Group(TypeGroup { elem, group_token }) => Type::Group(TypeGroup { + group_token: *group_token, + elem: Box::new(recurse(elem)), + }), + Type::Paren(TypeParen { elem, .. }) => { + let elem = recurse(elem); + parse_quote![(#elem)] + } + Type::Path(TypePath { path, qself: None }) => Type::Path(TypePath { + path: Path { + leading_colon: path.leading_colon, + segments: path + .segments + .iter() + .map(|x| match x.arguments { + P::None => x.clone(), + P::Parenthesized(ParenthesizedGenericArguments { + ref inputs, + ref output, + paren_token, + }) => PathSegment { + ident: x.ident.clone(), + arguments: PathArguments::Parenthesized( + ParenthesizedGenericArguments { + paren_token, + inputs: inputs.iter().map(recurse).collect(), + output: match output { + ReturnType::Default => ReturnType::Default, + ReturnType::Type(r_arrow, ty) => { + ReturnType::Type(*r_arrow, Box::new(recurse(ty))) + } + }, + }, + ), + }, + P::AngleBracketed(ref angle_bracketed) => PathSegment { + ident: x.ident.clone(), + arguments: P::AngleBracketed(type_as_infer_angle_bracketed( + angle_bracketed, + original_type, + )), + }, + }) + .collect(), + }, + qself: None, + }), + Type::Ptr(TypePtr { + elem, + star_token, + const_token, + mutability, + }) => { + let elem = recurse(elem); + parse_quote!(#star_token #const_token #mutability #elem) + } + Type::Reference(TypeReference { + elem, + lifetime, + and_token, + mutability, + }) => { + let elem = recurse(elem); + parse_quote!(#and_token #lifetime #mutability #elem) + } + Type::Slice(TypeSlice { elem, .. }) => { + let elem = recurse(elem); + parse_quote!([#elem]) + } + Type::Tuple(TypeTuple { elems, .. }) => { + let elems = elems.iter().map(recurse).collect::>(); + parse_quote![(#(#elems),*)] + } + x => x.clone(), + } +} + +fn type_as_infer_angle_bracketed( + angle_bracketed: &AngleBracketedGenericArguments, + original_type: &Type, +) -> AngleBracketedGenericArguments { + let recurse = |args: &AngleBracketedGenericArguments| -> AngleBracketedGenericArguments { + type_as_infer_angle_bracketed(args, original_type) + }; + + AngleBracketedGenericArguments { + colon2_token: angle_bracketed.colon2_token, + gt_token: angle_bracketed.gt_token, + lt_token: angle_bracketed.lt_token, + args: angle_bracketed + .args + .iter() + .map(|arg| match arg { + GenericArgument::Type(ty) => { + GenericArgument::Type(type_as_infer(ty, original_type)) + } + GenericArgument::AssocType(assoc_ty) => GenericArgument::AssocType(AssocType { + ident: assoc_ty.ident.clone(), + generics: assoc_ty.generics.as_ref().map(recurse), + eq_token: assoc_ty.eq_token, + ty: type_as_infer(&assoc_ty.ty, original_type), + }), + GenericArgument::Constraint(_) + | GenericArgument::AssocConst(_) + | GenericArgument::Lifetime(_) + | GenericArgument::Const(_) => arg.clone(), + _ => todo!(), }) - .collect::>()?, - ) + .collect(), + } } diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index b882287a..bcc2c740 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -111,7 +111,6 @@ fn format_field( let parsed_ty = type_as .as_ref() .map(|ty_as| type_as_infer(ty_as, &field.ty)) - .transpose()? .unwrap_or_else(|| field.ty.clone()); let (ty, optional_annotation) = match optional { diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index 601b1d65..be9262b0 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -31,7 +31,6 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) -> let inner_ty = type_as .as_ref() .map(|ty_as| type_as_infer(ty_as, &inner.ty)) - .transpose()? .unwrap_or_else(|| inner.ty.clone()); let mut dependencies = Dependencies::new(crate_rename.clone()); diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index 996a95d3..0083e6cc 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -68,7 +68,6 @@ fn format_field( let ty = type_as .as_ref() .map(|ty_as| type_as_infer(ty_as, &field.ty)) - .transpose()? .unwrap_or_else(|| field.ty.clone()); formatted_fields.push(match type_override { From d4fb6e8ce5f379878edbe99fa761993e659ae82b Mon Sep 17 00:00:00 2001 From: Gustavo Date: Mon, 8 Apr 2024 09:04:59 -0300 Subject: [PATCH 03/10] Remove todo and fix test --- macros/src/types/mod.rs | 6 +----- ts-rs/Cargo.toml | 1 - ts-rs/tests/infer_as.rs | 31 ------------------------------- 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index abf2042f..1438ffbb 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -165,11 +165,7 @@ fn type_as_infer_angle_bracketed( eq_token: assoc_ty.eq_token, ty: type_as_infer(&assoc_ty.ty, original_type), }), - GenericArgument::Constraint(_) - | GenericArgument::AssocConst(_) - | GenericArgument::Lifetime(_) - | GenericArgument::Const(_) => arg.clone(), - _ => todo!(), + _ => arg.clone(), }) .collect(), } diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml index a6bd854a..9f36e22f 100644 --- a/ts-rs/Cargo.toml +++ b/ts-rs/Cargo.toml @@ -36,7 +36,6 @@ import-esm = [] [dev-dependencies] serde = { version = "1.0", features = ["derive"] } -serde_json = "1" chrono = { version = "0.4", features = ["serde"] } [dependencies] diff --git a/ts-rs/tests/infer_as.rs b/ts-rs/tests/infer_as.rs index ed62ac57..35ce5397 100644 --- a/ts-rs/tests/infer_as.rs +++ b/ts-rs/tests/infer_as.rs @@ -9,40 +9,9 @@ struct Foo { #[ts(optional, as = "Option<_>")] #[serde(skip_serializing_if = "std::ops::Not::not", default)] my_optional_bool: bool, - - #[ts(as = "std::collections::HashMap")] - a: i32, } #[test] fn test() { - let falsy = Foo { - my_optional_bool: false, - a: 0, - }; - let truthy = Foo { - my_optional_bool: true, - a: 0, - }; - - // Type definition assert_eq!(Foo::inline(), "{ my_optional_bool?: boolean, }"); - - // Serializing - assert_eq!(serde_json::to_string(&falsy).unwrap(), "{}"); // `false` is not serialized - assert_eq!( - serde_json::to_string(&truthy).unwrap(), - r#"{"my_optional_bool":true}"# - ); - - // Deserializing - assert_eq!( - serde_json::from_str::(r#"{"my_optional_bool":true}"#).unwrap(), - truthy - ); - assert_eq!( - serde_json::from_str::(r#"{"my_optional_bool":false}"#).unwrap(), - falsy - ); - assert_eq!(serde_json::from_str::("{}").unwrap(), falsy); // `undefined` is deserialized into `false` } From a75928eb259806fe4e1290f830aafb2190c317e0 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 10 Apr 2024 10:01:32 -0300 Subject: [PATCH 04/10] Parse infered as in enum fields --- macros/src/types/enum.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index 6dcc0f4e..07eb5402 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -5,7 +5,7 @@ use syn::{Fields, ItemEnum, Variant}; use crate::{ attr::{Attr, EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr}, deps::Dependencies, - types::{self, type_as, type_override}, + types::{self, type_as, type_as_infer, type_override}, DerivedTS, }; @@ -151,7 +151,8 @@ fn format_variant( } (Some(type_override), None) => quote! { #type_override }, (None, Some(type_as)) => { - quote!(<#type_as as #crate_rename::TS>::name()) + let ty = type_as_infer(&type_as, &field.ty); + quote!(<#ty as #crate_rename::TS>::name()) } (None, None) => { let ty = &unnamed.unnamed[0].ty; @@ -207,7 +208,8 @@ fn format_variant( } (Some(type_override), None) => quote! { #type_override }, (None, Some(type_as)) => { - quote!(<#type_as as #crate_rename::TS>::name()) + let ty = type_as_infer(&type_as, &field.ty); + quote!(<#ty as #crate_rename::TS>::name()) } (None, None) => { let ty = &unnamed.unnamed[0].ty; From c85f34f3cde18b7a048f31ded1d458a34ca10471 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 10 Apr 2024 10:15:06 -0300 Subject: [PATCH 05/10] Make FieldAttr::type_as private --- macros/src/attr/field.rs | 14 ++++++++-- macros/src/types/enum.rs | 51 ++++++++----------------------------- macros/src/types/named.rs | 50 ++++++++++++++---------------------- macros/src/types/newtype.rs | 22 ++++------------ macros/src/types/tuple.rs | 25 ++++-------------- 5 files changed, 52 insertions(+), 110 deletions(-) diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index a1815f06..608e3fee 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -1,11 +1,14 @@ use syn::{Attribute, Field, Ident, Result, Type}; use super::{parse_assign_from_str, parse_assign_str, Attr, Serde}; -use crate::utils::{parse_attrs, parse_docs}; +use crate::{ + types::type_as_infer, + utils::{parse_attrs, parse_docs}, +}; #[derive(Default)] pub struct FieldAttr { - pub type_as: Option, + type_as: Option, pub type_override: Option, pub rename: Option, pub inline: bool, @@ -38,6 +41,13 @@ impl FieldAttr { Ok(result) } + + pub fn type_as(&self, original_type: &Type) -> Type { + self.type_as + .as_ref() + .map(|x| type_as_infer(x, original_type)) + .unwrap_or_else(|| original_type.clone()) + } } impl Attr for FieldAttr { diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index 07eb5402..3e85a925 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -5,7 +5,7 @@ use syn::{Fields, ItemEnum, Variant}; use crate::{ attr::{Attr, EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr}, deps::Dependencies, - types::{self, type_as, type_as_infer, type_override}, + types::{self, type_as, type_override}, DerivedTS, }; @@ -135,31 +135,16 @@ fn format_variant( field_attr.assert_validity(field)?; - let FieldAttr { - type_as, - type_override, - skip, - .. - } = field_attr; - - if skip { + if field_attr.skip { quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)) } else { - let ty = match (type_override, type_as) { - (Some(_), Some(_)) => { - unreachable!("This has been handled by assert_validity") - } - (Some(type_override), None) => quote! { #type_override }, - (None, Some(type_as)) => { - let ty = type_as_infer(&type_as, &field.ty); - quote!(<#ty as #crate_rename::TS>::name()) - } - (None, None) => { - let ty = &unnamed.unnamed[0].ty; + let ty = match field_attr.type_override { + Some(type_override) => quote!(#type_override), + None => { + let ty = field_attr.type_as(&field.ty); quote!(<#ty as #crate_rename::TS>::name()) } }; - quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty)) } } @@ -192,27 +177,13 @@ fn format_variant( field_attr.assert_validity(field)?; - let FieldAttr { - type_as, - skip, - type_override, - .. - } = field_attr; - - if skip { + if field_attr.skip { quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)) } else { - let ty = match (type_override, type_as) { - (Some(_), Some(_)) => { - unreachable!("This has been handled by assert_validity") - } - (Some(type_override), None) => quote! { #type_override }, - (None, Some(type_as)) => { - let ty = type_as_infer(&type_as, &field.ty); - quote!(<#ty as #crate_rename::TS>::name()) - } - (None, None) => { - let ty = &unnamed.unnamed[0].ty; + let ty = match field_attr.type_override { + Some(type_override) => quote! { #type_override }, + None => { + let ty = field_attr.type_as(&field.ty); quote!(<#ty as #crate_rename::TS>::name()) } }; diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index bcc2c740..3457b7a1 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -4,7 +4,6 @@ use syn::{ spanned::Spanned, Field, FieldsNamed, GenericArgument, Path, PathArguments, Result, Type, }; -use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, Inflection, Optional, StructAttr}, deps::Dependencies, @@ -93,27 +92,13 @@ fn format_field( field_attr.assert_validity(field)?; - let FieldAttr { - type_as, - type_override, - rename, - inline, - skip, - optional, - flatten, - docs, - } = field_attr; - - if skip { + if field_attr.skip { return Ok(()); } - let parsed_ty = type_as - .as_ref() - .map(|ty_as| type_as_infer(ty_as, &field.ty)) - .unwrap_or_else(|| field.ty.clone()); + let parsed_ty = field_attr.type_as(&field.ty); - let (ty, optional_annotation) = match optional { + let (ty, optional_annotation) = match field_attr.optional { Optional { optional: true, nullable, @@ -129,24 +114,27 @@ fn format_field( } => (&parsed_ty, ""), }; - if flatten { + if field_attr.flatten { flattened_fields.push(quote!(<#ty as #crate_rename::TS>::inline_flattened())); dependencies.append_from(ty); return Ok(()); } - let formatted_ty = type_override.map(|t| quote!(#t)).unwrap_or_else(|| { - if inline { - dependencies.append_from(ty); - quote!(<#ty as #crate_rename::TS>::inline()) - } else { - dependencies.push(ty); - quote!(<#ty as #crate_rename::TS>::name()) - } - }); + let formatted_ty = field_attr + .type_override + .map(|t| quote!(#t)) + .unwrap_or_else(|| { + if field_attr.inline { + dependencies.append_from(ty); + quote!(<#ty as #crate_rename::TS>::inline()) + } else { + dependencies.push(ty); + quote!(<#ty as #crate_rename::TS>::name()) + } + }); let field_name = to_ts_ident(field.ident.as_ref().unwrap()); - let name = match (rename, rename_all) { + let name = match (field_attr.rename, rename_all) { (Some(rn), _) => rn, (None, Some(rn)) => rn.apply(&field_name), (None, None) => field_name, @@ -154,9 +142,9 @@ fn format_field( let valid_name = raw_name_to_ts_field(name); // Start every doc string with a newline, because when other characters are in front, it is not "understood" by VSCode - let docs = match docs.is_empty() { + let docs = match field_attr.docs.is_empty() { true => "".to_string(), - false => format!("\n{}", &docs), + false => format!("\n{}", &field_attr.docs), }; formatted_fields.push(quote! { diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index be9262b0..c48b9556 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -1,7 +1,6 @@ use quote::quote; use syn::{FieldsUnnamed, Result}; -use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, @@ -14,36 +13,25 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) -> let field_attr = FieldAttr::from_attrs(&inner.attrs)?; field_attr.assert_validity(inner)?; - let FieldAttr { - type_as, - type_override, - inline, - skip, - .. - } = field_attr; - let crate_rename = attr.crate_rename(); - if skip { + if field_attr.skip { return super::unit::null(attr, name); } - let inner_ty = type_as - .as_ref() - .map(|ty_as| type_as_infer(ty_as, &inner.ty)) - .unwrap_or_else(|| inner.ty.clone()); + let inner_ty = field_attr.type_as(&inner.ty); let mut dependencies = Dependencies::new(crate_rename.clone()); - match (&type_override, inline) { + match (&field_attr.type_override, field_attr.inline) { (Some(_), _) => (), (None, true) => dependencies.append_from(&inner_ty), (None, false) => dependencies.push(&inner_ty), }; - let inline_def = match type_override { + let inline_def = match field_attr.type_override { Some(ref o) => quote!(#o.to_owned()), - None if inline => quote!(<#inner_ty as #crate_rename::TS>::inline()), + None if field_attr.inline => quote!(<#inner_ty as #crate_rename::TS>::inline()), None => quote!(<#inner_ty as #crate_rename::TS>::name()), }; diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index 0083e6cc..0b8571a6 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -2,7 +2,6 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Field, FieldsUnnamed, Path, Result}; -use super::type_as_infer; use crate::{ attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, @@ -50,33 +49,19 @@ fn format_field( let field_attr = FieldAttr::from_attrs(&field.attrs)?; field_attr.assert_validity(field)?; - let FieldAttr { - type_as, - type_override, - rename: _, - inline, - skip, - optional: _, - flatten: _, - docs: _, - } = field_attr; - - if skip { + if field_attr.skip { return Ok(()); } - let ty = type_as - .as_ref() - .map(|ty_as| type_as_infer(ty_as, &field.ty)) - .unwrap_or_else(|| field.ty.clone()); + let ty = field_attr.type_as(&field.ty); - formatted_fields.push(match type_override { + formatted_fields.push(match field_attr.type_override { Some(ref o) => quote!(#o.to_owned()), - None if inline => quote!(<#ty as #crate_rename::TS>::inline()), + None if field_attr.inline => quote!(<#ty as #crate_rename::TS>::inline()), None => quote!(<#ty as #crate_rename::TS>::name()), }); - match (inline, type_override) { + match (field_attr.inline, field_attr.type_override) { (_, Some(_)) => (), (false, _) => dependencies.push(&ty), (true, _) => dependencies.append_from(&ty), From dbd27c7390ad8a876a8e3c167eab606553bf61fb Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 10 Apr 2024 10:48:03 -0300 Subject: [PATCH 06/10] Remove irrelevant attributes from test --- macros/src/types/named.rs | 1 - ts-rs/tests/infer_as.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index 46ac06b5..3457b7a1 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -92,7 +92,6 @@ fn format_field( field_attr.assert_validity(field)?; - if field_attr.skip { return Ok(()); } diff --git a/ts-rs/tests/infer_as.rs b/ts-rs/tests/infer_as.rs index 35ce5397..459aaacd 100644 --- a/ts-rs/tests/infer_as.rs +++ b/ts-rs/tests/infer_as.rs @@ -1,13 +1,11 @@ #![allow(dead_code)] -use serde::{Deserialize, Serialize}; use ts_rs::TS; -#[derive(TS, Serialize, Deserialize, PartialEq, Debug)] +#[derive(TS)] #[ts(export)] struct Foo { #[ts(optional, as = "Option<_>")] - #[serde(skip_serializing_if = "std::ops::Not::not", default)] my_optional_bool: bool, } From 9874a67ef6bb4f72843285a7e913cc355673b2eb Mon Sep 17 00:00:00 2001 From: NyxCode Date: Wed, 10 Apr 2024 16:55:18 +0200 Subject: [PATCH 07/10] change function to take in `&mut Type` (#305) --- macros/src/attr/field.rs | 12 +-- macros/src/types/mod.rs | 161 ++++++++++++--------------------------- 2 files changed, 55 insertions(+), 118 deletions(-) diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 77819f66..6f42dd57 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -2,9 +2,9 @@ use syn::{Attribute, Field, Ident, Result, Type}; use super::{parse_assign_from_str, parse_assign_str, Attr, Serde}; use crate::{ - types::type_as_infer, utils::{parse_attrs, parse_docs}, }; +use crate::types::replace_underscore; #[derive(Default)] pub struct FieldAttr { @@ -46,10 +46,12 @@ impl FieldAttr { } pub fn type_as(&self, original_type: &Type) -> Type { - self.type_as - .as_ref() - .map(|x| type_as_infer(x, original_type)) - .unwrap_or_else(|| original_type.clone()) + if let Some(mut ty) = self.type_as.clone() { + replace_underscore(&mut ty, original_type); + ty + } else { + original_type.clone() + } } } diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index 1438ffbb..4fe3eb5c 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -1,8 +1,7 @@ use syn::{ - parse_quote, AngleBracketedGenericArguments, AssocType, Fields, GenericArgument, Ident, - ItemStruct, ParenthesizedGenericArguments, Path, PathArguments, PathSegment, Result, - ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, - TypeTuple, + AngleBracketedGenericArguments, Fields, GenericArgument, Ident, ItemStruct, PathArguments, + Result, ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference, + TypeSlice, TypeTuple, }; use crate::{ @@ -52,121 +51,57 @@ fn type_def(attr: &StructAttr, ident: &Ident, fields: &Fields) -> Result Type { - use PathArguments as P; - - let recurse = |ty: &Type| -> Type { type_as_infer(ty, original_type) }; - - match type_as { - Type::Infer(_) => original_type.clone(), - Type::Array(TypeArray { elem, len, .. }) => { - let elem = recurse(elem); - parse_quote!([#elem; #len]) - } - Type::Group(TypeGroup { elem, group_token }) => Type::Group(TypeGroup { - group_token: *group_token, - elem: Box::new(recurse(elem)), - }), - Type::Paren(TypeParen { elem, .. }) => { - let elem = recurse(elem); - parse_quote![(#elem)] - } - Type::Path(TypePath { path, qself: None }) => Type::Path(TypePath { - path: Path { - leading_colon: path.leading_colon, - segments: path - .segments - .iter() - .map(|x| match x.arguments { - P::None => x.clone(), - P::Parenthesized(ParenthesizedGenericArguments { - ref inputs, - ref output, - paren_token, - }) => PathSegment { - ident: x.ident.clone(), - arguments: PathArguments::Parenthesized( - ParenthesizedGenericArguments { - paren_token, - inputs: inputs.iter().map(recurse).collect(), - output: match output { - ReturnType::Default => ReturnType::Default, - ReturnType::Type(r_arrow, ty) => { - ReturnType::Type(*r_arrow, Box::new(recurse(ty))) - } - }, - }, - ), - }, - P::AngleBracketed(ref angle_bracketed) => PathSegment { - ident: x.ident.clone(), - arguments: P::AngleBracketed(type_as_infer_angle_bracketed( - angle_bracketed, - original_type, - )), - }, - }) - .collect(), - }, - qself: None, - }), - Type::Ptr(TypePtr { - elem, - star_token, - const_token, - mutability, - }) => { - let elem = recurse(elem); - parse_quote!(#star_token #const_token #mutability #elem) - } - Type::Reference(TypeReference { - elem, - lifetime, - and_token, - mutability, - }) => { - let elem = recurse(elem); - parse_quote!(#and_token #lifetime #mutability #elem) - } - Type::Slice(TypeSlice { elem, .. }) => { - let elem = recurse(elem); - parse_quote!([#elem]) +pub(super) fn replace_underscore(ty: &mut Type, with: &Type) { + match ty { + Type::Infer(_) => *ty = with.clone(), + Type::Array(TypeArray { elem, .. }) + | Type::Group(TypeGroup { elem, .. }) + | Type::Paren(TypeParen { elem, .. }) + | Type::Ptr(TypePtr { elem, .. }) + | Type::Reference(TypeReference { elem, .. }) + | Type::Slice(TypeSlice { elem, .. }) => { + replace_underscore(elem, with); } Type::Tuple(TypeTuple { elems, .. }) => { - let elems = elems.iter().map(recurse).collect::>(); - parse_quote![(#(#elems),*)] + for elem in elems { + replace_underscore(elem, with); + } + } + Type::Path(TypePath { path, qself: None }) => { + for segment in &mut path.segments { + match &mut segment.arguments { + PathArguments::None => (), + PathArguments::AngleBracketed(a) => { + replace_underscore_in_angle_bracketed(a, with); + } + PathArguments::Parenthesized(p) => { + for input in &mut p.inputs { + replace_underscore(input, with); + } + if let ReturnType::Type(_, output) = &mut p.output { + replace_underscore(output, with); + } + } + } + } } - x => x.clone(), + _ => (), } } -fn type_as_infer_angle_bracketed( - angle_bracketed: &AngleBracketedGenericArguments, - original_type: &Type, -) -> AngleBracketedGenericArguments { - let recurse = |args: &AngleBracketedGenericArguments| -> AngleBracketedGenericArguments { - type_as_infer_angle_bracketed(args, original_type) - }; - - AngleBracketedGenericArguments { - colon2_token: angle_bracketed.colon2_token, - gt_token: angle_bracketed.gt_token, - lt_token: angle_bracketed.lt_token, - args: angle_bracketed - .args - .iter() - .map(|arg| match arg { - GenericArgument::Type(ty) => { - GenericArgument::Type(type_as_infer(ty, original_type)) +fn replace_underscore_in_angle_bracketed(args: &mut AngleBracketedGenericArguments, with: &Type) { + for arg in &mut args.args { + match arg { + GenericArgument::Type(ty) => { + replace_underscore(ty, with); + } + GenericArgument::AssocType(assoc_ty) => { + replace_underscore(&mut assoc_ty.ty, with); + for g in &mut assoc_ty.generics { + replace_underscore_in_angle_bracketed(g, with); } - GenericArgument::AssocType(assoc_ty) => GenericArgument::AssocType(AssocType { - ident: assoc_ty.ident.clone(), - generics: assoc_ty.generics.as_ref().map(recurse), - eq_token: assoc_ty.eq_token, - ty: type_as_infer(&assoc_ty.ty, original_type), - }), - _ => arg.clone(), - }) - .collect(), + } + _ => (), + } } } From 15e9212f0370cc633dc142722b7b910211eb3992 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Wed, 10 Apr 2024 17:06:58 +0200 Subject: [PATCH 08/10] make replace_underscore private, move to attr/field.rs --- macros/src/attr/field.rs | 66 +++++++++++++++++++++++++++++++++++++--- macros/src/types/mod.rs | 61 +------------------------------------ 2 files changed, 62 insertions(+), 65 deletions(-) diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 6f42dd57..1417777a 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -1,10 +1,11 @@ -use syn::{Attribute, Field, Ident, Result, Type}; +use syn::{ + AngleBracketedGenericArguments, Attribute, Field, GenericArgument, Ident, PathArguments, + Result, ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference, + TypeSlice, TypeTuple, +}; use super::{parse_assign_from_str, parse_assign_str, Attr, Serde}; -use crate::{ - utils::{parse_attrs, parse_docs}, -}; -use crate::types::replace_underscore; +use crate::utils::{parse_attrs, parse_docs}; #[derive(Default)] pub struct FieldAttr { @@ -214,3 +215,58 @@ impl_parse! { }, } } + +fn replace_underscore(ty: &mut Type, with: &Type) { + match ty { + Type::Infer(_) => *ty = with.clone(), + Type::Array(TypeArray { elem, .. }) + | Type::Group(TypeGroup { elem, .. }) + | Type::Paren(TypeParen { elem, .. }) + | Type::Ptr(TypePtr { elem, .. }) + | Type::Reference(TypeReference { elem, .. }) + | Type::Slice(TypeSlice { elem, .. }) => { + replace_underscore(elem, with); + } + Type::Tuple(TypeTuple { elems, .. }) => { + for elem in elems { + replace_underscore(elem, with); + } + } + Type::Path(TypePath { path, qself: None }) => { + for segment in &mut path.segments { + match &mut segment.arguments { + PathArguments::None => (), + PathArguments::AngleBracketed(a) => { + replace_underscore_in_angle_bracketed(a, with); + } + PathArguments::Parenthesized(p) => { + for input in &mut p.inputs { + replace_underscore(input, with); + } + if let ReturnType::Type(_, output) = &mut p.output { + replace_underscore(output, with); + } + } + } + } + } + _ => (), + } +} + +fn replace_underscore_in_angle_bracketed(args: &mut AngleBracketedGenericArguments, with: &Type) { + for arg in &mut args.args { + match arg { + GenericArgument::Type(ty) => { + replace_underscore(ty, with); + } + GenericArgument::AssocType(assoc_ty) => { + replace_underscore(&mut assoc_ty.ty, with); + for g in &mut assoc_ty.generics { + replace_underscore_in_angle_bracketed(g, with); + } + } + _ => (), + } + } +} diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index 4fe3eb5c..caebefc0 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -1,8 +1,4 @@ -use syn::{ - AngleBracketedGenericArguments, Fields, GenericArgument, Ident, ItemStruct, PathArguments, - Result, ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference, - TypeSlice, TypeTuple, -}; +use syn::{Fields, Ident, ItemStruct, Result}; use crate::{ attr::{Attr, StructAttr}, @@ -50,58 +46,3 @@ fn type_def(attr: &StructAttr, ident: &Ident, fields: &Fields) -> Result unit::null(attr, &name), } } - -pub(super) fn replace_underscore(ty: &mut Type, with: &Type) { - match ty { - Type::Infer(_) => *ty = with.clone(), - Type::Array(TypeArray { elem, .. }) - | Type::Group(TypeGroup { elem, .. }) - | Type::Paren(TypeParen { elem, .. }) - | Type::Ptr(TypePtr { elem, .. }) - | Type::Reference(TypeReference { elem, .. }) - | Type::Slice(TypeSlice { elem, .. }) => { - replace_underscore(elem, with); - } - Type::Tuple(TypeTuple { elems, .. }) => { - for elem in elems { - replace_underscore(elem, with); - } - } - Type::Path(TypePath { path, qself: None }) => { - for segment in &mut path.segments { - match &mut segment.arguments { - PathArguments::None => (), - PathArguments::AngleBracketed(a) => { - replace_underscore_in_angle_bracketed(a, with); - } - PathArguments::Parenthesized(p) => { - for input in &mut p.inputs { - replace_underscore(input, with); - } - if let ReturnType::Type(_, output) = &mut p.output { - replace_underscore(output, with); - } - } - } - } - } - _ => (), - } -} - -fn replace_underscore_in_angle_bracketed(args: &mut AngleBracketedGenericArguments, with: &Type) { - for arg in &mut args.args { - match arg { - GenericArgument::Type(ty) => { - replace_underscore(ty, with); - } - GenericArgument::AssocType(assoc_ty) => { - replace_underscore(&mut assoc_ty.ty, with); - for g in &mut assoc_ty.generics { - replace_underscore_in_angle_bracketed(g, with); - } - } - _ => (), - } - } -} From 0cb2773fc5c43940dc1169e5cfe696726cc2cd34 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Wed, 10 Apr 2024 17:14:48 +0200 Subject: [PATCH 09/10] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d01ea0..8fc39e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ - Add support for `#[ts(type = "..")]` directly on structs and enums ([#286](https://github.com/Aleph-Alpha/ts-rs/pull/286)) - Add support for `#[ts(as = "..")]` directly on structs and enums ([#288](https://github.com/Aleph-Alpha/ts-rs/pull/288)) - Add support for `#[ts(rename_all = "SCREAMING-KEBAB-CASE")]` ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298)) +- Support `_` in `#[ts(type = "..")]` to refer to the type of the field ([#299](https://github.com/Aleph-Alpha/ts-rs/pull/299)) ### Fixes - Fix `#[ts(rename_all_fields = "...")]` on enums containing tuple or unit variants ([#287](https://github.com/Aleph-Alpha/ts-rs/pull/287)) +- Fix "overflow evaluating the requirement" and "reached the recursion limit" errors in some cases ([#293](https://github.com/Aleph-Alpha/ts-rs/pull/293)) # 8.1.0 From 62ffd53c56a4d9f19db1be98d05df645c8be99da Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Wed, 10 Apr 2024 17:19:14 +0200 Subject: [PATCH 10/10] add docs --- ts-rs/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 0954e697..5eb7d0e3 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -287,7 +287,8 @@ pub mod typelist; /// /// - **`#[ts(as = "..")]`** /// Overrides the type of the annotated field, using the provided Rust type instead. -/// This is useful when there's a type for which you cannot derive `TS`. +/// This is useful when there's a type for which you cannot derive `TS`. +/// `_` may be used to refer to the type of the field, e.g `#[ts(as = "Option<_>")]`. ///

/// /// - **`#[ts(rename = "..")]`**