typecheck.macro
typecheck.macro copied to clipboard
Support "extends" and intersection operator.
Support
type A = B & C;
and
interface Foo extends Bar {}
This (and basic mapped types, like Record
) is the main issue that prevents me from using this to it's full potential in a project that uses intersection types a lot (as I have to duplicate / manually "intersect" the types to validate right now, which kinda defeats the purpose.. 😅)
Is there anything specific you're looking for help with?
I'm just finished a big refactor that will allow me to implement type intersection + other features.
Quick question: With the types you are intersecting, are they both in the same file?
I saw your commits, looking forward to the next version 🙂 For now I've restructured the parts that I typecheck to all be in the same file and without any intersections or mapped types, but of course it would be a lot nicer to be as flexible as possible with the locations and separations 😉
So the reason I asked about the types being in different files was because I just realized I can't validate multi-file types (Foo imports Bar from another file) using just a macro.
As such, I am trying to figure out if this is a big issue for users. If it is, I will build a CLI tool that can handle multi-file types.
Mh.. not sure how big of a problem that would be. Would registering the imported type / creating a separate validator in the source file help at all? (is there a way to "connect" these afterwards?)
I think it would probably be fine in most cases for now, seeing as in most cases you could just move the types to where the validation is created, and then import it in the other files from there instead. What about "library" types (something like Record
), would these also be affected the same way?
The fundamental issue is that Babel processes files one at a time. So I cannot wait until all files have been processed to build a global "namespace" of types.
The original idea was as you are describing: "register" the imported type in the source file. However, this doesn't work reliably because if the file with the exported type/the register call is processed after the file that consumes the registered/exported type, the macro will throw an error.
Library types won't be affected because I can just hardcode library types like Set
, Map
, Record
into the macro. (Infact, this is what I already do).
Another thing you can do is, instead of moving your types to where you want to generate your validation functions, just generate your validation functions where you declare your types and export them. This works because validation functions are just normal Javascript that can be imported/exported like anything else.
What is the status of this issue? Assuming I have all my types in the same file...
@schontz Intersection is supported. extends
is not supported, but I don't think it would be too bad to support it.
Let me know if "extends" support is important!
For my uses, yes, I use extends
all over the place. Is any form of extends
supported?
Hmm, right now no. I'll take a shot at implementing this -- it seems like the extends algorithm is simpler than intersection anyway.
In the meantime, intersection is a pretty viable alternative. It covers pretty much everything extends
does I think.
So just convert
interface Foo extends Bar {
id: string;
}
to:
type Foo = Bar & {
id: string;
}
Like that?
Yes, that should work.
OK. But what if I have generics? For example:
interface Foo<E extends string> {
data: E;
}
interface Bar extends Foo<'bar'> {}
const bar: Bar = { data: 'foo' }; // error
const bar2: Bar = { data: 'bar' }; // good
I believe intersection can cover everything extends
does.
The above example would become:
interface Foo<E extends string> {
data : E
}
type Bar = Foo<'bar'>
There's no intersection involved because the body of the Bar
interface is empty.
If the body of the interface wasn't empty (let's say it was: data2: Q
, where Q
is another generic parameter) then you would do
type Bar<Q> = Foo<'bar'> & { data2: Q }
Ok great. I'll see if I can get this to work. Thanks for all your help.
E extends string
doesn't cause any issues?
Hm, I should check that it doesn't. If it does -- I can fix that pretty quickly, since upper bound at types are compile time constraints that are not relevant at runtime.
OK. No big deal, because I control enough of my code that I can drop E extends string
. If I make E
a number, shame on me!
Just tested -- the E extends string
inside of the type parameter is supported.
This is great. Looking forward to playing with this.