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 All @@ -29,6 +30,7 @@ impl FieldAttr {
fn merge(
&mut self,
FieldAttr {
type_as,
type_override,
rename,
inline,
Expand All @@ -38,6 +40,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 @@ -48,6 +51,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
43 changes: 36 additions & 7 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 @@ -95,12 +95,26 @@ fn format_variant(
},
Tagged::Adjacently { tag, content } => match &variant.fields {
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let FieldAttr { type_override, .. } =
FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
let FieldAttr {
type_as,
type_override,
..
} = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;

if let (Some(_type_as), Some(_type_override)) = (&type_as, &type_override) {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn_err!("`type` is not compatible with `as`")
}

let parsed_ty = if let Some(_type_as) = &type_as {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn::parse_str::<Type>(_type_as)?
} else {
unnamed.unnamed[0].ty.clone()
};

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

if let (Some(_type_as), Some(_type_override)) = (&type_as, &type_override) {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn_err!("`type` is not compatible with `as`")
}

let parsed_ty = if let Some(_type_as) = &type_as {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn::parse_str::<Type>(_type_as)?
} else {
unnamed.unnamed[0].ty.clone()
};

let ty = if let Some(type_override) = type_override {
quote! { #type_override }
} else {
format_type(&unnamed.unnamed[0].ty, dependencies, generics)
format_type(&parsed_ty, dependencies, generics)
};

quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #ty))
}
Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)),
Expand Down
28 changes: 21 additions & 7 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn format_field(
generics: &Generics,
) -> Result<()> {
let FieldAttr {
type_as,
type_override,
rename,
inline,
Expand All @@ -75,16 +76,29 @@ fn format_field(
return Ok(());
}

let (ty, optional_annotation) = match optional {
true => (extract_option_argument(&field.ty)?, "?"),
false => (&field.ty, ""),
if let (Some(_type_as), Some(_type_override)) = (&type_as, &type_override) {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn_err!("`type` is not compatible with `as`")
}

let parsed_ty = if let Some(_type_as) = &type_as {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn::parse_str::<Type>(_type_as)?
} else {
field.ty.clone()
};

let (ty, optional_annotation) = {
match optional {
true => (extract_option_argument(&parsed_ty)?, "?"),
false => (&parsed_ty, ""),
}
};
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved

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
20 changes: 15 additions & 5 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,26 @@ pub(crate) fn newtype(
_ => {}
};

let inner_ty = &inner.ty;
if let (Some(_type_as), Some(_type_override)) = (&type_as, &type_override) {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn_err!("`type` is not compatible with `as`")
}

let inner_ty = if let Some(_type_as) = &type_as {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
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),
(true, _) => dependencies.append_from(&inner_ty),
(false, _) => dependencies.push_or_append_from(&inner_ty),
};
let inline_def = match &type_override {
Some(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
19 changes: 14 additions & 5 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,6 +71,15 @@ fn format_field(
if skip {
return Ok(());
}

let ty = if let Some(_type_as) = &type_as {
escritorio-gustavo marked this conversation as resolved.
Show resolved Hide resolved
syn::parse_str::<Type>(_type_as)?
} else {
field.ty.clone()
};
if let (Some(_type_as), Some(_type_override)) = (&type_as, &type_override) {
syn_err!("`type` is not compatible with `as`")
}
if rename.is_some() {
syn_err!("`rename` is not applicable to tuple structs")
}
Expand All @@ -84,16 +93,16 @@ fn format_field(
formatted_fields.push(match &type_override {
Some(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) {
(_, 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, } }"#
)
}