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

---------

Co-authored-by: NyxCode <moritz.bischof1@gmail.com>
This commit is contained in:
escritorio-gustavo
2024-04-10 14:07:50 -03:00
committed by GitHub
parent b9a4cca637
commit e671a7a343
8 changed files with 128 additions and 103 deletions

View File

@@ -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

View File

@@ -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,
@@ -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 {
@@ -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);
}
}
_ => (),
}
}
}

View File

@@ -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))
}
}
@@ -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())
}
};

View File

@@ -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,
@@ -128,24 +114,27 @@ 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,
@@ -153,9 +142,9 @@ fn format_field(
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! {

View File

@@ -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()),
};

View File

@@ -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),

View File

@@ -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 = "..")]`**

15
ts-rs/tests/infer_as.rs Normal file
View File

@@ -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, }");
}