Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support #[ts(as = "...")] and #[ts(type = "...")] on enum variants #284

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
42 changes: 40 additions & 2 deletions macros/src/attr/variant.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use syn::{Attribute, Fields, Ident, Result, Variant};
use syn::{Attribute, Fields, Ident, Result, Type, Variant};

use super::{Attr, Serde};
use crate::{
attr::{parse_assign_inflection, parse_assign_str, Inflection},
attr::{parse_assign_from_str, parse_assign_inflection, parse_assign_str, Inflection},
utils::parse_attrs,
};

#[derive(Default)]
pub struct VariantAttr {
pub type_as: Option<Type>,
pub type_override: Option<String>,
pub rename: Option<String>,
pub rename_all: Option<Inflection>,
pub inline: bool,
Expand All @@ -32,6 +34,8 @@ impl Attr for VariantAttr {

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),
rename_all: self.rename_all.or(other.rename_all),
inline: self.inline || other.inline,
Expand All @@ -41,6 +45,38 @@ impl Attr for VariantAttr {
}

fn assert_validity(&self, item: &Self::Item) -> Result<()> {
if self.type_as.is_some() {
if self.type_override.is_some() {
syn_err_spanned!(
item;
"`as` is not compatible with `type`"
)
}

if self.rename_all.is_some() {
syn_err_spanned!(
item;
"`as` is not compatible with `rename_all`"
)
}
}

if self.type_override.is_some() {
if self.rename_all.is_some() {
syn_err_spanned!(
item;
"`type` is not compatible with `rename_all`"
)
}

if self.inline {
syn_err_spanned!(
item;
"`type` is not compatible with `inline`"
)
}
}

if !matches!(item.fields, Fields::Named(_)) && self.rename_all.is_some() {
syn_err_spanned!(
item;
Expand All @@ -54,6 +90,8 @@ impl Attr for VariantAttr {

impl_parse! {
VariantAttr(input, out) {
"as" => out.type_as = Some(parse_assign_from_str(input)?),
"type" => out.type_override = Some(parse_assign_str(input)?),
"rename" => out.rename = Some(parse_assign_str(input)?),
"rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
"inline" => out.inline = true,
Expand Down
24 changes: 18 additions & 6 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,21 @@ fn format_variant(
let variant_dependencies = variant_type.dependencies;
let inline_type = variant_type.inline;

let parsed_ty = match (&variant_attr.type_as, &variant_attr.type_override) {
(Some(_), Some(_)) => syn_err_spanned!(variant; "`type` is not compatible with `as`"),
(Some(ty), None) => {
dependencies.push(ty);
quote!(<#ty as #crate_rename::TS>::name())
}
(None, Some(ty)) => quote!(#ty.to_owned()),
(None, None) => {
dependencies.append(variant_dependencies);
inline_type
}
};

let formatted = match (untagged_variant, enum_attr.tagged()?) {
(true, _) | (_, Tagged::Untagged) => quote!(#inline_type),
(true, _) | (_, Tagged::Untagged) => quote!(#parsed_ty),
(false, Tagged::Externally) => match &variant.fields {
Fields::Unit => quote!(format!("\"{}\"", #name)),
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
Expand All @@ -123,10 +136,10 @@ fn format_variant(
if field_attr.skip {
quote!(format!("\"{}\"", #name))
} else {
quote!(format!("{{ \"{}\": {} }}", #name, #inline_type))
quote!(format!("{{ \"{}\": {} }}", #name, #parsed_ty))
}
}
_ => quote!(format!("{{ \"{}\": {} }}", #name, #inline_type)),
_ => quote!(format!("{{ \"{}\": {} }}", #name, #parsed_ty)),
},
(false, Tagged::Adjacently { tag, content }) => match &variant.fields {
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
Expand All @@ -150,7 +163,7 @@ fn format_variant(
}
Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)),
_ => quote!(
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #inline_type)
format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #parsed_ty)
),
},
(false, Tagged::Internally { tag }) => match variant_type.inline_flattened {
Expand Down Expand Up @@ -193,13 +206,12 @@ fn format_variant(
}
Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)),
_ => {
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #inline_type))
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #parsed_ty))
}
},
},
};

dependencies.append(variant_dependencies);
formatted_variants.push(formatted);
Ok(())
}
Expand Down
42 changes: 41 additions & 1 deletion ts-rs/tests/integration/type_as.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
cell::UnsafeCell, mem::MaybeUninit, ptr::NonNull, sync::atomic::AtomicPtr, time::Instant,
};

use serde::Serialize;
use ts_rs::TS;

type Unsupported = UnsafeCell<MaybeUninit<NonNull<AtomicPtr<i32>>>>;
Expand Down Expand Up @@ -58,12 +59,51 @@ enum OverrideEnum {
},
}

mod deser {
use serde::{Serialize, Serializer};

use super::Instant;
pub fn serialize<S: Serializer>(field: &Instant, serializer: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
struct Foo {
x: i32,
}
Foo { x: 0 }.serialize(serializer)
}
}

#[derive(TS)]
struct OverrideVariantDef {
x: i32,
}

#[derive(TS, Serialize)]
#[ts(export, export_to = "type_as/")]
enum OverrideVariant {
#[ts(as = "OverrideVariantDef")]
#[serde(with = "deser")]
A {
x: Instant,
},
B {
y: i32,
z: i32,
},
}

#[test]
fn enum_variants() {
let a = OverrideVariant::A { x: Instant::now() };
assert_eq!(serde_json::to_string(&a).unwrap(), r#"{"A":{"x":0}}"#);
assert_eq!(
OverrideEnum::inline(),
r#"{ "A": ExternalTypeDef } | { "B": { x: ExternalTypeDef, y: number, z: number, } }"#
)
);

assert_eq!(
OverrideVariant::inline(),
r#"{ "A": OverrideVariantDef } | { "B": { y: number, z: number, } }"#
);
}

#[derive(TS)]
Expand Down