fix regression: #[ts(optional)] incompatible with #[ts(type = ...)] (#416)

This commit is contained in:
NyxCode
2025-06-04 13:47:33 +02:00
committed by GitHub
parent a7b73a50f8
commit dc2892f441
6 changed files with 59 additions and 55 deletions

View File

@@ -106,13 +106,6 @@ impl Attr for FieldAttr {
"`type` is not compatible with `flatten`"
);
}
if let Optional::Optional { .. } = self.optional {
syn_err_spanned!(
field;
"`type` is not compatible with `optional`"
);
}
}
if self.flatten {

View File

@@ -64,10 +64,10 @@ pub fn apply(
crate_rename: &Path,
for_struct: Optional,
field_ty: &Type,
field_attr: &FieldAttr,
attr: &FieldAttr,
span: Span,
) -> (Expr, Type) {
match (for_struct, field_attr.optional) {
match (for_struct, attr.optional) {
// explicit `#[ts(optional = false)]` on field, or inherited from struct.
(Optional::NotOptional, Optional::Inherit) | (_, Optional::NotOptional) => {
(parse_quote!(false), field_ty.clone())
@@ -89,7 +89,7 @@ pub fn apply(
// Inherited `#[ts(optional)]` from the struct.
// Acts like `#[ts(optional)]` on a field, but does not error on non-`Option` fields.
// Instead, it is a no-op.
(Optional::Optional { nullable }, Optional::Inherit) => (
(Optional::Optional { nullable }, Optional::Inherit) if attr.type_override.is_none() => (
parse_quote! {
<#field_ty as #crate_rename::TS>::IS_OPTION
},
@@ -97,10 +97,11 @@ pub fn apply(
.then(|| field_ty.clone())
.unwrap_or_else(|| unwrap_option(crate_rename, field_ty)),
),
// field may be omitted during serialization and has a default value, so the field can be
// treated as `#[ts(optional = nullable)]`.
(Optional::Inherit, Optional::Inherit) => {
let is_optional = field_attr.maybe_omitted && field_attr.has_default;
// no applicable `#[ts(optional)]` attributes
_ => {
// field may be omitted during serialization and has a default value, so the field can be
// treated as `#[ts(optional = nullable)]`.
let is_optional = attr.maybe_omitted && attr.has_default;
(parse_quote!(#is_optional), field_ty.clone())
}
}

View File

@@ -102,28 +102,6 @@ fn format_field(
return Ok(());
}
if let Some(ref type_override) = field_attr.type_override {
let field_name = to_ts_ident(field.ident.as_ref().unwrap());
let name = match (field_attr.rename.as_ref(), rename_all) {
(Some(rn), _) => rn.to_owned(),
(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 &*field_attr.docs {
&[] => quote!(""),
docs => quote!(format!("\n{}", #crate_rename::format_docs(&[#(#docs),*]))),
};
formatted_fields.push(quote! {
format!("{}{}: {},", #docs, #valid_name, #type_override)
});
return Ok(());
}
let ty = field_attr.type_as(&field.ty);
let (is_optional, ty) = crate::optional::apply(
@@ -141,13 +119,18 @@ fn format_field(
return Ok(());
}
let formatted_ty = 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 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 (field_attr.rename, rename_all) {

View File

@@ -56,11 +56,6 @@ fn format_field(
return Ok(());
}
if let Some(ref type_override) = field_attr.type_override {
formatted_fields.push(quote!(#type_override.to_owned()));
return Ok(());
}
let ty = field_attr.type_as(&field.ty);
let (is_optional, ty) = crate::optional::apply(
crate_rename,
@@ -70,13 +65,18 @@ fn format_field(
field.span(),
);
let formatted_ty = 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 formatted_ty = field_attr
.type_override
.map(|t| quote!(#t.to_owned()))
.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())
}
});
formatted_fields.push(quote! {
if #is_optional {

View File

@@ -0,0 +1,26 @@
#![cfg(feature = "serde-compat")]
use ts_rs::TS;
struct Foreign;
#[derive(TS)]
#[ts(export, export_to = "issue_415/")]
struct Issue415 {
#[ts(optional, type = "Date")]
a: Option<Foreign>,
}
#[test]
fn issue_415() {
assert_eq!(Issue415::decl(), "type Issue415 = { a?: Date, };");
}
#[derive(TS)]
#[ts(export, export_to = "issue_415/")]
struct InTuple(i32, #[ts(optional, type = "Date")] Option<Foreign>);
#[test]
fn in_tuple() {
assert_eq!(InTuple::decl(), "type InTuple = [number, (Date)?];");
}

View File

@@ -31,6 +31,7 @@ mod issue_308;
mod issue_317;
mod issue_338;
mod issue_397;
mod issue_415;
mod issue_70;
mod issue_80;
mod leading_colon;