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.32k stars 42 forks source link

Proxy support #33

Open kaleidawave opened 1 year ago

kaleidawave commented 1 year ago

As much as want it not to exist. I guess it needs to be supported.

Steps

TODO

PatrickLaflamme commented 2 months ago

The more I investigate implementing this, the more it grows in scope.

Are we interested in ensuring that the Proxy handler returns values that respect the type definition of the proxied type? For example, we could implement a Proxy like the following:

type SomeType = {
    a: number
}

const handler = {
    get(_target: SomeClass, _propKey: keyof SomeType) {
        return "invalid"
    }
}

const proxiedType = new Proxy<SomeType>({ a: 4 }, handler) 

const b: number = proxiedType.a

The above proxy is invalid because the get method always returns a string, but the proxied type has only one property which is a number. However, even the typescript type checking as it exists today doesn't fail at compilation. From what I can tell, we could do something similar to what we did in #138 and run the handler methods where invoked to make sure it returns the type we think it will. It's also worth noting that there are many traps in the Proxy type that can intercept just about any usage of the proxied type (eg. get, set, in, new, construct etc.)

kaleidawave commented 2 months ago

Hmm yes looking at this there is quite a lot to cover. I think the first step would to be get

const p = new Proxy({}, { get(target, key) { return key });
print_type(p.something)

to print "Something". Then from there setters and calling should be good for a MVP.

I think the last time I attempted it there were some pieces missing in the checker and I couldn't create a SpecialObject::Proxy type never mind start using it.

Will have another look today, I think it might be possible now. I will try and get the foundation wired up, so others can tie up and test the trap logic.


Type checking is harder: how can we know that p satisfies { a: "a", b: "b" }? Subtyping doesn't run side effects and constant logic, so it might be more strict about what can be passed (aka disallow some some proxies that are technically valid at runtime).

kaleidawave commented 1 month ago

Hey @PatrickLaflamme, I have added some of the Proxy constructor stuff for #146 so this looks good to start on if you want to give it a crack.

Firstly Type::SpecialObject(SpecialObject::Proxy(..)) is being created successfully by the constructor

image

So for the parts left to implement:

I have created a branch for the property access logic here

https://github.com/kaleidawave/ezno/blob/bdac106f180f8b4230a0ed667b6f600910542648/checker/src/types/properties.rs#L513-L516

You basically want to get the trap type of handler and then call it with all the relevant information. As you implemented setters in #138 you should be proficient.

Once you can get the example above working in a PR

const p = new Proxy({}, { get(target, key) { return key });
print_type(p.something)

then setting and calling should be okay.

Can ignore other traps for the first iteration.

I have some ideas for subtyping but they can also be worked out later.