mirror of
https://github.com/xazukx/ts-rs.git
synced 2026-08-23 12:58:33 +00:00
Infered types (_) in #[ts(as = "...")] (#299)
--------- Co-authored-by: NyxCode <moritz.bischof1@gmail.com>
This commit is contained in:
committed by
GitHub
parent
b9a4cca637
commit
e671a7a343
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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()),
|
||||
};
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
15
ts-rs/tests/infer_as.rs
Normal 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, }");
|
||||
}
|
||||
Reference in New Issue
Block a user