Closed NyxCode closed 6 months ago
Awesome stuff! I'm still struggeling a bit to understand why this works though.
I read the code a few times, and my understanding of it is:
So for
struct Struct<A, I: Iterator> {
a: Vec<A>,
i: I::Item,
}
the associated types are [I::Item]
, and the type parameters used outside of associated types are [A]
.
So in the end, we've got impl<I, A> where I: Iterator, I::Item: TS, A: TS
.
That seems perfect, great!
I'm still struggeling a bit to understand why this works though.
Yeah, I even failed to failed to realize I had to filter_map
rather than just filter
on the first commit that added filter_ty
.
Here's a rough overview of how this works:
dependencies.types
filter_ty
, which will result in impl Iterator<Item = Vec<Type>>
impl Iterator<Item = Type>
HashSet
to remove duplicatesVec
because HashSet
can't be used in quote!
Now here's what filter_ty
does:
(T)
(not to be confused with a tuple with one element (T,)
- notice the comma), all of these types are just some wrapper around some type T
, so we call fillter_ty
on that inner type(T, U, ...)
we must run filter_ty
on each type of the tuple (this is why I changed filter_ty
to return Option<Vec<Type>>
)None
Some(vec![])
to respect the function signaturePathArguments::AngleBracketed
)None
filter_ty
on every generic of the type (like we did in the tuple case)Is there anything you think still needs to be done here? I'm pretty happy with where this ended up, especially with the new trait bounds 😊
Is there anything you think still needs to be done here?
There is nothing I can think of, from my side this is ready for merge. @WilsonGramer in its current state does this PR fully resolve your issue?
I'm pretty happy with where this ended up, especially with the new trait bounds 😊
Me too! And creating that filter function turned out to be a lot of fun :D
@WilsonGramer in its current state does this PR fully resolve your issue?
I think it may be a while until he responds, maybe we should merge this and if needed @WilsonGramer can open a new issue. This PR is already getting way too long anyway
@escritorio-gustavo @NyxCode Sorry for the delay, I will test it now!
@escritorio-gustavo It looks like this example with nested structs fails to compile:
use ts_rs::TS;
trait Driver {
type Info;
}
struct TsDriver;
#[derive(TS)]
struct TsInfo;
impl Driver for TsDriver {
type Info = TsInfo;
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Inner<D: Driver> {
info: D::Info,
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Outer<D: Driver> {
inner: Inner<D>,
}
error[E0277]: the trait bound `<D as Driver>::Info: ts_rs::TS` is not satisfied
--> src/lib.rs:25:12
|
25 | inner: Inner<D>,
| ^^^^^^^^ the trait `ts_rs::TS` is not implemented for `<D as Driver>::Info`
|
note: required for `Inner<D>` to implement `ts_rs::TS`
--> src/lib.rs:16:10
|
16 | #[derive(TS)]
| ^^ unsatisfied trait bound introduced in this `derive` macro
= note: this error originates in the derive macro `TS` (in Nightly builds, run with -Z macro-backtrace for more info)
I think it's adding the bound to D
because it thinks D
is being used in Inner
, when it's actually only the associated type that's being used. I ran into this issue in my compiler because I have, for example, an Expression<D>
that contains a Type<D>
— both types only use D::Info
.
If I add a Info: TS
bound in the trait, I get another error:
use ts_rs::TS;
trait Driver {
type Info: TS;
}
struct TsDriver;
#[derive(TS)]
struct TsInfo;
impl Driver for TsDriver {
type Info = TsInfo;
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Inner<D: Driver> {
info: D::Info,
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Outer<D: Driver> {
inner: Inner<D>,
}
error[E0277]: the trait bound `TsDriver: ts_rs::TS` is not satisfied
--> src/lib.rs:22:10
|
22 | #[derive(TS)]
| ^^ the trait `ts_rs::TS` is not implemented for `TsDriver`
|
= help: the following other types implement trait `ts_rs::TS`:
bool
char
isize
i8
i16
i32
i64
i128
and 66 others
note: required for `Outer<TsDriver>` to implement `ts_rs::TS`
--> src/lib.rs:22:10
|
22 | #[derive(TS)]
| ^^ unsatisfied trait bound introduced in this `derive` macro
note: required by a bound in `ts_rs::TS::WithoutGenerics`
--> /Users/wilson/.cargo/git/checkouts/ts-rs-092fe627e6f08b9c/95e6dac/ts-rs/src/lib.rs:332:27
|
332 | type WithoutGenerics: TS + ?Sized;
| ^^ required by this bound in `TS::WithoutGenerics`
= note: this error originates in the derive macro `TS` (in Nightly builds, run with -Z macro-backtrace for more info)
I believe this is the same issue — it thinks D
is used directly because it's passed as a generic parameter to another type.
I am not sure there's anything we can do to automatically fix this, since that would require struct Outer<D: Driver> where D::Info: TS
even though Outer
doesn't directly contain D::Info
, but at least the following code compiles:
use ts_rs::TS;
trait Driver {
type Info;
}
#[derive(TS)]
struct TsDriver;
#[derive(TS)]
struct TsInfo;
impl Driver for TsDriver {
type Info = TsInfo;
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Inner<D: Driver> {
info: D::Info,
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Outer<D: Driver>
where
D::Info: TS,
{
inner: Inner<D>,
}
You'd need to derive TS
for TsDriver
and add the where clause I mentioned manually
Alternatively, this also compiles:
use ts_rs::TS;
trait Driver {
type Info: TS;
}
#[derive(TS)]
struct TsDriver;
#[derive(TS)]
struct TsInfo;
impl Driver for TsDriver {
type Info = TsInfo;
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Inner<D: Driver> {
info: D::Info,
}
#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Outer<D: Driver> {
inner: Inner<D>,
}
Either way, you need TsDriver
to implement TS
because D
is directly used in Outer
@escritorio-gustavo What do you think about adding a #[ts(bound)]
attribute to suppress the bounds if needed?
#[derive(TS)]
#[ts(export, concrete(D = TsDriver), bound = "")]
struct Outer<D: Driver> {
inner: Inner<D>,
}
That way the D::Info: TS
bound is only added in the TS
impl for Outer
, and isn't required for all users of Outer
.
Yeah, I don't think there's a better trait bound we can generate here.
You'd run into the same issue with serde (literally the exact same error if you replace TS
with Serialize
).
There, there's a #[serde(bound(..))]
workaround to make the trait bounds more specific, though I have never used it.
There, there's a
#[serde(bound(..))]
workaround to make the trait bounds more specific, though I have never used it.
I've never used it either, I think this should be a brand new issue & PR showing the desired macro expansion, this PR is getting hard to follow
@WilsonGramer's snippet
#[derive(TS)] #[ts(export, concrete(D = TsDriver), bound = "")] struct Outer<D: Driver> { inner: Inner<D>, }
Here, the bound would still need to be
D::Info: TS
, becauseInner
is usingimpl<D: Driver> TS for Inner<D> where D::Inner: TS
. Just leaving it blank would not compile.
My suggestion would be to - unless there's something missing in this PR - merge this first, and tackle a potential #[ts(bound)]
separately.
The easiest approach there would be to have #[ts(bound(...)]
completely replace our trait bounds. Serde does that more granually on a per-field basis (which I think we could also pretty easily do), but we'll have to see which one makes more sense here.
I think opening a new PR for the bound
attribute makes sense! I can draft an issue later today.
Awesome, thanks!
Then we're all set & can merge this?
This PR aims to allow users to "disable" a generic parameter:
should behave as if the struct was declared without the generic parameter
Y
:Motivation
The main motivation behind this are associated types, which have no equivalent in TypeScript. Therefore, given a type like this, there's no generic TypeScript type we could generate:
The solution here is to "disable" the generic parameter "Y" by making it "concrete".
TODOs
TS::decl
andTS::decl_concrete
should behavedecl
is clear, anddecl_concrete
still uses the actual type parameters, even if#[ts(concrete)]
is present.TS::generics
though.