Skip to content

Commit

Permalink
Infered types (_) in #[ts(as = "...")] (#299)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: NyxCode <[email protected]>
  • Loading branch information
escritorio-gustavo and NyxCode authored Apr 10, 2024
1 parent b9a4cca commit e671a7a
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 103 deletions.
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);
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, }");
}

0 comments on commit e671a7a

Please sign in to comment.