kaleidawave / ezno

A JavaScript compiler and TypeScript checker written in Rust with a focus on static analysis and runtime performance
https://kaleidawave.github.io/posts/introducing-ezno/
MIT License
2.3k stars 42 forks source link

`never` type lookup - "Cannot find type never" #136

Open CharlesTaylor7 opened 2 months ago

CharlesTaylor7 commented 2 months ago
function loops(): never {
    while (true);
}

loops() satisfies never

When I check this block of code I get an error of "Cannot find type never".

I suspect this is an issue at the parsing stage, because there is already code in the subtyping logic that checks for the never type.

CharlesTaylor7 commented 2 months ago

@kaleidawave I started a branch for never related issues. It's necessary to get Pick, Omit, Exclude and Extract utility types to work properly. https://github.com/CharlesTaylor7/ezno/tree/never-type

Also, confirmed that tsc does enough reachability analysis to verify the return type of an infinite loop is never, instead of void. https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAGznADgZwBQEoBciYApgG7EBOiA3gFCIOIDuAFjMsYtlBSMbgG5aAX1q1UGHLkSYAhrEzAYxTETKVaQA

kaleidawave commented 2 months ago

Ah yes this is a good bug.

So some types are special as they need to be referenced inside the checker. These types such as string, any, Array have a fixed TypeId that is set ahead of time. Each id references the index of the type in this vector in the root global type store. Here never is types[1].

The reason why it "Cannot find type never" here is because although the type exists, its name isn't registered as a named type in the context. Looks like I missed it out here 🤦‍♂️.

I have checked your branch and seen you have done it by adding a override to the type name lookup behaviour which is another way to fix it! It does mean you can't create another type or type parameter named never but I think that is a totally fine. If you want to PR will merge.

And yes I do want to somehow sync up the "fixed TypeId declarations (in types/mod.rs) <-> Type in (in types/store.rs) <-> root global names (in context/root.rs". I haven't figured out a simple way to do that atm..


Awesome, so the next part is about the unreachability. That's also not implemented. You can check out features/iteration.rs for the current while, for implementations. I can give some help on the process in there if you want to add infinite loop detection there (and maybe never run detection while (false))! (or course the halting problem means it won't catch all cases). Might be worth rewording this one or opening a new issue for that 👍

CharlesTaylor7 commented 2 months ago

@kaleidawave It's an interesting question you posed about shadowing never with another type. I've never tried to do that before, so I checked in TS playground. Apparently certain types are treated like keywords and cannot be shadowed.

https://www.typescriptlang.org/play?#code/C4TwDgpgBAdhBuEBOUC8UCsBuAUAMwFcYBjYASwHsYAeORJAPgAo8KKAuWBZASigG8cOAL5DQkKAGdgSMjADmaKABZchEuSrVpshc1YcpMufL6CRQA

kaleidawave commented 2 months ago

Interesting. That's actually quite a good idea. I could see having a type parameter named never could cause a lot of a confusion.

So your addition to the type annotation name lookup is correct and my suggestion about the Root context named typed would create diverging behaviour with TSC.

I think type shadowing works ATM (although it seems I have added any tests for it). Adding prevention and emitting of errors around invalid type aliases and type parameter names needs adding.