diff --git a/CHANGELOG.md b/CHANGELOG.md index 947be9e3..0d4150d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes - Do not emit warning for `#[serde(crate = "..")]` ([#447](https://github.com/Aleph-Alpha/ts-rs/pull/447)) +- Fix trait bound generation when using `#[ts(optional)]` on an `Option` ([#454](https://github.com/Aleph-Alpha/ts-rs/pull/454)) # 11.1.0 ### Features diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 00613489..6a030162 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -7,7 +7,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse_quote, spanned::Spanned, ConstParam, Expr, GenericParam, Generics, Item, LifetimeParam, - Path, Result, Type, TypeArray, TypeParam, TypeParen, TypePath, TypeReference, TypeSlice, + Path, QSelf, Result, Type, TypeArray, TypeParam, TypeParen, TypePath, TypeReference, TypeSlice, TypeTuple, WhereClause, WherePredicate, }; @@ -486,6 +486,12 @@ fn used_type_params<'ty, 'out>( } } } + Type::Path(TypePath { + qself: Some(QSelf { ty, .. }), + .. + }) => { + used_type_params(out, ty, is_type_param); + } _ => (), } } diff --git a/macros/src/optional.rs b/macros/src/optional.rs index 5c43ee13..d4887568 100644 --- a/macros/src/optional.rs +++ b/macros/src/optional.rs @@ -77,17 +77,15 @@ pub fn apply( // explicit `#[ts(optional)]` on field. // It takes precedence over the struct attribute, and is enforced **AT COMPILE TIME** (_, Optional::Optional { nullable }) => ( - // expression that evaluates to the string "?", but fails to compile if `ty` is not an `Option`. - parse_quote_spanned! { span => { - fn check_that_field_is_option(_: std::marker::PhantomData) {} - let x: std::marker::PhantomData<#field_ty> = std::marker::PhantomData; - check_that_field_is_option(x); - true - }}, + parse_quote!(true), if nullable { field_ty.clone() } else { - unwrap_option(crate_rename, field_ty) + // expression that evaluates to the the Option's inner type, + // but fails to compile if `field_ty` is not an `Option`. + parse_quote_spanned! { + span => <#field_ty as #crate_rename::IsOption>::Inner + } }, ), // Inherited `#[ts(optional)]` from the struct. diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index 47ada7cf..a41012d7 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -114,9 +114,16 @@ fn format_field( ); let optional_annotation = quote!(if #is_optional { "?" } else { "" }); + if field_attr.type_override.is_none() { + if field_attr.inline || field_attr.flatten { + dependencies.append_from(&ty); + } else { + dependencies.push(&ty); + } + } + if field_attr.flatten { flattened_fields.push(quote!(<#ty as #crate_rename::TS>::inline_flattened())); - dependencies.append_from(&ty); return Ok(()); } @@ -125,10 +132,8 @@ fn format_field( .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()) } }); diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index d2919a9f..41cfb0a6 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -66,15 +66,21 @@ fn format_field( field.span(), ); + if field_attr.type_override.is_none() { + if field_attr.inline { + dependencies.append_from(&ty); + } else { + dependencies.push(&ty); + } + } + 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()) } }); diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 08b90ff3..06fb42d8 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -666,9 +666,13 @@ impl Dependency { note = "`#[ts(optional)]` was used on a field of type {Self}, which is not permitted", label = "`#[ts(optional)]` is not allowed on field of type {Self}" )] -pub trait IsOption {} +pub trait IsOption { + type Inner; +} -impl IsOption for Option {} +impl IsOption for Option { + type Inner = T; +} // generate impls for primitive types macro_rules! impl_primitives { diff --git a/ts-rs/tests/integration/optional_field.rs b/ts-rs/tests/integration/optional_field.rs index 6d8ad200..ff7791fa 100644 --- a/ts-rs/tests/integration/optional_field.rs +++ b/ts-rs/tests/integration/optional_field.rs @@ -21,6 +21,21 @@ fn in_struct() { assert_eq!(OptionalInStruct::inline(), format!("{{ {a}, {b}, {c}, }}")); } +#[derive(Serialize, TS)] +#[ts(export, export_to = "optional_field/")] +struct GenericOptionalStruct { + #[ts(optional)] + a: Option, +} + +#[test] +fn in_generic_struct() { + assert_eq!( + GenericOptionalStruct::<()>::decl(), + "type GenericOptionalStruct = { a?: T, };" + ) +} + #[derive(Serialize, TS)] #[ts(export, export_to = "optional_field/")] enum OptionalInEnum {