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

add "as" derive helper attribute #174

Merged
merged 12 commits into from
Jan 31, 2024
4 changes: 4 additions & 0 deletions macros/src/attr/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::utils::parse_attrs;

#[derive(Default)]
pub struct FieldAttr {
pub type_as: Option<String>,
pub type_override: Option<String>,
pub rename: Option<String>,
pub inline: bool,
Expand Down Expand Up @@ -32,6 +33,7 @@ impl FieldAttr {
fn merge(
&mut self,
FieldAttr {
type_as,
type_override,
rename,
inline,
Expand All @@ -41,6 +43,7 @@ impl FieldAttr {
}: 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;
Expand All @@ -51,6 +54,7 @@ impl FieldAttr {

impl_parse! {
FieldAttr(input, out) {
"as" => out.type_as = Some(parse_assign_str(input)?),
"type" => out.type_override = Some(parse_assign_str(input)?),
"rename" => out.rename = Some(parse_assign_str(input)?),
"inline" => out.inline = true,
Expand Down
32 changes: 20 additions & 12 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::{format_ident, quote};
use syn::{Fields, Generics, ItemEnum, Variant};
use syn::{Fields, Generics, ItemEnum, Type, Variant};

use crate::{
attr::{EnumAttr, FieldAttr, StructAttr, Tagged, VariantAttr},
Expand Down Expand Up @@ -106,18 +106,22 @@ fn format_variant(
Tagged::Adjacently { tag, content } => match &variant.fields {
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let FieldAttr {
type_as,
type_override,
skip,
..
} = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;

let ty = match (type_override, type_as) {
(Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics),
(None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
};

if skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = if let Some(type_override) = type_override {
quote! { #type_override }
} else {
format_type(&unnamed.unnamed[0].ty, dependencies, generics)
};
quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty))
}
}
Expand Down Expand Up @@ -146,18 +150,22 @@ fn format_variant(
None => match &variant.fields {
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let FieldAttr {
type_override,
type_as,
skip,
type_override,
..
} = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;

let ty = match (type_override, type_as) {
(Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics),
(None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
};

if skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = if let Some(type_override) = type_override {
quote! { #type_override }
} else {
format_type(&unnamed.unnamed[0].ty, dependencies, generics)
};
quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #ty))
}
}
Expand Down
24 changes: 18 additions & 6 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ fn format_field(
generics: &Generics,
) -> Result<()> {
let FieldAttr {
type_as,
type_override,
rename,
inline,
Expand All @@ -91,16 +92,27 @@ fn format_field(
return Ok(());
}

if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
}

let parsed_ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
} else {
field.ty.clone()
};

let (ty, optional_annotation) = match optional {
true => (extract_option_argument(&field.ty)?, "?"),
false => (&field.ty, ""),
true => (extract_option_argument(&parsed_ty)?, "?"),
false => (&parsed_ty, ""),
};

if flatten {
match (&type_override, &rename, inline) {
(Some(_), _, _) => syn_err!("`type` is not compatible with `flatten`"),
(_, Some(_), _) => syn_err!("`rename` is not compatible with `flatten`"),
(_, _, true) => syn_err!("`inline` is not compatible with `flatten`"),
match (&type_as, &type_override, &rename, inline) {
(Some(_), _, _, _) => syn_err!("`as` is not compatible with `flatten`"),
(_, Some(_), _, _) => syn_err!("`type` is not compatible with `flatten`"),
(_, _, Some(_), _) => syn_err!("`rename` is not compatible with `flatten`"),
(_, _, _, true) => syn_err!("`inline` is not compatible with `flatten`"),
_ => {}
}

Expand Down
30 changes: 21 additions & 9 deletions macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use quote::quote;
use syn::{FieldsUnnamed, Generics, Result};
use syn::{FieldsUnnamed, Generics, Result, Type};

use crate::{
attr::{FieldAttr, StructAttr},
Expand All @@ -22,6 +22,7 @@ pub(crate) fn newtype(
}
let inner = fields.unnamed.first().unwrap();
let FieldAttr {
type_as,
type_override,
rename: rename_inner,
inline,
Expand All @@ -38,17 +39,28 @@ pub(crate) fn newtype(
_ => {}
};

let inner_ty = &inner.ty;
if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
}

let inner_ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
} else {
inner.ty.clone()
};

let mut dependencies = Dependencies::default();
match (inline, &type_override) {
(_, Some(_)) => (),
(true, _) => dependencies.append_from(inner_ty),
(false, _) => dependencies.push_or_append_from(inner_ty),

match (type_override.is_none(), inline) {
(false, _) => (),
(true, true) => dependencies.append_from(&inner_ty),
(true, false) => dependencies.push_or_append_from(&inner_ty),
};
let inline_def = match &type_override {
Some(o) => quote!(#o.to_owned()),

let inline_def = match type_override {
Some(ref o) => quote!(#o.to_owned()),
None if inline => quote!(<#inner_ty as ts_rs::TS>::inline()),
None => format_type(inner_ty, &mut dependencies, generics),
None => format_type(&inner_ty, &mut dependencies, generics),
};

let generic_args = format_generics(&mut dependencies, generics);
Expand Down
29 changes: 21 additions & 8 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, FieldsUnnamed, Generics, Result};
use syn::{Field, FieldsUnnamed, Generics, Result, Type};

use crate::{
attr::{FieldAttr, StructAttr},
Expand Down Expand Up @@ -58,8 +58,8 @@ fn format_field(
field: &Field,
generics: &Generics,
) -> Result<()> {
let ty = &field.ty;
let FieldAttr {
type_as,
type_override,
rename,
inline,
Expand All @@ -71,29 +71,42 @@ fn format_field(
if skip {
return Ok(());
}

let ty = if let Some(ref type_as) = type_as {
syn::parse_str::<Type>(type_as)?
} else {
field.ty.clone()
};

if type_as.is_some() && type_override.is_some() {
syn_err!("`type` is not compatible with `as`")
}

if rename.is_some() {
syn_err!("`rename` is not applicable to tuple structs")
}

if optional {
syn_err!("`optional` is not applicable to tuple fields")
}

if flatten {
syn_err!("`flatten` is not applicable to tuple fields")
}

formatted_fields.push(match &type_override {
Some(o) => quote!(#o.to_owned()),
formatted_fields.push(match type_override {
Some(ref o) => quote!(#o.to_owned()),
None if inline => quote!(<#ty as ts_rs::TS>::inline()),
None => format_type(ty, dependencies, generics),
None => format_type(&ty, dependencies, generics),
});

match (inline, &type_override) {
match (inline, type_override) {
(_, Some(_)) => (),
(false, _) => {
dependencies.push_or_append_from(ty);
dependencies.push_or_append_from(&ty);
}
(true, _) => {
dependencies.append_from(ty);
dependencies.append_from(&ty);
}
};

Expand Down
47 changes: 47 additions & 0 deletions ts-rs/tests/type_as.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#![allow(dead_code)]

use std::time::Instant;

use ts_rs::TS;

#[derive(TS)]
struct ExternalTypeDef {
a: i32,
b: i32,
c: i32,
}

#[test]
fn struct_properties() {
#[derive(TS)]
struct Override {
a: i32,
#[ts(as = "ExternalTypeDef")]
#[ts(inline)]
x: Instant,
}

assert_eq!(
Override::inline(),
"{ a: number, x: { a: number, b: number, c: number, }, }"
)
}

#[test]
fn enum_variants() {
#[derive(TS)]
enum OverrideEnum {
A(#[ts(as = "ExternalTypeDef")] Instant),
B {
#[ts(as = "ExternalTypeDef")]
x: i32,
y: i32,
z: i32,
},
}

assert_eq!(
OverrideEnum::inline(),
r#"{ "A": ExternalTypeDef } | { "B": { x: ExternalTypeDef, y: number, z: number, } }"#
)
}
Loading