Skip to content

Conversation

@gustavo-shigueo
Copy link
Collaborator

@gustavo-shigueo gustavo-shigueo commented Oct 25, 2025

Goal

Fix dependency tracking for generic type when using #[ts(optional)]

Using #[ts(optional)] on a field of type Option<T>, where T is an actual generic type in the struct declaration, caused the generation of the impl block to fail to include the where T: TS clause. This was caused by the fact that the used_type_params function couldn't see through the type generated by the application of #[ts(optional)], which is <T as ::ts_rs::TS>::OptionInnerType and would fail to detect the use of the type parameter T

To be clearer, the following type

#[derive(TS)]
struct Foo<T> {
  #[ts(optional)]
  foo: Option<T>
}

Would generate the following impl block:

impl<T> TS for Foo<T> {
  // ...
}

Instead of the correct impl block:

impl<T> TS for Foo<T> where T: TS {
  // ...
}

Closes #453

Changes

Added a match arm in used_type_params that handles the case Type::Path(TypePath { qself: Some(_), .. })

I also found a way to simplify the compile-time checking that ensures #[ts(optional)] cannot be used in non-option fields, with the added bonus that it moves the compiler error to the field's type instead of the attribute itself and makes it a lot shorter:

Before
image

After
image

Checklist

  • I have followed the steps listed in the Contributing guide.
  • If necessary, I have added documentation related to the changes made.
  • I have added or updated the tests related to the changes made.

@gustavo-shigueo gustavo-shigueo changed the title Fix generic optional Fix trait bound generation when using #[ts(optional)] on an Option<Generic> Oct 25, 2025
GenericOptionalStruct::<()>::decl(),
"type GenericOptionalStruct<T> = { a?: T, b?: T | null, c: T | null, d: T, e: number | null, f: number, };"
)
}
Copy link

@sinder38 sinder38 Oct 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration test will work with or without the changes.
Please use my example from the issue:

#[derive(Serialize, TS)]
#[ts(export, export_to = "optional_field/")]
struct GenericOptionalStruct<T1, T2>
{
	#[ts(optional)] // <- error is happening here
	a: Option<T2>,
	#[ts(optional = nullable)]
	b: Option<T1>,
	c: Option<T1>,
}

Copy link
Collaborator Author

@gustavo-shigueo gustavo-shigueo Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right, I tried adding more fields after I finished the fix to be thorough but I forgot that the test only fails if the ONLY field that mentions T is the one with #[ts(optional)], i.e., lines 31 - 34 ruin this test. I'll remove the extra fields

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep the current type though because I don't want the test to imply that the issue comes from having 2 or more generics

@gustavo-shigueo
Copy link
Collaborator Author

gustavo-shigueo commented Oct 26, 2025

I have also just found out that the reason the type <Option<T> as TS>::OptionInnerType failed to generate the where clause is NOT because it failed to update the type dependencies, instead it is because our used_type_params function is missing a match arm to handle Type::Path(TypePath { qself: Some(_), .. })

I've changed named and tuple to once again apply optional before tracking the dependency and instead changed used_type_params to handle qself as that is a more general solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Auto Genric Bounds failing for #[ts(optional)]

3 participants