Closed escritorio-gustavo closed 5 months ago
Interesting! So _
refers to the original type, right?
I do think this is pretty neat, though I think it might be a bit unintuitive for anyone who doesn't know what _
does in that context.
Have you considered a different syntax, e.g #[ts(as = "Option<type>")]
. I feel like we might be able to come up with something more intuitive, but I'm not sure if the extra effort/complexity involved in parsing this would be worth it.
If the syntax was #[ts(as = "Option<$type>")]
, we might be able to just string-replace $type
with the actual type before parsing it into a syn::Type
. Pretty hacky, though.
Interesting! So
_
refers to the original type, right?
Yes, this is mostly a way to resolve the discussion in #175
I do think this is pretty neat, though I think it might be a bit unintuitive for anyone who doesn't know what
_
does in that context. Have you considered a different syntax, e.g#[ts(as = "Option<type>")]
. I feel like we might be able to come up with something more intuitive, but I'm not sure if the extra effort/complexity involved in parsing this would be worth it.
Both of these would require some heavy refactoring, because as
expects valid syntax for a type. Option<type>
isn't a valid type because type
is a keyword and any syntax we come up with (like Option<$type>
) is not gonna be valid either.
Type::Infer(_)
is the only thing I could think of that both:
syn
can parseIf the syntax was
#[ts(as = "Option<$type>")]
, we might be able to just string-replace$type
with the actual type before parsing it into asyn::Type
. Pretty hacky, though.
We could make a parse_type
function like:
fn parse_type(input: ParseStream) -> Result<Type> {
let str = parse_assign_str(input)?;
syn::parse_str(&str.replace("$type", "_"))
}
but since this function wouldn't have access to the original_type
the parser implemented in this PR would have to stay, and this function's job would be to replace our custom syntax with _
I think the understore is super intuitive, as it's already widely used throughout rust syntax. So the code is instantly readable. The other nice thing is that you could, for example, turn a type into a vector, such as as = Vec<_>
, or as = Result<_,Something>
, etc.
There is also precedent for this behavior in serde_with
https://github.com/jonasbb/serde_with?tab=readme-ov-file#examples
Annotate your struct or enum to enable the custom de/serializer. The
#[serde_as]
attribute must be placed before the#[derive]
.The
as
is analogous to thewith
attribute of serde. You mirror the type structure of the field you want to de/serialize. You can specify converters for the inner types of a field, e.g.,Vec<DisplayFromStr>
. The default de/serialization behavior can be restored by using_
as a placeholder, e.g.,BTreeMap<_, DisplayFromStr>
.
Oh, alright! Maybe it's just me then.
I feel like the semantics of _
are very different here, but I think you two convinced me!
Also, you were spot on with your take in #175. This solution keeps #[ts(optional)]
as it was. Specifically, #[ts(optional)]
still doesn't alter the type, just how it's represented. This seems super clean to me!
Interestingly, this kinda makes #[ts(optional = nullable)]
obsolete. It'd be equivalent to
#[ts(as = "Option<_>", optional)]
field: Option<i32>,
Interestingly, this kinda makes
#[ts(optional = nullable)]
obsolete. It'd be equivalent to#[ts(as = "Option<_>", optional)] field: Option<i32>,
I just hope no one is crazy enough to do that 😆
Goal
This PR aims to allow the use of infered types (
_
) inside the#[ts(as = "...")]
attribute. The_
type will be interpreted as the field's original type. E.g.:is the same as:
but without having to repeat the original type.
This allows us to somewhat support using
#[ts(optional)]
with any type, by using#[ts(as = "Option<_>", optional)]
without requiring us to change the semantics of#[ts(optional)]
Changes
Added a recursive function to traverse the provided type and replace
Type::Infer(_)
with the field's original typeChecklist