-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement generic inlining with trait bounds #217
Implement generic inlining with trait bounds #217
Conversation
@NyxCode what do you think of this approach? |
I like that this fixed the issue, but to be honest, I kinda dislike that we have to fix it. |
Don't worry about it, this one can wait |
In that case I'm gonna convert this to a draft to signify that it's on hold |
Reopening this PR as discussed in #219 |
@@ -34,7 +35,7 @@ impl DerivedTS { | |||
.params | |||
.iter() | |||
.filter(|param| matches!(param, GenericParam::Type(_))) | |||
.map(|_| quote! { () }); | |||
.map(|_| self.unit_type.as_ref().map(|x| x.to_token_stream()).unwrap_or(quote!{ () })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This here looks interesting!
What happens with something like this, when there are multiple generic parameters with different bounds?
#[ts(export)]
struct X<A: ToString, B: IntoIterator<Item = u32>> {
a: A,
b: B
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type given to #[ts(unit_type = "...")]
will be used for all generics, same as ()
, so it must implement all trait bounds from all of the generic parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, makes sense! Just an idea here: What if the attribute didnt only parse one type, but a list of types? That should just work, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the attribute didnt only parse one type, but a list of types?
What do you mean? One type per generic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, we'd get rid of the hardcoded "null", and a user could specify any "default generic type", without the need to declare his own "unit" type.
#[derive(TS)] #[ts(default_generics = "String, Box<dyn Error>")] struct Something<S: AsRef<str>, E: Error>();But: TypeId::of requires 'static, and idk if it's even possible to work around that in this case.
Yeah, I tried that when I was making the first PR about inlining generics and couldn't get past this, but the bigger problem is, if the user actually wanted at some point to use the struct with the default generic (String
in this example), inlining the generic would fail, emiiting S
instead of string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the user actually wanted at some point to use the struct with the default generic (String in this example), inlining the generic would fail, emiiting S instead of String
What I mean here is:
#[derive(TS)]
#[ts(default_generics = "String")]
pub struct Generic<T: ToString> {
pub x: T,
}
#[derive(TS)]
pub struct Foo {
#[ts(inline)]
pub a: Generic<String>, // This will fail, will emit `T` instead of `string` because String == String
#[ts(inline)]
pub b: Generic<i32>, // This will not fail because i32 != String
#[ts(inline)]
pub b: Generic<&'static str>, // This will not fail because &str != String
}
This generates
type Foo = { a: { x: T, }, b: { x: number, }, c: { x: string, }, }
// ^ should be `string`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another alternative is, when deciding whether to use T
or string
, we need to know if the generic passed to the field is the identifier of one of the struct definition generics or an actual type.
Don't know how we could get this information though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: I think I made it work. Only hacked something together, but it does pass the tests. It's not as robust as the
TypeId
solution before, but it might be preferable to the"null"
solution - i dont know yet. Here's how it looks like. What do you think?return quote!( if std::any::type_name::<#generic_ident>() == std::any::type_name::<#unit_type>() { #generic_ident_str.to_owned() } else { <#generic_ident>::inline() } );If that's something you'd like to look in further, I'd be happy to clean my hacky solution up a bit & push it to a branch for you to check out.
This looks interesting, let's take a look!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I tried that when I was making the first PR about inlining generics and couldn't get past this, but the bigger problem is, if the user actually wanted at some point to use the struct with the default generic (String in this example), inlining the generic would fail, emiiting S instead of string
Yeah, I think you're right. Let's scratch that idea.
Another alternative is, when deciding whether to use T or string, we need to know if the generic passed to the field is the identifier of one of the struct definition generics or an actual type.
Don't know how we could get this information though
This might be worth exploring. I'll play with that idea if I find some time later. I do have the feeling though that we'd have to make a somewhat big change to the TS
trait, so I'm not sure yet if it'd be worth it.
Rest looks good! |
Yeah, I also struggled with that, still have no idea what would be a good name for this attribute |
Also, I just realized what would probably fix this, if that feature ever comes to stable - #![feature(never_type)]
struct X<T: ToString>(T);
type XX = X<!>; |
(there's no point in waiting for it, the Rust PR is from 2016) |
That's a huge shame, I'm sure |
Maybe |
Nope, just tried it |
WOW, wait a minute! |
Okay, never mind 😅 |
Yeah, I think there is currently no way to do this properly |
So I've spent some time experimenting, but I haven't been able to come up with a nicer way to handle generics. If you believe there's a need for this feature1, I think we can merge this. I'll leave it up to you to decide if adding this feature is worth the added complexity. There's no docs so far - maybe a sentence at the very bottom of the docs for the Footnotes
|
Interesting! I haven't been writing Rust for very long (just under a year now) so I never realized trait bounds on structs were such a bad idea. |
I'm fine with closing this PR in light of that! It really adds a lot of complexity with the totally useless unit type that must implement all traits required by the struct and the attribute name is not helpful at all. |
I love the idea of creating a wiki! It's usually dificult to find docs for derive macro helper attributes through LSP definitions (which don't autocomplete, only telling you the mispelled word isn't accepted) or docs.rs, which only allows you to document the trait itself, making something like |
This should be the last PR related to inlining generics (unless a new bug shows up)
Now, when dealing with trait bounds, instead of the unit type
()
, a unit struct defined by the user that implements all the given traits must be passed into#[ts(unit_type = "path::to::Struct")]
Example
Fixes #214
TODO:
null
(maybe this should be a separate PR?)