Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
865 changes: 465 additions & 400 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Supported serde attributes: `rename`, `rename-all`, `rename-all-fields`, `tag`,
from the generated type, but cannot use `#[serde(skip)]`, use `#[ts(skip)]` instead.

When ts-rs encounters an unsupported serde attribute, a warning is emitted, unless the feature `no-serde-warnings` is enabled.\
We are currently waiting for [#54140](https://github.com/rust-lang/rust/issues/54140), which will improve the ergonomics arund these diagnostics.
We are currently waiting for [#54140](https://github.com/rust-lang/rust/issues/54140), which will improve the ergonomics around these diagnostics.

### Cargo Features
| **Feature** | **Description** |
Expand Down
13 changes: 13 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct DerivedTS {
docs: Vec<Expr>,
inline: TokenStream,
inline_flattened: Option<TokenStream>,
optional_inline_flattened: Option<TokenStream>,
dependencies: Dependencies,
concrete: HashMap<Ident, Type>,
bound: Option<Vec<WherePredicate>>,
Expand Down Expand Up @@ -179,6 +180,7 @@ impl DerivedTS {
fn name(cfg: &#crate_rename::Config) -> String { stringify!(#generics).to_owned() }
fn inline(cfg: &#crate_rename::Config) -> String { panic!("{} cannot be inlined", #name) }
fn inline_flattened(cfg: &#crate_rename::Config) -> String { stringify!(#generics).to_owned() }
fn optional_inline_flattened(cfg: &#crate_rename::Config) -> String { stringify!(#generics).to_owned() }
fn decl(cfg: &#crate_rename::Config) -> String { panic!("{} cannot be declared", #name) }
fn decl_concrete(cfg: &#crate_rename::Config) -> String { panic!("{} cannot be declared", #name) }
}
Expand Down Expand Up @@ -251,6 +253,13 @@ impl DerivedTS {
}
});

let optional_inline_flattened =
self.optional_inline_flattened.clone().unwrap_or_else(|| {
quote! {
panic!("{} cannot be flattened", <Self as #crate_rename::TS>::name(cfg))
}
});

let inline = match self.ts_enum {
Some(Repr::Int) => quote! {
let variants = #inline.replace(|x: char| !x.is_numeric() && x != ',', "");
Expand Down Expand Up @@ -305,6 +314,10 @@ impl DerivedTS {
fn inline_flattened(cfg: &#crate_rename::Config) -> String {
#inline_flattened
}

fn optional_inline_flattened(cfg: &#crate_rename::Config) -> String {
#optional_inline_flattened
}
}
}

Expand Down
176 changes: 135 additions & 41 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{ext::IdentExt, Expr, Fields, ItemEnum, Variant};
use syn::{ext::IdentExt, punctuated::Punctuated, token::Comma, Expr, Fields, ItemEnum, Variant};

use crate::{
attr::{Attr, EnumAttr, FieldAttr, Repr, StructAttr, Tagged, VariantAttr},
Expand Down Expand Up @@ -35,14 +35,20 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {
}

let mut formatted_variants = Vec::new();
let mut formatted_optional_variants = Vec::new();
let mut dependencies = Dependencies::new(crate_rename.clone());
let (field_names, never_fields) = collect_field_names(&s.variants, &enum_attr)?;

for variant in &s.variants {
for (index, variant) in s.variants.iter().enumerate() {
format_variant(
&mut formatted_variants,
&mut formatted_optional_variants,
&mut dependencies,
&enum_attr,
variant,
&field_names[index],
&never_fields,
index,
)?;
}

Expand All @@ -57,6 +63,15 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {
inline_flattened: enum_attr.repr.is_none().then_some(quote!(
format!("({})", [#(#formatted_variants),*].join(" | "))
)),
optional_inline_flattened: if formatted_optional_variants.len() == never_fields.len() {
enum_attr.repr.is_none().then_some(quote!(format!(
"({} | {{ {} }})",
[#(#formatted_optional_variants),*].join(" | "),
[#(#never_fields),*].join("; "),
)))
} else {
None
},
dependencies,
docs: enum_attr.docs,
export: enum_attr.export,
Expand All @@ -71,11 +86,16 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {

fn format_variant(
formatted_variants: &mut Vec<TokenStream>,
formatted_optional_variants: &mut Vec<TokenStream>,
dependencies: &mut Dependencies,
enum_attr: &EnumAttr,
variant: &Variant,
ts_name: &Expr,
never_fields: &Vec<TokenStream>,
index: usize,
) -> syn::Result<()> {
let crate_rename = enum_attr.crate_rename();
let never_fields_len = never_fields.len();

// If `variant.fields` is not a `Fields::Named(_)` the `rename_all_fields`
// attribute must be ignored to prevent a `rename_all` from getting to
Expand All @@ -89,16 +109,6 @@ fn format_variant(
}

let untagged_variant = variant_attr.untagged;
let ts_name = match (variant_attr.rename.clone(), &enum_attr.rename_all) {
(Some(rn), _) => rn,
(None, None) => {
make_string_literal(&variant.ident.unraw().to_string(), variant.ident.span())
}
(None, Some(rn)) => make_string_literal(
&rn.apply(&variant.ident.unraw().to_string()),
variant.ident.span(),
),
};

if let Some(ref repr) = enum_attr.repr {
let formatted = match (repr, &variant.discriminant) {
Expand Down Expand Up @@ -138,88 +148,171 @@ fn format_variant(
}
};

let formatted = match (untagged_variant, enum_attr.tagged()?) {
(true, _) | (_, Tagged::Untagged) => quote!(#parsed_ty),
let (formatted, formatted_optional) = match (untagged_variant, enum_attr.tagged()?) {
(true, _) | (_, Tagged::Untagged) => (quote!(#parsed_ty), None),
(false, Tagged::Externally) => match &variant.fields {
Fields::Unit => quote!(format!("\"{}\"", #ts_name)),
Fields::Unit => (quote!(format!("\"{}\"", #ts_name)), None),
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let field = &unnamed.unnamed[0];
let field_attr = FieldAttr::from_attrs(&field.attrs)?;

field_attr.assert_validity(field)?;

if field_attr.skip {
quote!(format!("\"{}\"", #ts_name))
(quote!(format!("\"{}\"", #ts_name)), None)
} else {
quote!(format!("{{ \"{}\": {} }}", #ts_name, #parsed_ty))
(
quote!(format!("{{ \"{}\": {} }}", #ts_name, #parsed_ty)),
Some(quote!({
if #never_fields_len >= 2 && #never_fields_len >= #index {
let mut never_field_options = vec![#(#never_fields),*];
never_field_options.remove(#index);

format!("{{ \"{}\": {}; {} }}", #ts_name, #parsed_ty, never_field_options.join("; "))
} else {
format!("{{ \"{}\": {}; }}", #ts_name, #parsed_ty)
}
})),
)
}
}
_ => quote!(format!("{{ \"{}\": {} }}", #ts_name, #parsed_ty)),
_ => (
quote!(format!("{{ \"{}\": {} }}", #ts_name, #parsed_ty)),
Some(quote!({
if #never_fields_len >= 2 && #never_fields_len >= #index {
let mut never_field_options = vec![#(#never_fields),*];
never_field_options.remove(#index);

format!("{{ \"{}\": {}; {} }}", #ts_name, #parsed_ty, never_field_options.join("; "))
} else {
format!("{{ \"{}\": {}; }}", #ts_name, #parsed_ty)
}
})),
),
},
(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)?;
let field_attr = FieldAttr::from_attrs(&field.attrs)?;

field_attr.assert_validity(field)?;

let field_ty = field_attr.type_as(&field.ty);

if field_attr.skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name))
(
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
None,
)
} else {
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(cfg))
}
None => quote!(<#field_ty as #crate_rename::TS>::name(cfg)),
};
quote!(
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #ts_name, #content, #ty)

(
quote!(
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #ts_name, #content, #ty)
),
None,
)
}
}
Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
_ => quote!(
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #ts_name, #content, #parsed_ty)
Fields::Unit => (
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
None,
),
_ => (
quote!(
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #ts_name, #content, #parsed_ty)
),
None,
),
},
(false, Tagged::Internally { tag }) => match variant_type.inline_flattened {
Some(_) => {
quote! { #parsed_ty }
}
Some(_) => (quote! { #parsed_ty }, None),
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)?;
let field_attr = FieldAttr::from_attrs(&field.attrs)?;

field_attr.assert_validity(field)?;

let field_ty = field_attr.type_as(&field.ty);

if field_attr.skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name))
(
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
None,
)
} else {
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(cfg))
quote!(<#field_ty as #crate_rename::TS>::name(cfg))
}
};

quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #ts_name, #ty))
(
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #ts_name, #ty)),
None,
)
}
}
Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
_ => {
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #ts_name, #parsed_ty))
}
Fields::Unit => (
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #ts_name)),
None,
),
_ => (
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #ts_name, #parsed_ty)),
None,
),
},
},
};

formatted_variants.push(formatted);
if let Some(formatted) = formatted_optional {
formatted_optional_variants.push(formatted);
}

Ok(())
}

fn collect_field_names(
variants: &Punctuated<Variant, Comma>,
enum_attr: &EnumAttr,
) -> syn::Result<(Vec<Expr>, Vec<TokenStream>)> {
let mut field_names = Vec::new();
let mut never_fields = Vec::new();

for variant in variants {
let variant_attr = VariantAttr::from_attrs(&variant.attrs)?;

variant_attr.assert_validity(variant)?;

if variant_attr.skip {
continue;
}

let ts_name = match (variant_attr.rename.clone(), &enum_attr.rename_all) {
(Some(rn), _) => rn,
(None, None) => {
make_string_literal(&variant.ident.unraw().to_string(), variant.ident.span())
}
(None, Some(rn)) => make_string_literal(
&rn.apply(&variant.ident.unraw().to_string()),
variant.ident.span(),
),
};

field_names.push(ts_name.clone());
never_fields.push(quote!(format!("\"{}\"?: never", #ts_name)));
}

Ok((field_names, never_fields))
}

// bindings for an empty enum (`never` in TS)
fn empty_enum(ts_name: Expr, enum_attr: EnumAttr) -> DerivedTS {
let crate_rename = enum_attr.crate_rename();
Expand All @@ -228,6 +321,7 @@ fn empty_enum(ts_name: Expr, enum_attr: EnumAttr) -> DerivedTS {
inline: quote!("never".to_owned()),
docs: enum_attr.docs,
inline_flattened: None,
optional_inline_flattened: None,
dependencies: Dependencies::new(crate_rename),
export: enum_attr.export,
export_to: enum_attr.export_to,
Expand Down
Loading