-
Notifications
You must be signed in to change notification settings - Fork 115
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
Bug: Type Parameters are not imported #241
Conversation
While deriving To do that, we could just use the type we're given, and get the generic parameters out of it. pub fn push(&mut self, ty: &Type) {
self.0.push(quote![.push::<#ty>()]);
let generics: Vec<Type> = extract_generics(ty);
generics.for_each(|g| self.push(g);
} HOWEVER, that would immediately break if type aliases are used: type MyAlias = Vec<TypeA>;
struct TypeB {
a: MyAlias
} Here, we only pass So, instead of that, what pub fn push(&mut self, ty: &Type) {
self.0.push(quote![.push::<#ty>()]);
self.0.push(quote![
.extend(<#ty as ts_rs::TS>::generics())
]);
} For that, we'd need this method In other words, Entirely possible that i'm missing a simpler solution here, but I think that's the right approach. |
@escritorio-gustavo I've implemented that fix here. The example still fails, but that issue might be orthogonal. EDIT: I think that test still fails because I haven't yet updated the impls for |
Nice! I spent the last 30 minutes trying to get this to work before realizing there was the fn generate_generics_fn(generics: &Generics) -> TokenStream {
let generic_types = generics
.type_params()
.map(|ty| ty.ident.clone())
.collect::<Vec<_>>();
if generic_types.is_empty() {
return quote!{
fn generics() -> impl ts_rs::typelist::TypeList {}
};
}
let generic_types = generic_types
.iter()
.fold(
quote!(),
|acc, cur| quote!{
(std::marker::PhantomData<#cur>, #acc)
}
);
quote!{
fn generics() -> impl ts_rs::typelist::TypeList {
#generic_types
}
}
} |
Alright, CI passes. (Meaning that our test coverage still isn't ideal ^^). I haven't really touched the impls for |
Great! At least our underlying problem with generics is fixed! Now we just have to decide on how to even handle the impls I mentioned in the discussion |
I think we have a new problem... #[derive(TS)]
#[ts(export)]
struct Test {}
#[derive(TS)]
#[ts(export)]
enum MyOption<T> {
Some(T),
None
}
#[derive(TS)]
#[ts(export)]
struct Outer {
a: MyOption<Test>
}
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Test } from "./Test";
export type MyOption<T> = { "Some": T } | "None"; It now imports |
Ah :D Gotta look into that a bit deeper. Definetely something we want to fix (I even enabled errors for unused imports in the CI), but at least it's not as bad as a missing import. |
I think I found out why. The expansion of #[allow(clippy::unused_unit)]
fn dependency_types() -> impl ts_rs::typelist::TypeList
where
Self: 'static,
{
{
use ts_rs::typelist::TypeList;
().push::<MyOption<Test>>() // This should be MyOption<ts_rs::Dummy>
.extend(<MyOption<Test> as ts_rs::TS>::generics())
}
} I have copied the expansion and replaced the type manually, which fixed |
Yup, seems that way to me as well
Sure!
If this really is the solution, I think type aliases are gonna be a problem again lol |
True! I think we can do it a bit differently though: Currently, we start at If we had some way of first doing that conversion, and then doing the imports, we'd be fine |
One approach would be to make the conversion an operation independant of One way of doing this would be with an associated type. This is pretty ugly, especially since the dummy types would need to be in the public scope. With Without fn decl() -> String {
struct Visit(String);
impl TypeVisitor for Visit {
fn visit<T: TS>(&mut self) {
self.0 = T::decl_concrete();
}
}
let mut visit = Visit(String::new());
Self::with_generic(&mut visit);
visit.0
} Alternatively, we could also do the import generation inside |
Okay... I'm gonna be honest, I have no idea what any of that means lol.
What would that look like? |
So if I got this right, we'd add
|
I couldn't find this error anywhere. The closest is #168, which brings extra imports to the type itself, but not its dependencies |
No, sorry, I explained that very poorly. As far as I can tell, all dependencies and all let mut out = String::new();
generate_imports::<MyOption<Test>>(&mut out);
out.push_str(MyOption<Dummy>::decl()); So tl;dr: The type for which we generate the imports is not the type which we generate.
Therefore, my suggestion was to swap the generic type for our Dummy type before generating the imports. Does that make a bit more sense? Happy to expand on this a bit more if anything is still unclear. |
Just confirmed that the same thing happens on main, so it's not actually related to the changes in this PR. |
Yeah, that makes more sense. So the |
Maybe we should rename the |
I think this also needs to be done for |
Hey! That being said, it does work like you did here, because
So, if we keep it like this, I agree, we'd want to rename the associated type. Maybe I can also give the original idea (using the on-the-fly generated dummy types) a try to see how that'd look. Might be more complicated, but we'll see. |
…to type-params-are-not-imported
This reverts commit f4572ea.
Now we actually see the real error since the files dont get overwritten! Seems like internally tagged enums don't like self-references. Not sure we can do anything about that. |
Looks like it doesn't like self references with type intersection |
Agreed. Self references are only allowed as property values |
Oh, look at that! Someone left a comment about this xD
If that was me, then I dont remember ^^ I'll remove the |
This either never worked (as in the TS generated was always invalid), or it is a regression caused by enum flattening support |
Well that literally explains a lot xD
Git blame says it was you 😆 |
Okay, I did some thinking. This never worked in the first place, because it cant! For one, the internally-tagged representation doesn't allow newtype/tuple variants. Serde just panics at runtime for them. "Internally tagged" means "put the tag inside the variant". But if the variant is not an object to put the tag in, this cant work. Sensible types, like this one here work, since they don't use the type intersection: #[derive(TS)]
#[ts(tag = "tag")]
enum X {
A { inner: Box<Self> },
B { inner: i32 }
} export type X = { "tag": "A", inner: X, } | { "tag": "B", inner: number, }; |
That makes a lot of sense! Especially given that one of the variants was trying to do { "tag": "E" } & Array<I> which is just nonsense |
Added a note on the wiki about that |
Awesome!
This made me curious, so I added tests for the 4 remaining types using |
I think it's because |
Yep! Seems like I just skipped that (since it didnt break any tests 😆) |
That's odd, in my computer TS complains that |
I'll be out for a about 2 hours now, so I'll be unable to respond. See you later |
Interesting! Seems like that's only caught with I think it'd be alright if we merge this. It's getting annoying to scroll through this huge PR. Before the next release, I'd like to
Thank you so much for your work, it has been invaluable! I'm super stoked to see where this library is going. |
Yup, this PR is way too big already
While adding
Definetly something I'd like to see!
Cool! Would that be a feature flag or a new attribute?
I don't see much of a problem, as long as the next release is
Thanks! It's awesome to be able to help out with a project that I use a lot! |
No description provided.