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

Non-type subtyping and type pairing #159

Open kaleidawave opened 1 month ago

kaleidawave commented 1 month ago

Currently type pairing (/generic inference) is done during subtyping

For

function x<T>(t: T): T { return t }

x(5)

During subtyping in the function invocation: the 5 argument is matched against the T parameter.

The current implementation first checks whether 5 meets the constraint of T. It is any in this case and thus passes.

Then it adds (T, 5) to the current state of subtyping, which effectively implies that T=5 and during return substitution it substitutes any T references with literal type 5.

This currently works very well. Additionally it:


This works when doing TypeId <=> TypeId comparison. The problem is for the following

Possible implementation 1

Move the calculation into the type reference. Aka something like infer T like `Hello ${string}` ? remove first 6 characters from T :... and do the logic internally.

Doesn't solve mapped type property key issue

Possible implementation 2

Make the paired result into a enum. Little bit scared that adds overhead to core item for the sake of TS consistency? Started on enum PairRHS { Type(TypeId), PropertyKey(PropertyKey<'static>), SliceOf(...) } in #146 but paused to get it merged. Maybe should have another go?

kaleidawave commented 2 weeks ago

Subtyping

Subtyping of

and the combination isn't too difficult. Might require some abstraction away from the type_is_subtype function, a CompareToString type etc

array slices are similar, but that is comparing slices of TypeIds rather than chars

Pairing / generic inference / contributions

This is the harder issue

For mapped types with as, this requires pairing types during property lookup

export type Mapped<T> = {
    [P in (keyof T & string) as `property_${P}`]: T[P]
}

function func(p: Mapped<{ a: string }>) {
    p.property_a
}

Adding contributions to property lookup matching is quite complex.

Desugaring

instead maybe

export type Mapped<T> = {
    [P in (keyof T & string) as `property_${P}`]: T[P]
}
// under synthesis it could be synthesised as the following. Where the `P` is moved to the RHS
export type Mapped<T> = {
    [P in (keyof T & string) as `property_${keyof T & string}`]: T[Slice<P, 0, 8>]
}

Where Slice is an internal type.

This could be implemented quite simply here because the root would be just P type argument

https://github.com/kaleidawave/ezno/blob/5191329edd448c364914917297bef2846527b91b/checker/src/types/properties.rs#L1034-L1044

kaleidawave commented 1 week ago

Managed to get type_is_subtype into key_matches.

The next part is to try and get any matches through. There are two problems

  1. get_property_unbound needs to pass the slice through. To do this it could do through Logical::Implies. However that requires changing either GenericChainLink, GenericArguments or ExplicitTypeRestrictions. Don't like GenericArguments or ExplicitTypeRestrictions because that is PartiallyAppliedGenerics and that is a lot simpler using TypeId. Also that is perfectly fine because PartiallyAppliedGenerics is created with &mut TypeStore. GenericChainLink is a little harder.
  2. Similarly during subtyping, there is no &mut TypeStore, so getting these arguments out needs a bit of work :/