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

Infered types (_) in #[ts(as = "...")] #299

Merged
merged 11 commits into from
Apr 10, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
- Add support for `#[ts(type = "..")]` directly on structs and enums ([#286](https://github.com/Aleph-Alpha/ts-rs/pull/286))
- Add support for `#[ts(as = "..")]` directly on structs and enums ([#288](https://github.com/Aleph-Alpha/ts-rs/pull/288))
- Add support for `#[ts(rename_all = "SCREAMING-KEBAB-CASE")]` ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298))
- Support `_` in `#[ts(type = "..")]` to refer to the type of the field ([#299](https://github.com/Aleph-Alpha/ts-rs/pull/299))

### Fixes

- Fix `#[ts(rename_all_fields = "...")]` on enums containing tuple or unit variants ([#287](https://github.com/Aleph-Alpha/ts-rs/pull/287))
- Fix "overflow evaluating the requirement" and "reached the recursion limit" errors in some cases ([#293](https://github.com/Aleph-Alpha/ts-rs/pull/293))

# 8.1.0

Expand Down
72 changes: 70 additions & 2 deletions macros/src/attr/field.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use syn::{Attribute, Field, Ident, Result, Type};
use syn::{
AngleBracketedGenericArguments, Attribute, Field, GenericArgument, Ident, PathArguments,
Result, ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference,
TypeSlice, TypeTuple,
};

use super::{parse_assign_from_str, parse_assign_str, Attr, Serde};
use crate::utils::{parse_attrs, parse_docs};

#[derive(Default)]
pub struct FieldAttr {
pub type_as: Option<Type>,
type_as: Option<Type>,
pub type_override: Option<String>,
pub rename: Option<String>,
pub inline: bool,
Expand Down Expand Up @@ -41,6 +45,15 @@ impl FieldAttr {

Ok(result)
}

pub fn type_as(&self, original_type: &Type) -> Type {
if let Some(mut ty) = self.type_as.clone() {
replace_underscore(&mut ty, original_type);
ty
} else {
original_type.clone()
}
}
}

impl Attr for FieldAttr {
Expand Down Expand Up @@ -202,3 +215,58 @@ impl_parse! {
},
}
}

fn replace_underscore(ty: &mut Type, with: &Type) {
match ty {
Type::Infer(_) => *ty = with.clone(),
Type::Array(TypeArray { elem, .. })
| Type::Group(TypeGroup { elem, .. })
| Type::Paren(TypeParen { elem, .. })
| Type::Ptr(TypePtr { elem, .. })
| Type::Reference(TypeReference { elem, .. })
| Type::Slice(TypeSlice { elem, .. }) => {
replace_underscore(elem, with);
}
Type::Tuple(TypeTuple { elems, .. }) => {
for elem in elems {
replace_underscore(elem, with);
}
}
Type::Path(TypePath { path, qself: None }) => {
for segment in &mut path.segments {
match &mut segment.arguments {
PathArguments::None => (),
PathArguments::AngleBracketed(a) => {
replace_underscore_in_angle_bracketed(a, with);
}
PathArguments::Parenthesized(p) => {
for input in &mut p.inputs {
replace_underscore(input, with);
}
if let ReturnType::Type(_, output) = &mut p.output {
replace_underscore(output, with);
}
}
}
}
}
_ => (),
}
}

fn replace_underscore_in_angle_bracketed(args: &mut AngleBracketedGenericArguments, with: &Type) {
for arg in &mut args.args {
match arg {
GenericArgument::Type(ty) => {
replace_underscore(ty, with);
}
GenericArgument::AssocType(assoc_ty) => {
replace_underscore(&mut assoc_ty.ty, with);
for g in &mut assoc_ty.generics {
replace_underscore_in_angle_bracketed(g, with);
}
}
_ => (),
}
}
}
47 changes: 10 additions & 37 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,16 @@ fn format_variant(

field_attr.assert_validity(field)?;

let FieldAttr {
type_as,
type_override,
skip,
..
} = field_attr;

if skip {
if field_attr.skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = match (type_override, type_as) {
(Some(_), Some(_)) => {
unreachable!("This has been handled by assert_validity")
}
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => {
quote!(<#type_as as #crate_rename::TS>::name())
}
(None, None) => {
let ty = &unnamed.unnamed[0].ty;
let ty = match field_attr.type_override {
Some(type_override) => quote!(#type_override),
None => {
let ty = field_attr.type_as(&field.ty);
Comment on lines +141 to +144
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love how this got a lot simpler. Awesome.

quote!(<#ty as #crate_rename::TS>::name())
}
};

quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty))
}
}
Expand Down Expand Up @@ -191,26 +177,13 @@ fn format_variant(

field_attr.assert_validity(field)?;

let FieldAttr {
type_as,
skip,
type_override,
..
} = field_attr;

if skip {
if field_attr.skip {
quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
} else {
let ty = match (type_override, type_as) {
(Some(_), Some(_)) => {
unreachable!("This has been handled by assert_validity")
}
(Some(type_override), None) => quote! { #type_override },
(None, Some(type_as)) => {
quote!(<#type_as as #crate_rename::TS>::name())
}
(None, None) => {
let ty = &unnamed.unnamed[0].ty;
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())
}
};
Expand Down
49 changes: 19 additions & 30 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,27 +92,13 @@ fn format_field(

field_attr.assert_validity(field)?;

let FieldAttr {
type_as,
type_override,
rename,
inline,
skip,
optional,
flatten,
docs,

#[cfg(feature = "serde-compat")]
using_serde_with: _,
} = field_attr;

if skip {
if field_attr.skip {
return Ok(());
}

let parsed_ty = type_as.as_ref().unwrap_or(&field.ty).clone();
let parsed_ty = field_attr.type_as(&field.ty);

let (ty, optional_annotation) = match optional {
let (ty, optional_annotation) = match field_attr.optional {
Optional {
optional: true,
nullable,
Expand All @@ -128,34 +114,37 @@ fn format_field(
} => (&parsed_ty, ""),
};

if flatten {
if field_attr.flatten {
flattened_fields.push(quote!(<#ty as #crate_rename::TS>::inline_flattened()));
dependencies.append_from(ty);
return Ok(());
}

let formatted_ty = type_override.map(|t| quote!(#t)).unwrap_or_else(|| {
if inline {
dependencies.append_from(ty);
quote!(<#ty as #crate_rename::TS>::inline())
} else {
dependencies.push(ty);
quote!(<#ty as #crate_rename::TS>::name())
}
});
let formatted_ty = field_attr
.type_override
.map(|t| quote!(#t))
.unwrap_or_else(|| {
if field_attr.inline {
dependencies.append_from(ty);
quote!(<#ty as #crate_rename::TS>::inline())
} else {
dependencies.push(ty);
quote!(<#ty as #crate_rename::TS>::name())
}
});

let field_name = to_ts_ident(field.ident.as_ref().unwrap());
let name = match (rename, rename_all) {
let name = match (field_attr.rename, rename_all) {
(Some(rn), _) => rn,
(None, Some(rn)) => rn.apply(&field_name),
(None, None) => field_name,
};
let valid_name = raw_name_to_ts_field(name);

// Start every doc string with a newline, because when other characters are in front, it is not "understood" by VSCode
let docs = match docs.is_empty() {
let docs = match field_attr.docs.is_empty() {
true => "".to_string(),
false => format!("\n{}", &docs),
false => format!("\n{}", &field_attr.docs),
};

formatted_fields.push(quote! {
Expand Down
19 changes: 5 additions & 14 deletions macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,25 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) ->
let field_attr = FieldAttr::from_attrs(&inner.attrs)?;
field_attr.assert_validity(inner)?;

let FieldAttr {
type_as,
type_override,
inline,
skip,
docs: _,
..
} = field_attr;

let crate_rename = attr.crate_rename();

if skip {
if field_attr.skip {
return super::unit::null(attr, name);
}

let inner_ty = type_as.as_ref().unwrap_or(&inner.ty).clone();
let inner_ty = field_attr.type_as(&inner.ty);

let mut dependencies = Dependencies::new(crate_rename.clone());

match (&type_override, inline) {
match (&field_attr.type_override, field_attr.inline) {
(Some(_), _) => (),
(None, true) => dependencies.append_from(&inner_ty),
(None, false) => dependencies.push(&inner_ty),
};

let inline_def = match type_override {
let inline_def = match field_attr.type_override {
Some(ref o) => quote!(#o.to_owned()),
None if inline => quote!(<#inner_ty as #crate_rename::TS>::inline()),
None if field_attr.inline => quote!(<#inner_ty as #crate_rename::TS>::inline()),
None => quote!(<#inner_ty as #crate_rename::TS>::name()),
};

Expand Down
24 changes: 5 additions & 19 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,19 @@ fn format_field(
let field_attr = FieldAttr::from_attrs(&field.attrs)?;
field_attr.assert_validity(field)?;

let FieldAttr {
type_as,
type_override,
rename: _,
inline,
skip,
optional: _,
flatten: _,
docs: _,

#[cfg(feature = "serde-compat")]
using_serde_with: _,
} = field_attr;

if skip {
if field_attr.skip {
return Ok(());
}

let ty = type_as.as_ref().unwrap_or(&field.ty).clone();
let ty = field_attr.type_as(&field.ty);

formatted_fields.push(match type_override {
formatted_fields.push(match field_attr.type_override {
Some(ref o) => quote!(#o.to_owned()),
None if inline => quote!(<#ty as #crate_rename::TS>::inline()),
None if field_attr.inline => quote!(<#ty as #crate_rename::TS>::inline()),
None => quote!(<#ty as #crate_rename::TS>::name()),
});

match (inline, type_override) {
match (field_attr.inline, field_attr.type_override) {
(_, Some(_)) => (),
(false, _) => dependencies.push(&ty),
(true, _) => dependencies.append_from(&ty),
Expand Down
3 changes: 2 additions & 1 deletion ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ pub mod typelist;
///
/// - **`#[ts(as = "..")]`**
/// Overrides the type of the annotated field, using the provided Rust type instead.
/// This is useful when there's a type for which you cannot derive `TS`.
/// This is useful when there's a type for which you cannot derive `TS`.
/// `_` may be used to refer to the type of the field, e.g `#[ts(as = "Option<_>")]`.
/// <br/><br/>
///
/// - **`#[ts(rename = "..")]`**
Expand Down
15 changes: 15 additions & 0 deletions ts-rs/tests/infer_as.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![allow(dead_code)]

use ts_rs::TS;

#[derive(TS)]
#[ts(export)]
struct Foo {
#[ts(optional, as = "Option<_>")]
my_optional_bool: bool,
}

#[test]
fn test() {
assert_eq!(Foo::inline(), "{ my_optional_bool?: boolean, }");
}
Loading