From 7eff5a11b65c58b949f5808df49ed5279ee3d47f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 2 Apr 2024 15:46:02 -0300 Subject: [PATCH 1/9] Rework attribute parsing --- macros/src/attr/enum.rs | 138 ++++++++++++++++++++---------- macros/src/attr/field.rs | 138 +++++++++++++++++++++++------- macros/src/attr/mod.rs | 13 ++- macros/src/attr/struct.rs | 107 +++++++++++++---------- macros/src/attr/variant.rs | 55 ++++++------ macros/src/types/enum.rs | 39 ++++++--- macros/src/types/mod.rs | 8 +- macros/src/types/named.rs | 25 ++---- macros/src/types/newtype.rs | 43 +++------- macros/src/types/tuple.rs | 36 ++------ macros/src/types/type_override.rs | 25 +----- macros/src/types/unit.rs | 6 +- 12 files changed, 374 insertions(+), 259 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index d89f13d7..5fe76e75 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use syn::{parse_quote, Attribute, Ident, Path, Result, Type, WherePredicate}; +use syn::{parse_quote, Attribute, Ident, ItemEnum, Path, Result, Type, WherePredicate}; -use super::{parse_assign_from_str, parse_bound}; +use super::{parse_assign_from_str, parse_bound, Attr, ContainerAttr}; use crate::{ attr::{parse_assign_inflection, parse_assign_str, parse_concrete, Inflection}, utils::{parse_attrs, parse_docs}, @@ -51,14 +51,14 @@ impl EnumAttr { } pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = Self::default(); - parse_attrs(attrs)?.for_each(|a| result.merge(a)); + let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); let docs = parse_docs(attrs)?; result.docs = docs; #[cfg(feature = "serde-compat")] - crate::utils::parse_serde_attrs::(attrs).for_each(|a| result.merge(a.0)); + let result = crate::utils::parse_serde_attrs::(attrs) + .fold(result, |acc, cur| acc.merge(cur.0)); Ok(result) } @@ -67,46 +67,96 @@ impl EnumAttr { .clone() .unwrap_or_else(|| parse_quote!(::ts_rs)) } +} + +impl Attr for EnumAttr { + type Item = ItemEnum; + + fn merge(self, other: Self) -> Self { + Self { + crate_rename: self.crate_rename.or(other.crate_rename), + type_override: self.type_override.or(other.type_override), + rename: self.rename.or(other.rename), + rename_all: self.rename_all.or(other.rename_all), + rename_all_fields: self.rename_all_fields.or(other.rename_all_fields), + tag: self.tag.or(other.tag), + untagged: self.untagged || other.untagged, + content: self.content.or(other.content), + export: self.export || other.export, + export_to: self.export_to.or(other.export_to), + docs: other.docs, + concrete: self.concrete.into_iter().chain(other.concrete).collect(), + bound: match (self.bound, other.bound) { + (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()), + (Some(bound), None) | (None, Some(bound)) => Some(bound), + (None, None) => None, + }, + } + } - fn merge( - &mut self, - EnumAttr { - crate_rename, - type_override, - rename_all, - rename_all_fields, - rename, - tag, - content, - untagged, - export_to, - export, - docs, - concrete, - bound, - }: EnumAttr, - ) { - self.crate_rename = self.crate_rename.take().or(crate_rename); - self.type_override = self.type_override.take().or(type_override); - self.rename = self.rename.take().or(rename); - self.rename_all = self.rename_all.take().or(rename_all); - self.rename_all_fields = self.rename_all_fields.take().or(rename_all_fields); - self.tag = self.tag.take().or(tag); - self.untagged = self.untagged || untagged; - self.content = self.content.take().or(content); - self.export = self.export || export; - self.export_to = self.export_to.take().or(export_to); - self.docs = docs; - self.concrete.extend(concrete); - self.bound = self - .bound - .take() - .map(|b| { - b.into_iter() - .chain(bound.clone().unwrap_or_default()) - .collect() - }) - .or(bound); + fn assert_validity(&self, item: &Self::Item) -> Result<()> { + if self.type_override.is_some() { + if self.rename_all.is_some() { + syn_err_spanned!( + item; + "`rename_all` is not compatible with `type`" + ); + } + + if self.rename_all_fields.is_some() { + syn_err_spanned!( + item; + "`rename_all_fields` is not compatible with `type`" + ); + } + + if self.tag.is_some() { + syn_err_spanned!( + item; + "`tag` is not compatible with `type`" + ); + } + + if self.content.is_some() { + syn_err_spanned!( + item; + "`content` is not compatible with `type`" + ); + } + + if self.untagged { + syn_err_spanned!( + item; + "`untagged` is not compatible with `type`" + ); + } + } + + match (self.untagged, &self.tag, &self.content) { + (true, Some(_), None) => syn_err_spanned!( + item; + "untagged cannot be used with tag" + ), + (true, _, Some(_)) => syn_err_spanned!( + item; + "untagged cannot be used with content" + ), + (false, None, Some(_)) => syn_err_spanned!( + item; + "content cannot be used without tag" + ), + _ => (), + }; + + Ok(()) + } +} + +impl ContainerAttr for EnumAttr { + fn crate_rename(&self) -> Path { + self.crate_rename + .clone() + .unwrap_or_else(|| parse_quote!(::ts_rs)) } } diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 7345eb4a..e69fb09e 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -1,6 +1,6 @@ -use syn::{Attribute, Ident, Result, Type}; +use syn::{Attribute, Field, Ident, Result, Type}; -use super::{parse_assign_from_str, parse_assign_str}; +use super::{parse_assign_from_str, parse_assign_str, Attr}; use crate::utils::{parse_attrs, parse_docs}; #[derive(Default)] @@ -30,41 +30,119 @@ pub struct SerdeFieldAttr(FieldAttr); impl FieldAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = Self::default(); - parse_attrs(attrs)?.for_each(|a| result.merge(a)); + let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); + result.docs = parse_docs(attrs)?; + #[cfg(feature = "serde-compat")] if !result.skip { - crate::utils::parse_serde_attrs::(attrs) - .for_each(|a| result.merge(a.0)); + result = crate::utils::parse_serde_attrs::(attrs) + .fold(result, |acc, cur| acc.merge(cur.0)); } + Ok(result) } +} + +impl Attr for FieldAttr { + type Item = Field; + + fn merge(self, other: Self) -> Self { + Self { + type_as: self.type_as.or(other.type_as), + type_override: self.type_override.or(other.type_override), + rename: self.rename.or(other.rename), + inline: self.inline || other.inline, + skip: self.skip || other.skip, + optional: Optional { + optional: self.optional.optional || other.optional.optional, + nullable: self.optional.nullable || other.optional.nullable, + }, + flatten: self.flatten || other.flatten, + + // We can't emit TSDoc for a flattened field + // and we cant make this invalid in assert_validity because + // this documentation is totally valid in Rust + docs: if self.flatten || other.flatten { + String::new() + } else { + self.docs + &other.docs + }, + } + } + + fn assert_validity(&self, field: &Self::Item) -> Result<()> { + if self.type_override.is_some() { + if self.type_as.is_some() { + syn_err_spanned!(field; "`type` is not compatible with `as`") + } + + if self.inline { + syn_err_spanned!(field; "`type` is not compatible with `inline`") + } + + if self.flatten { + syn_err_spanned!( + field; + "`type` is not compatible with `flatten`" + ); + } + } + + if self.flatten { + if self.type_as.is_some() { + syn_err_spanned!( + field; + "`as` is not compatible with `flatten`" + ); + } + + if self.rename.is_some() { + syn_err_spanned!( + field; + "`rename` is not compatible with `flatten`" + ); + } + + if self.inline { + syn_err_spanned!( + field; + "`inline` is not compatible with `flatten`" + ); + } + + if self.optional.optional { + syn_err_spanned!( + field; + "`optional` is not compatible with `flatten`" + ); + } + } + + if field.ident.is_none() { + if self.flatten { + syn_err_spanned!( + field; + "`flatten` cannot with tuple struct fields" + ); + } + + if self.rename.is_some() { + syn_err_spanned!( + field; + "`flatten` cannot with tuple struct fields" + ); + } + + if self.optional.optional { + syn_err_spanned!( + field; + "`optional` cannot with tuple struct fields" + ); + } + } - fn merge( - &mut self, - FieldAttr { - type_as, - type_override, - rename, - inline, - skip, - optional: Optional { optional, nullable }, - flatten, - docs, - }: FieldAttr, - ) { - self.rename = self.rename.take().or(rename); - self.type_as = self.type_as.take().or(type_as); - self.type_override = self.type_override.take().or(type_override); - self.inline = self.inline || inline; - self.skip = self.skip || skip; - self.optional = Optional { - optional: self.optional.optional || optional, - nullable: self.optional.nullable || nullable, - }; - self.flatten |= flatten; - self.docs.push_str(&docs); + Ok(()) } } diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index b80ea7bb..7efdaa3d 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -6,7 +6,7 @@ pub use r#struct::*; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, - Error, Lit, Result, Token, WherePredicate, + Error, Lit, Path, Result, Token, WherePredicate, }; pub use variant::*; @@ -26,6 +26,17 @@ pub enum Inflection { Kebab, } +pub(super) trait Attr { + type Item; + + fn merge(self, other: Self) -> Self; + fn assert_validity(&self, item: &Self::Item) -> Result<()>; +} + +pub(super) trait ContainerAttr: Attr { + fn crate_rename(&self) -> Path; +} + impl Inflection { pub fn apply(self, string: &str) -> String { use inflector::Inflector; diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 4464f4c2..62184195 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use syn::{parse_quote, Attribute, Ident, Path, Result, Type, WherePredicate}; +use syn::{parse_quote, Attribute, Fields, Ident, Path, Result, Type, WherePredicate}; -use super::{parse_assign_from_str, parse_bound, parse_concrete}; +use super::{parse_assign_from_str, parse_bound, parse_concrete, Attr, ContainerAttr}; use crate::{ attr::{parse_assign_str, EnumAttr, Inflection, VariantAttr}, utils::{parse_attrs, parse_docs}, @@ -28,67 +28,88 @@ pub struct SerdeStructAttr(StructAttr); impl StructAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = Self::default(); - parse_attrs(attrs)?.for_each(|a| result.merge(a)); + let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); let docs = parse_docs(attrs)?; result.docs = docs; #[cfg(feature = "serde-compat")] - crate::utils::parse_serde_attrs::(attrs).for_each(|a| result.merge(a.0)); + let result = crate::utils::parse_serde_attrs::(attrs) + .fold(result, |acc, cur| acc.merge(cur.0)); Ok(result) } - pub fn from_variant(enum_attr: &EnumAttr, variant_attr: &VariantAttr) -> Self { + pub fn from_variant( + enum_attr: &EnumAttr, + variant_attr: &VariantAttr, + variant_fields: &Fields, + ) -> Self { Self { crate_rename: Some(enum_attr.crate_rename()), rename: variant_attr.rename.clone(), - rename_all: variant_attr.rename_all, + rename_all: variant_attr.rename_all.or(match variant_fields { + Fields::Named(_) => enum_attr.rename_all_fields, + Fields::Unnamed(_) | Fields::Unit => None, + }), // inline and skip are not supported on StructAttr ..Self::default() } } +} + +impl Attr for StructAttr { + type Item = Fields; + + fn merge(self, other: Self) -> Self { + Self { + crate_rename: self.crate_rename.or(other.crate_rename), + type_override: self.type_override.or(other.type_override), + rename: self.rename.or(other.rename), + rename_all: self.rename_all.or(other.rename_all), + export_to: self.export_to.or(other.export_to), + export: self.export || other.export, + tag: self.tag.or(other.tag), + docs: other.docs, + concrete: self.concrete.into_iter().chain(other.concrete).collect(), + bound: match (self.bound, other.bound) { + (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()), + (Some(bound), None) | (None, Some(bound)) => Some(bound), + (None, None) => None, + }, + } + } + + fn assert_validity(&self, item: &Self::Item) -> Result<()> { + if self.type_override.is_some() { + if self.rename_all.is_some() { + syn_err!("`rename_all` is not compatible with `type`"); + } + + if self.tag.is_some() { + syn_err!("`tag` is not compatible with `type`"); + } + } - pub fn crate_rename(&self) -> Path { + if !matches!(item, Fields::Named(_)) { + if self.tag.is_some() { + syn_err!("`tag` cannot be used with unit or tuple structs"); + } + + if self.rename_all.is_some() { + syn_err!("`rename_all` cannot be used with unit or tuple structs"); + } + } + + Ok(()) + } +} + +impl ContainerAttr for StructAttr { + fn crate_rename(&self) -> Path { self.crate_rename .clone() .unwrap_or_else(|| parse_quote!(::ts_rs)) } - - fn merge( - &mut self, - StructAttr { - crate_rename, - type_override, - rename_all, - rename, - export, - export_to, - tag, - docs, - concrete, - bound, - }: StructAttr, - ) { - self.crate_rename = self.crate_rename.take().or(crate_rename); - self.type_override = self.type_override.take().or(type_override); - self.rename = self.rename.take().or(rename); - self.rename_all = self.rename_all.take().or(rename_all); - self.export_to = self.export_to.take().or(export_to); - self.export = self.export || export; - self.tag = self.tag.take().or(tag); - self.docs = docs; - self.concrete.extend(concrete); - self.bound = self - .bound - .take() - .map(|b| { - b.into_iter() - .chain(bound.clone().unwrap_or_default()) - .collect() - }) - .or(bound); - } } impl_parse! { diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index cca95326..2ca61899 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -1,6 +1,6 @@ -use syn::{Attribute, Ident, Result}; +use syn::{Attribute, Fields, Ident, Result, Variant}; -use super::EnumAttr; +use super::Attr; use crate::{ attr::{parse_assign_inflection, parse_assign_str, Inflection}, utils::parse_attrs, @@ -20,38 +20,39 @@ pub struct VariantAttr { pub struct SerdeVariantAttr(VariantAttr); impl VariantAttr { - pub fn new(attrs: &[Attribute], enum_attr: &EnumAttr) -> Result { - let mut result = Self::from_attrs(attrs)?; - result.rename_all = result.rename_all.or(enum_attr.rename_all_fields); - Ok(result) - } - pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = Self::default(); - parse_attrs(attrs)?.for_each(|a| result.merge(a)); + let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); #[cfg(feature = "serde-compat")] if !result.skip { - crate::utils::parse_serde_attrs::(attrs) - .for_each(|a| result.merge(a.0)); + result = crate::utils::parse_serde_attrs::(attrs) + .fold(result, |acc, cur| acc.merge(cur.0)); } Ok(result) } +} + +impl Attr for VariantAttr { + type Item = Variant; + + fn merge(self, other: Self) -> Self { + Self { + rename: self.rename.or(other.rename), + rename_all: self.rename_all.or(other.rename_all), + inline: self.inline || other.inline, + skip: self.skip || other.skip, + untagged: self.untagged || other.untagged, + } + } + + fn assert_validity(&self, item: &Self::Item) -> Result<()> { + if !matches!(item.fields, Fields::Named(_)) && self.rename_all.is_some() { + syn_err_spanned!( + item; + "`rename_all` is not applicable to unit or tuple variants" + ) + } - fn merge( - &mut self, - VariantAttr { - rename, - rename_all, - inline, - skip, - untagged, - }: VariantAttr, - ) { - self.rename = self.rename.take().or(rename); - self.rename_all = self.rename_all.take().or(rename_all); - self.inline = self.inline || inline; - self.skip = self.skip || skip; - self.untagged = self.untagged || untagged; + Ok(()) } } diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index afaa8a17..fae9b98b 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -3,7 +3,7 @@ use quote::{format_ident, quote}; use syn::{Fields, ItemEnum, Variant}; use crate::{ - attr::{EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr}, + attr::{Attr, EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr}, deps::Dependencies, types::{self, type_override}, DerivedTS, @@ -12,6 +12,8 @@ use crate::{ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result { let enum_attr: EnumAttr = EnumAttr::from_attrs(&s.attrs)?; + enum_attr.assert_validity(s)?; + let crate_rename = enum_attr.crate_rename(); let name = match &enum_attr.rename { @@ -80,10 +82,9 @@ fn format_variant( // If `variant.fields` is not a `Fields::Named(_)` the `rename_all_fields` // attribute must be ignored to prevent a `rename_all` from getting to // the newtype, tuple or unit formatting, which would cause an error - let variant_attr = match variant.fields { - Fields::Unit | Fields::Unnamed(_) => VariantAttr::from_attrs(&variant.attrs)?, - Fields::Named(_) => VariantAttr::new(&variant.attrs, enum_attr)?, - }; + let variant_attr = VariantAttr::from_attrs(&variant.attrs)?; + + variant_attr.assert_validity(variant)?; if variant_attr.skip { return Ok(()); @@ -96,7 +97,7 @@ fn format_variant( (None, Some(rn)) => rn.apply(&variant.ident.to_string()), }; - let struct_attr = StructAttr::from_variant(enum_attr, &variant_attr); + let struct_attr = StructAttr::from_variant(enum_attr, &variant_attr, &variant.fields); let variant_type = types::type_def( &struct_attr, // since we are generating the variant as a struct, it doesn't have a name @@ -111,8 +112,12 @@ fn format_variant( (false, Tagged::Externally) => match &variant.fields { Fields::Unit => quote!(format!("\"{}\"", #name)), Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { - let FieldAttr { skip, .. } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?; - if skip { + let field = &unnamed.unnamed[0]; + let field_attr = FieldAttr::from_attrs(&field.attrs)?; + + field_attr.assert_validity(field)?; + + if field_attr.skip { quote!(format!("\"{}\"", #name)) } else { quote!(format!("{{ \"{}\": {} }}", #name, #inline_type)) @@ -122,19 +127,24 @@ fn format_variant( }, (false, Tagged::Adjacently { tag, content }) => match &variant.fields { Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { + let field = &unnamed.unnamed[0]; + let field_attr = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?; + + field_attr.assert_validity(field)?; + let FieldAttr { type_as, type_override, skip, .. - } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?; + } = field_attr; if skip { quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)) } else { let ty = match (type_override, type_as) { (Some(_), Some(_)) => { - syn_err_spanned!(variant; "`type` is not compatible with `as`") + unreachable!("This has been handled by assert_validity") } (Some(type_override), None) => quote! { #type_override }, (None, Some(type_as)) => { @@ -173,19 +183,24 @@ fn format_variant( }, None => match &variant.fields { Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { + let field = &unnamed.unnamed[0]; + let field_attr = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?; + + field_attr.assert_validity(field)?; + let FieldAttr { type_as, skip, type_override, .. - } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?; + } = field_attr; if skip { quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)) } else { let ty = match (type_override, type_as) { (Some(_), Some(_)) => { - syn_err_spanned!(variant; "`type` is not compatible with `as`") + unreachable!("This has been handled by assert_validity") } (Some(type_override), None) => quote! { #type_override }, (None, Some(type_as)) => { diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index c0ac8c31..0e730d38 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -1,6 +1,10 @@ use syn::{Fields, Ident, ItemStruct, Result}; -use crate::{attr::StructAttr, utils::to_ts_ident, DerivedTS}; +use crate::{ + attr::{Attr, StructAttr}, + utils::to_ts_ident, + DerivedTS, +}; mod r#enum; mod named; @@ -18,6 +22,8 @@ pub(crate) fn struct_def(s: &ItemStruct) -> Result { } fn type_def(attr: &StructAttr, ident: &Ident, fields: &Fields) -> Result { + attr.assert_validity(fields)?; + let name = attr.rename.clone().unwrap_or_else(|| to_ts_ident(ident)); if let Some(attr_type_override) = &attr.type_override { return type_override::type_override_struct(attr, &name, attr_type_override); diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index c0ddb43f..a5db8992 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -5,7 +5,7 @@ use syn::{ }; use crate::{ - attr::{FieldAttr, Inflection, Optional, StructAttr}, + attr::{Attr, ContainerAttr, FieldAttr, Inflection, Optional, StructAttr}, deps::Dependencies, utils::{raw_name_to_ts_field, to_ts_ident}, DerivedTS, @@ -88,6 +88,10 @@ fn format_field( field: &Field, rename_all: &Option, ) -> Result<()> { + let field_attr = FieldAttr::from_attrs(&field.attrs)?; + + field_attr.assert_validity(field)?; + let FieldAttr { type_as, type_override, @@ -97,16 +101,12 @@ fn format_field( optional, flatten, docs, - } = FieldAttr::from_attrs(&field.attrs)?; + } = field_attr; if skip { return Ok(()); } - if type_as.is_some() && type_override.is_some() { - syn_err_spanned!(field; "`type` is not compatible with `as`") - } - let parsed_ty = type_as.as_ref().unwrap_or(&field.ty).clone(); let (ty, optional_annotation) = match optional { @@ -126,18 +126,6 @@ fn format_field( }; if flatten { - match (&type_as, &type_override, &rename, inline) { - (Some(_), _, _, _) => syn_err_spanned!(field; "`as` is not compatible with `flatten`"), - (_, Some(_), _, _) => { - syn_err_spanned!(field; "`type` is not compatible with `flatten`") - } - (_, _, Some(_), _) => { - syn_err_spanned!(field; "`rename` is not compatible with `flatten`") - } - (_, _, _, true) => syn_err_spanned!(field; "`inline` is not compatible with `flatten`"), - _ => {} - } - flattened_fields.push(quote!(<#ty as #crate_rename::TS>::inline_flattened())); dependencies.append_from(ty); return Ok(()); @@ -152,6 +140,7 @@ fn format_field( quote!(<#ty as #crate_rename::TS>::name()) } }); + let field_name = to_ts_ident(field.ident.as_ref().unwrap()); let name = match (rename, rename_all) { (Some(rn), _) => rn, diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index df198da5..9589d05b 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -2,56 +2,39 @@ use quote::quote; use syn::{FieldsUnnamed, Result}; use crate::{ - attr::{FieldAttr, StructAttr}, + attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, DerivedTS, }; pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not applicable to newtype structs"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not applicable to newtype structs"); - } let inner = fields.unnamed.first().unwrap(); + + let field_attr = FieldAttr::from_attrs(&inner.attrs)?; + field_attr.assert_validity(inner)?; + let FieldAttr { type_as, type_override, - rename: rename_inner, inline, skip, - optional, - flatten, - docs: _, - } = FieldAttr::from_attrs(&inner.attrs)?; + .. + } = field_attr; let crate_rename = attr.crate_rename(); - match (&rename_inner, skip, optional.optional, flatten) { - (Some(_), ..) => syn_err_spanned!(fields; "`rename` is not applicable to newtype fields"), - (_, true, ..) => return super::unit::null(attr, name), - (_, _, true, ..) => { - syn_err_spanned!(fields; "`optional` is not applicable to newtype fields") - } - (_, _, _, true) => { - syn_err_spanned!(fields; "`flatten` is not applicable to newtype fields") - } - _ => {} - }; - - if type_as.is_some() && type_override.is_some() { - syn_err_spanned!(fields; "`type` is not compatible with `as`") + if skip { + return super::unit::null(attr, name); } let inner_ty = type_as.as_ref().unwrap_or(&inner.ty).clone(); let mut dependencies = Dependencies::new(crate_rename.clone()); - match (type_override.is_none(), inline) { - (false, _) => (), - (true, true) => dependencies.append_from(&inner_ty), - (true, false) => dependencies.push(&inner_ty), + match (&type_override, inline) { + (Some(_), _) => (), + (None, true) => dependencies.append_from(&inner_ty), + (None, false) => dependencies.push(&inner_ty), }; let inline_def = match type_override { diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index e85ca1a3..2b3f44a1 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -3,19 +3,12 @@ use quote::quote; use syn::{Field, FieldsUnnamed, Path, Result}; use crate::{ - attr::{FieldAttr, StructAttr}, + attr::{Attr, ContainerAttr, FieldAttr, StructAttr}, deps::Dependencies, DerivedTS, }; pub(crate) fn tuple(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not applicable to tuple structs"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not applicable to tuple structs"); - } - let crate_rename = attr.crate_rename(); let mut formatted_fields = Vec::new(); let mut dependencies = Dependencies::new(crate_rename.clone()); @@ -53,16 +46,19 @@ fn format_field( dependencies: &mut Dependencies, field: &Field, ) -> Result<()> { + let field_attr = FieldAttr::from_attrs(&field.attrs)?; + field_attr.assert_validity(field)?; + let FieldAttr { type_as, type_override, - rename, + rename: _, inline, skip, - optional, - flatten, + optional: _, + flatten: _, docs: _, - } = FieldAttr::from_attrs(&field.attrs)?; + } = field_attr; if skip { return Ok(()); @@ -70,22 +66,6 @@ fn format_field( let ty = type_as.as_ref().unwrap_or(&field.ty).clone(); - if type_as.is_some() && type_override.is_some() { - syn_err_spanned!(field; "`type` is not compatible with `as`") - } - - if rename.is_some() { - syn_err_spanned!(field; "`rename` is not applicable to tuple structs") - } - - if optional.optional { - syn_err_spanned!(field; "`optional` is not applicable to tuple fields") - } - - if flatten { - syn_err_spanned!(field; "`flatten` is not applicable to tuple fields") - } - formatted_fields.push(match type_override { Some(ref o) => quote!(#o.to_owned()), None if inline => quote!(<#ty as #crate_rename::TS>::inline()), diff --git a/macros/src/types/type_override.rs b/macros/src/types/type_override.rs index 823619b8..1f9c06f9 100644 --- a/macros/src/types/type_override.rs +++ b/macros/src/types/type_override.rs @@ -2,7 +2,7 @@ use quote::quote; use syn::Result; use crate::{ - attr::{EnumAttr, StructAttr}, + attr::{ContainerAttr, EnumAttr, StructAttr}, deps::Dependencies, DerivedTS, }; @@ -12,13 +12,6 @@ pub(crate) fn type_override_struct( name: &str, type_override: &str, ) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not compatible with `type`"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not compatible with `type`"); - } - let crate_rename = attr.crate_rename(); Ok(DerivedTS { @@ -40,22 +33,6 @@ pub(crate) fn type_override_enum( name: &str, type_override: &str, ) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not compatible with `type`"); - } - if attr.rename_all_fields.is_some() { - syn_err!("`rename_all_fields` is not compatible with `type`"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not compatible with `type`"); - } - if attr.content.is_some() { - syn_err!("`content` is not compatible with `type`"); - } - if attr.untagged { - syn_err!("`untagged` is not compatible with `type`"); - } - let crate_rename = attr.crate_rename(); Ok(DerivedTS { diff --git a/macros/src/types/unit.rs b/macros/src/types/unit.rs index 6a3c0c36..1a379545 100644 --- a/macros/src/types/unit.rs +++ b/macros/src/types/unit.rs @@ -1,7 +1,11 @@ use quote::quote; use syn::Result; -use crate::{attr::StructAttr, deps::Dependencies, DerivedTS}; +use crate::{ + attr::{ContainerAttr, StructAttr}, + deps::Dependencies, + DerivedTS, +}; pub(crate) fn empty_object(attr: &StructAttr, name: &str) -> Result { check_attributes(attr)?; From 280b404044966950cf6ea3dc34f8068556a5207b Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 2 Apr 2024 16:33:21 -0300 Subject: [PATCH 2/9] Move fold into parse_args --- macros/src/attr/enum.rs | 2 +- macros/src/attr/field.rs | 2 +- macros/src/attr/mod.rs | 2 +- macros/src/attr/struct.rs | 2 +- macros/src/attr/variant.rs | 2 +- macros/src/utils.rs | 8 +++++--- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index 5fe76e75..876f9548 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -51,7 +51,7 @@ impl EnumAttr { } pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); + let mut result = parse_attrs::(attrs)?; let docs = parse_docs(attrs)?; result.docs = docs; diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index e69fb09e..76b205d4 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -30,7 +30,7 @@ pub struct SerdeFieldAttr(FieldAttr); impl FieldAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); + let mut result = parse_attrs::(attrs)?; result.docs = parse_docs(attrs)?; diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index 7efdaa3d..b0b90345 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -26,7 +26,7 @@ pub enum Inflection { Kebab, } -pub(super) trait Attr { +pub(super) trait Attr: Default { type Item; fn merge(self, other: Self) -> Self; diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 62184195..ea3800c3 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -28,7 +28,7 @@ pub struct SerdeStructAttr(StructAttr); impl StructAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); + let mut result = parse_attrs::(attrs)?; let docs = parse_docs(attrs)?; result.docs = docs; diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index 2ca61899..22050746 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -21,7 +21,7 @@ pub struct SerdeVariantAttr(VariantAttr); impl VariantAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { - let mut result = parse_attrs(attrs)?.fold(Self::default(), |acc, cur| acc.merge(cur)); + let mut result = parse_attrs::(attrs)?; #[cfg(feature = "serde-compat")] if !result.skip { result = crate::utils::parse_serde_attrs::(attrs) diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 01a99538..0fe2b3bb 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -7,6 +7,7 @@ use syn::{ Result, Type, }; +use super::attr::Attr; use crate::deps::Dependencies; macro_rules! syn_err { @@ -96,16 +97,17 @@ pub fn raw_name_to_ts_field(value: String) -> String { } /// Parse all `#[ts(..)]` attributes from the given slice. -pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result> +pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result where - A: TryFrom<&'a Attribute, Error = Error>, + A: TryFrom<&'a Attribute, Error = Error> + Attr, { Ok(attrs .iter() .filter(|a| a.path().is_ident("ts")) .map(A::try_from) .collect::>>()? - .into_iter()) + .into_iter() + .fold(A::default(), |acc, cur| acc.merge(cur))) } /// Parse all `#[serde(..)]` attributes from the given slice. From 151f8c7bf7ebb0a824c8c9cc963a7465dc5ddf53 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 2 Apr 2024 17:20:59 -0300 Subject: [PATCH 3/9] Fix compiler errors --- macros/src/attr/enum.rs | 37 +++++++++++++++++++++++++++++++++++++ macros/src/attr/struct.rs | 10 ++++++++++ macros/src/types/type_as.rs | 28 +--------------------------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index d9cdbbc9..b09225bb 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -141,6 +141,43 @@ impl Attr for EnumAttr { } } + if self.type_as.is_some() { + if self.rename_all.is_some() { + syn_err_spanned!( + item; + "`rename_all` is not compatible with `as`" + ); + } + + if self.rename_all_fields.is_some() { + syn_err_spanned!( + item; + "`rename_all_fields` is not compatible with `as`" + ); + } + + if self.tag.is_some() { + syn_err_spanned!( + item; + "`tag` is not compatible with `as`" + ); + } + + if self.content.is_some() { + syn_err_spanned!( + item; + "`content` is not compatible with `as`" + ); + } + + if self.untagged { + syn_err_spanned!( + item; + "`untagged` is not compatible with `as`" + ); + } + } + match (self.untagged, &self.tag, &self.content) { (true, Some(_), None) => syn_err_spanned!( item; diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 7f46131c..2460f0f9 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -96,6 +96,16 @@ impl Attr for StructAttr { } } + if self.type_as.is_some() { + if self.tag.is_some() { + syn_err!("`tag` is not compatible with `as`"); + } + + if self.rename_all.is_some() { + syn_err!("`rename_all` is not compatible with `as`"); + } + } + if !matches!(item, Fields::Named(_)) { if self.tag.is_some() { syn_err!("`tag` cannot be used with unit or tuple structs"); diff --git a/macros/src/types/type_as.rs b/macros/src/types/type_as.rs index e964af11..267c6f08 100644 --- a/macros/src/types/type_as.rs +++ b/macros/src/types/type_as.rs @@ -2,19 +2,12 @@ use quote::quote; use syn::{Result, Type}; use crate::{ - attr::{EnumAttr, StructAttr}, + attr::{EnumAttr, StructAttr, ContainerAttr}, deps::Dependencies, DerivedTS, }; pub(crate) fn type_as_struct(attr: &StructAttr, name: &str, type_as: &Type) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not compatible with `as`"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not compatible with `as`"); - } - let crate_rename = attr.crate_rename(); Ok(DerivedTS { @@ -32,25 +25,6 @@ pub(crate) fn type_as_struct(attr: &StructAttr, name: &str, type_as: &Type) -> R } pub(crate) fn type_as_enum(attr: &EnumAttr, name: &str, type_as: &Type) -> Result { - if attr.rename_all.is_some() { - syn_err!("`rename_all` is not compatible with `as`"); - } - if attr.rename_all_fields.is_some() { - syn_err!("`rename_all_fields` is not compatible with `as`"); - } - if attr.tag.is_some() { - syn_err!("`tag` is not compatible with `as`"); - } - if attr.content.is_some() { - syn_err!("`content` is not compatible with `as`"); - } - if attr.untagged { - syn_err!("`untagged` is not compatible with `as`"); - } - if attr.type_override.is_some() { - syn_err!("`type` is not compatible with `as`"); - } - let crate_rename = attr.crate_rename(); Ok(DerivedTS { From 7d015e55909945bb067e6a1c2b78ff5d413a032d Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 2 Apr 2024 17:22:45 -0300 Subject: [PATCH 4/9] Fix compiler errors --- macros/src/attr/enum.rs | 2 +- macros/src/types/type_as.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index b09225bb..edd09f1f 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -104,7 +104,7 @@ impl Attr for EnumAttr { "`as` is not compatible with `type`" ); } - + if self.rename_all.is_some() { syn_err_spanned!( item; diff --git a/macros/src/types/type_as.rs b/macros/src/types/type_as.rs index 267c6f08..9a76eac2 100644 --- a/macros/src/types/type_as.rs +++ b/macros/src/types/type_as.rs @@ -2,7 +2,7 @@ use quote::quote; use syn::{Result, Type}; use crate::{ - attr::{EnumAttr, StructAttr, ContainerAttr}, + attr::{ContainerAttr, EnumAttr, StructAttr}, deps::Dependencies, DerivedTS, }; From 6e27a7badb6f3c00b882474a26df211a6e63f08f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 08:45:46 -0300 Subject: [PATCH 5/9] Rework serde attr parsing --- macros/src/attr/enum.rs | 14 ++++++-------- macros/src/attr/field.rs | 12 +++--------- macros/src/attr/mod.rs | 22 ++++++++++++++++++++++ macros/src/attr/struct.rs | 13 +++++-------- macros/src/attr/variant.rs | 11 +++-------- macros/src/utils.rs | 25 ++++++++++++++----------- ts-rs/tests/serde-skip-with-default.rs | 2 +- 7 files changed, 54 insertions(+), 45 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index edd09f1f..19b3df22 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use syn::{parse_quote, Attribute, Ident, ItemEnum, Path, Result, Type, WherePredicate}; -use super::{parse_assign_from_str, parse_bound, Attr, ContainerAttr}; +use super::{parse_assign_from_str, parse_bound, Attr, ContainerAttr, Serde}; use crate::{ attr::{parse_assign_inflection, parse_assign_str, parse_concrete, Inflection}, utils::{parse_attrs, parse_docs}, @@ -26,10 +26,6 @@ pub struct EnumAttr { pub content: Option, } -#[cfg(feature = "serde-compat")] -#[derive(Default)] -pub struct SerdeEnumAttr(EnumAttr); - #[derive(Copy, Clone)] pub enum Tagged<'a> { Externally, @@ -58,8 +54,10 @@ impl EnumAttr { result.docs = docs; #[cfg(feature = "serde-compat")] - let result = crate::utils::parse_serde_attrs::(attrs) - .fold(result, |acc, cur| acc.merge(cur.0)); + { + result = crate::utils::parse_serde_attrs::(attrs, result); + } + Ok(result) } @@ -226,7 +224,7 @@ impl_parse! { #[cfg(feature = "serde-compat")] impl_parse! { - SerdeEnumAttr(input, out) { + Serde(input, out) { "rename" => out.0.rename = Some(parse_assign_str(input)?), "rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?), "rename_all_fields" => out.0.rename_all_fields = Some(parse_assign_inflection(input)?), diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 76b205d4..0bcc790e 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -1,6 +1,6 @@ use syn::{Attribute, Field, Ident, Result, Type}; -use super::{parse_assign_from_str, parse_assign_str, Attr}; +use super::{parse_assign_from_str, parse_assign_str, Attr, Serde}; use crate::utils::{parse_attrs, parse_docs}; #[derive(Default)] @@ -24,10 +24,6 @@ pub struct Optional { pub nullable: bool, } -#[cfg(feature = "serde-compat")] -#[derive(Default)] -pub struct SerdeFieldAttr(FieldAttr); - impl FieldAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; @@ -36,8 +32,7 @@ impl FieldAttr { #[cfg(feature = "serde-compat")] if !result.skip { - result = crate::utils::parse_serde_attrs::(attrs) - .fold(result, |acc, cur| acc.merge(cur.0)); + result = crate::utils::parse_serde_attrs::(attrs, result); } Ok(result) @@ -176,7 +171,7 @@ impl_parse! { #[cfg(feature = "serde-compat")] impl_parse! { - SerdeFieldAttr(input, out) { + Serde(input, out) { "rename" => out.0.rename = Some(parse_assign_str(input)?), "skip" => out.0.skip = true, "flatten" => out.0.flatten = true, @@ -184,7 +179,6 @@ impl_parse! { "default" => { use syn::Token; if input.peek(Token![=]) { - input.parse::()?; parse_assign_str(input)?; } }, diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index b0b90345..eadbe205 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -37,6 +37,28 @@ pub(super) trait ContainerAttr: Attr { fn crate_rename(&self) -> Path; } +#[cfg(feature = "serde-compat")] +#[derive(Default)] +pub(super) struct Serde(pub T) +where + T: Attr; + +#[cfg(feature = "serde-compat")] +impl Attr for Serde +where + T: Attr, +{ + type Item = syn::Error; + + fn merge(self, other: Self) -> Self { + Self(self.0.merge(other.0)) + } + + fn assert_validity(&self, _: &Self::Item) -> Result<()> { + unimplemented!("This method should not be called on Serde") + } +} + impl Inflection { pub fn apply(self, string: &str) -> String { use inflector::Inflector; diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 2460f0f9..6957896e 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use syn::{parse_quote, Attribute, Fields, Ident, Path, Result, Type, WherePredicate}; -use super::{parse_assign_from_str, parse_bound, parse_concrete, Attr, ContainerAttr}; +use super::{parse_assign_from_str, parse_bound, parse_concrete, Attr, ContainerAttr, Serde}; use crate::{ attr::{parse_assign_str, EnumAttr, Inflection, VariantAttr}, utils::{parse_attrs, parse_docs}, @@ -23,10 +23,6 @@ pub struct StructAttr { pub bound: Option>, } -#[cfg(feature = "serde-compat")] -#[derive(Default)] -pub struct SerdeStructAttr(StructAttr); - impl StructAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; @@ -35,8 +31,9 @@ impl StructAttr { result.docs = docs; #[cfg(feature = "serde-compat")] - let result = crate::utils::parse_serde_attrs::(attrs) - .fold(result, |acc, cur| acc.merge(cur.0)); + { + result = crate::utils::parse_serde_attrs::(attrs, result); + } Ok(result) } @@ -145,7 +142,7 @@ impl_parse! { #[cfg(feature = "serde-compat")] impl_parse! { - SerdeStructAttr(input, out) { + Serde(input, out) { "rename" => out.0.rename = Some(parse_assign_str(input)?), "rename_all" => out.0.rename_all = Some(parse_assign_str(input).and_then(Inflection::try_from)?), "tag" => out.0.tag = Some(parse_assign_str(input)?), diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index 22050746..5c2d519d 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -1,6 +1,6 @@ use syn::{Attribute, Fields, Ident, Result, Variant}; -use super::Attr; +use super::{Attr, Serde}; use crate::{ attr::{parse_assign_inflection, parse_assign_str, Inflection}, utils::parse_attrs, @@ -15,17 +15,12 @@ pub struct VariantAttr { pub untagged: bool, } -#[cfg(feature = "serde-compat")] -#[derive(Default)] -pub struct SerdeVariantAttr(VariantAttr); - impl VariantAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; #[cfg(feature = "serde-compat")] if !result.skip { - result = crate::utils::parse_serde_attrs::(attrs) - .fold(result, |acc, cur| acc.merge(cur.0)); + result = crate::utils::parse_serde_attrs::(attrs, result); } Ok(result) } @@ -68,7 +63,7 @@ impl_parse! { #[cfg(feature = "serde-compat")] impl_parse! { - SerdeVariantAttr(input, out) { + Serde(input, out) { "rename" => out.0.rename = Some(parse_assign_str(input)?), "rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?), "skip" => out.0.skip = true, diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 0fe2b3bb..24397002 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -7,7 +7,7 @@ use syn::{ Result, Type, }; -use super::attr::Attr; +use super::attr::{Attr, Serde}; use crate::deps::Dependencies; macro_rules! syn_err { @@ -26,16 +26,16 @@ macro_rules! syn_err_spanned { } macro_rules! impl_parse { - ($i:ident ($input:ident, $out:ident) { $($k:pat => $e:expr),* $(,)? }) => { - impl std::convert::TryFrom<&syn::Attribute> for $i { + ($i:ident $(<$inner: ident>)? ($input:ident, $out:ident) { $($k:pat => $e:expr),* $(,)? }) => { + impl std::convert::TryFrom<&syn::Attribute> for $i $(<$inner>)? { type Error = syn::Error; fn try_from(attr: &syn::Attribute) -> syn::Result { attr.parse_args() } } - impl syn::parse::Parse for $i { + impl syn::parse::Parse for $i $(<$inner>)? { fn parse($input: syn::parse::ParseStream) -> syn::Result { - let mut $out = $i::default(); + let mut $out = Self::default(); loop { let span = $input.span(); let key: Ident = $input.call(syn::ext::IdentExt::parse_any)?; @@ -113,13 +113,17 @@ where /// Parse all `#[serde(..)]` attributes from the given slice. #[cfg(feature = "serde-compat")] #[allow(unused)] -pub fn parse_serde_attrs<'a, A: TryFrom<&'a Attribute, Error = Error>>( - attrs: &'a [Attribute], -) -> impl Iterator { +pub fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute], initial: A) -> A +where + A: Attr, + Serde: TryFrom<&'a Attribute, Error = Error> + Attr, +{ + use crate::attr::Serde; + attrs .iter() .filter(|a| a.path().is_ident("serde")) - .flat_map(|attr| match A::try_from(attr) { + .flat_map(|attr| match Serde::::try_from(attr) { Ok(attr) => Some(attr), Err(_) => { #[cfg(not(feature = "no-serde-warnings"))] @@ -135,8 +139,7 @@ pub fn parse_serde_attrs<'a, A: TryFrom<&'a Attribute, Error = Error>>( None } }) - .collect::>() - .into_iter() + .fold(initial, |acc, cur| acc.merge(cur.0)) } /// Return doc comments parsed and formatted as JSDoc. diff --git a/ts-rs/tests/serde-skip-with-default.rs b/ts-rs/tests/serde-skip-with-default.rs index fc11b9b6..b67e2e58 100644 --- a/ts-rs/tests/serde-skip-with-default.rs +++ b/ts-rs/tests/serde-skip-with-default.rs @@ -12,7 +12,7 @@ fn default_http_version() -> String { #[derive(Debug, Clone, Deserialize, Serialize, TS)] #[ts(export, export_to = "serde_skip_with_default/")] pub struct Foobar { - #[ts(skip)] + // #[ts(skip)] #[serde(skip, default = "default_http_version")] pub http_version: String, pub something_else: i32, From 8cb2742a9084656a28e7b99aa9be11adac0ba041 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 08:49:05 -0300 Subject: [PATCH 6/9] Make Serde::::assert_validity impossible to call --- macros/src/attr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index eadbe205..9bf4d79e 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -48,7 +48,7 @@ impl Attr for Serde where T: Attr, { - type Item = syn::Error; + type Item = std::convert::Infallible; fn merge(self, other: Self) -> Self { Self(self.0.merge(other.0)) From d889cc9c0a86d4e773703170d28478f2c7e9d79d Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 10:43:22 -0300 Subject: [PATCH 7/9] No longer pass result into parse_serde_attrs --- macros/src/attr/enum.rs | 14 +++++++++++++- macros/src/attr/field.rs | 10 +++++++++- macros/src/attr/mod.rs | 12 ++++-------- macros/src/attr/struct.rs | 11 ++++++++++- macros/src/attr/variant.rs | 11 ++++++++++- macros/src/utils.rs | 6 +++--- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index 19b3df22..4759ab8a 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -55,7 +55,8 @@ impl EnumAttr { #[cfg(feature = "serde-compat")] { - result = crate::utils::parse_serde_attrs::(attrs, result); + let serde_attr = crate::utils::parse_serde_attrs::(attrs); + result.merge_with_serde(serde_attr); } Ok(result) @@ -94,6 +95,17 @@ impl Attr for EnumAttr { } } + #[cfg(feature = "serde-compat")] + fn merge_with_serde(&mut self, serde: Serde) { + self.rename = self.rename.take().or(serde.0.rename); + self.rename_all = self.rename_all.take().or(serde.0.rename_all); + self.rename_all_fields = self.rename_all_fields.take().or(serde.0.rename_all_fields); + self.tag = self.tag.take().or(serde.0.tag); + self.content = self.content.take().or(serde.0.content); + self.untagged = self.untagged || serde.0.untagged; + self.bound = self.bound.take().or(serde.0.bound); + } + fn assert_validity(&self, item: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 0bcc790e..f423a946 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -32,7 +32,8 @@ impl FieldAttr { #[cfg(feature = "serde-compat")] if !result.skip { - result = crate::utils::parse_serde_attrs::(attrs, result); + let serde_attr = crate::utils::parse_serde_attrs::(attrs); + result.merge_with_serde(serde_attr) } Ok(result) @@ -66,6 +67,13 @@ impl Attr for FieldAttr { } } + #[cfg(feature = "serde-compat")] + fn merge_with_serde(&mut self, serde: Serde) { + self.rename = self.rename.take().or(serde.0.rename); + self.skip = self.skip || serde.0.skip; + self.flatten = self.flatten || serde.0.flatten; + } + fn assert_validity(&self, field: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index 9bf4d79e..8c7be458 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -30,6 +30,8 @@ pub(super) trait Attr: Default { type Item; fn merge(self, other: Self) -> Self; + #[cfg(feature = "serde-compat")] + fn merge_with_serde(&mut self, serde: Serde); fn assert_validity(&self, item: &Self::Item) -> Result<()>; } @@ -44,19 +46,13 @@ where T: Attr; #[cfg(feature = "serde-compat")] -impl Attr for Serde +impl Serde where T: Attr, { - type Item = std::convert::Infallible; - - fn merge(self, other: Self) -> Self { + pub fn merge(self, other: Self) -> Self { Self(self.0.merge(other.0)) } - - fn assert_validity(&self, _: &Self::Item) -> Result<()> { - unimplemented!("This method should not be called on Serde") - } } impl Inflection { diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 6957896e..3a8f3b9b 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -32,7 +32,8 @@ impl StructAttr { #[cfg(feature = "serde-compat")] { - result = crate::utils::parse_serde_attrs::(attrs, result); + let serde_attr = crate::utils::parse_serde_attrs::(attrs); + result.merge_with_serde(serde_attr) } Ok(result) } @@ -78,6 +79,14 @@ impl Attr for StructAttr { } } + #[cfg(feature = "serde-compat")] + fn merge_with_serde(&mut self, serde: Serde) { + self.rename = self.rename.take().or(serde.0.rename); + self.rename_all = self.rename_all.take().or(serde.0.rename_all); + self.tag = self.tag.take().or(serde.0.tag); + self.bound = self.bound.take().or(serde.0.bound); + } + fn assert_validity(&self, item: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index 5c2d519d..b1e24fd1 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -20,7 +20,8 @@ impl VariantAttr { let mut result = parse_attrs::(attrs)?; #[cfg(feature = "serde-compat")] if !result.skip { - result = crate::utils::parse_serde_attrs::(attrs, result); + let serde_attr = crate::utils::parse_serde_attrs::(attrs, result); + result.merge_with_serde(serde_attr); } Ok(result) } @@ -39,6 +40,14 @@ impl Attr for VariantAttr { } } + #[cfg(feature = "serde-compat")] + fn merge_with_serde(&mut self, serde: Serde) { + self.rename = self.rename.take().or(serde.0.rename); + self.rename_all = self.rename_all.take().or(serde.0.rename_all); + self.skip = self.skip || serde.0.skip; + self.untagged = self.untagged || serde.0.untagged; + } + fn assert_validity(&self, item: &Self::Item) -> Result<()> { if !matches!(item.fields, Fields::Named(_)) && self.rename_all.is_some() { syn_err_spanned!( diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 24397002..51511d3d 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -113,10 +113,10 @@ where /// Parse all `#[serde(..)]` attributes from the given slice. #[cfg(feature = "serde-compat")] #[allow(unused)] -pub fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute], initial: A) -> A +pub fn parse_serde_attrs<'a, A>(attrs: &'a [Attribute]) -> Serde where A: Attr, - Serde: TryFrom<&'a Attribute, Error = Error> + Attr, + Serde: TryFrom<&'a Attribute, Error = Error>, { use crate::attr::Serde; @@ -139,7 +139,7 @@ where None } }) - .fold(initial, |acc, cur| acc.merge(cur.0)) + .fold(Serde::::default(), |acc, cur| acc.merge(cur)) } /// Return doc comments parsed and formatted as JSDoc. From cd609828e05b0e299fb73d22e73671bcb82a6bcf Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 10:45:16 -0300 Subject: [PATCH 8/9] Fix compiler error --- macros/src/attr/variant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index b1e24fd1..a10327b9 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -20,7 +20,7 @@ impl VariantAttr { let mut result = parse_attrs::(attrs)?; #[cfg(feature = "serde-compat")] if !result.skip { - let serde_attr = crate::utils::parse_serde_attrs::(attrs, result); + let serde_attr = crate::utils::parse_serde_attrs::(attrs); result.merge_with_serde(serde_attr); } Ok(result) From ba04528662ca13d300b674f5875e1340a672a689 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 11:00:06 -0300 Subject: [PATCH 9/9] Remove merge_with_serde and fix doc comments --- macros/src/attr/enum.rs | 19 ++++--------------- macros/src/attr/field.rs | 13 +++---------- macros/src/attr/mod.rs | 2 -- macros/src/attr/struct.rs | 17 +++++------------ macros/src/attr/variant.rs | 10 +--------- 5 files changed, 13 insertions(+), 48 deletions(-) diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs index 4759ab8a..5532a69b 100644 --- a/macros/src/attr/enum.rs +++ b/macros/src/attr/enum.rs @@ -50,15 +50,15 @@ impl EnumAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; - let docs = parse_docs(attrs)?; - result.docs = docs; - #[cfg(feature = "serde-compat")] { let serde_attr = crate::utils::parse_serde_attrs::(attrs); - result.merge_with_serde(serde_attr); + result = result.merge(serde_attr.0); } + let docs = parse_docs(attrs)?; + result.docs = docs; + Ok(result) } @@ -95,17 +95,6 @@ impl Attr for EnumAttr { } } - #[cfg(feature = "serde-compat")] - fn merge_with_serde(&mut self, serde: Serde) { - self.rename = self.rename.take().or(serde.0.rename); - self.rename_all = self.rename_all.take().or(serde.0.rename_all); - self.rename_all_fields = self.rename_all_fields.take().or(serde.0.rename_all_fields); - self.tag = self.tag.take().or(serde.0.tag); - self.content = self.content.take().or(serde.0.content); - self.untagged = self.untagged || serde.0.untagged; - self.bound = self.bound.take().or(serde.0.bound); - } - fn assert_validity(&self, item: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index f423a946..a1815f06 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -28,14 +28,14 @@ impl FieldAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; - result.docs = parse_docs(attrs)?; - #[cfg(feature = "serde-compat")] if !result.skip { let serde_attr = crate::utils::parse_serde_attrs::(attrs); - result.merge_with_serde(serde_attr) + result = result.merge(serde_attr.0); } + result.docs = parse_docs(attrs)?; + Ok(result) } } @@ -67,13 +67,6 @@ impl Attr for FieldAttr { } } - #[cfg(feature = "serde-compat")] - fn merge_with_serde(&mut self, serde: Serde) { - self.rename = self.rename.take().or(serde.0.rename); - self.skip = self.skip || serde.0.skip; - self.flatten = self.flatten || serde.0.flatten; - } - fn assert_validity(&self, field: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index 8c7be458..2e0931fe 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -30,8 +30,6 @@ pub(super) trait Attr: Default { type Item; fn merge(self, other: Self) -> Self; - #[cfg(feature = "serde-compat")] - fn merge_with_serde(&mut self, serde: Serde); fn assert_validity(&self, item: &Self::Item) -> Result<()>; } diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs index 3a8f3b9b..bb8d186b 100644 --- a/macros/src/attr/struct.rs +++ b/macros/src/attr/struct.rs @@ -27,14 +27,15 @@ impl StructAttr { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut result = parse_attrs::(attrs)?; - let docs = parse_docs(attrs)?; - result.docs = docs; - #[cfg(feature = "serde-compat")] { let serde_attr = crate::utils::parse_serde_attrs::(attrs); - result.merge_with_serde(serde_attr) + result = result.merge(serde_attr.0); } + + let docs = parse_docs(attrs)?; + result.docs = docs; + Ok(result) } @@ -79,14 +80,6 @@ impl Attr for StructAttr { } } - #[cfg(feature = "serde-compat")] - fn merge_with_serde(&mut self, serde: Serde) { - self.rename = self.rename.take().or(serde.0.rename); - self.rename_all = self.rename_all.take().or(serde.0.rename_all); - self.tag = self.tag.take().or(serde.0.tag); - self.bound = self.bound.take().or(serde.0.bound); - } - fn assert_validity(&self, item: &Self::Item) -> Result<()> { if self.type_override.is_some() { if self.type_as.is_some() { diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs index a10327b9..52daedad 100644 --- a/macros/src/attr/variant.rs +++ b/macros/src/attr/variant.rs @@ -21,7 +21,7 @@ impl VariantAttr { #[cfg(feature = "serde-compat")] if !result.skip { let serde_attr = crate::utils::parse_serde_attrs::(attrs); - result.merge_with_serde(serde_attr); + result = result.merge(serde_attr.0); } Ok(result) } @@ -40,14 +40,6 @@ impl Attr for VariantAttr { } } - #[cfg(feature = "serde-compat")] - fn merge_with_serde(&mut self, serde: Serde) { - self.rename = self.rename.take().or(serde.0.rename); - self.rename_all = self.rename_all.take().or(serde.0.rename_all); - self.skip = self.skip || serde.0.skip; - self.untagged = self.untagged || serde.0.untagged; - } - fn assert_validity(&self, item: &Self::Item) -> Result<()> { if !matches!(item.fields, Fields::Named(_)) && self.rename_all.is_some() { syn_err_spanned!(