Pick a lane

This commit is contained in:
ajeffrey@roblox.com 2022-03-29 14:28:39 -05:00
parent a67bce4b48
commit 4cac2eea03

View file

@ -45,11 +45,81 @@ Examples like this are quite common in TypeScript code, for example in [ReduxJS]
## Design
*This section to be filled in once we decide which alternative to use*
The design is to continue to use strict type aliases, but to use a
cache during type alias definition. Type aliases are handled as they
currently are, except for recursive cases.
When defining a type alias `T<a1,...,aN>`, if we encounter a recursive use
`T<U1,...,UN>` we proceed as follows:
* If every `UI` is `aI`, or if every `UI` does not contain any of the `aJ`s:
* look `<U1,...,UN>` up in the cache,
* if the cache lookup succeeds, return the cached result,
* otherwise create a fresh type variable, add it to the cache, and return it.
* Otherwise, produce a type error and return an error type.
Once we are finished defining the type alias, iterate through the cache
* for each entry with key `<U1,...,UN>` and value `X`, unify `X` with
the result of expanding `T<U1,...,UN>`.
* expanding `T<U1,...,UN>` may encounter a generic function,
for example `<b>(S) -> R`, which needs a bit of care, since `a` may occur free in
some of the `Ui`. We need to rename `b` to `c`, but this renaming may also
introduce new cache entries, since any cache entry for `U` containing `c`
needs a new cache entry for `U[c/b]`.
For example, with type
```
type TreeWithMap<a> = { data: a, children: {Tree<a>}, map: <b>(a -> b) -> Tree<b> }
```
The type alias is `Z` where
```
Z = { data: a, children: {Z}, map: <b>(a -> b) -> X }
```
the cache contains
```
<b> |-> X
```
and after unification
```
X = { data: b, children: {X}, map: <c>(b -> c) -> Y }
```
which introduces a new cache entry
```
<c> |-> Y
```
and after unification
```
Y = { data: b, children: {Y}, map: <b>(c -> b) -> X }
```
This algorithm can be generalized to handle mutual recursion, by using a cache for each mutually recursive type.
This design is the strictest one we came up with that copes with the examples we're interested in, in particular the `PromiseFor` example.
## Drawbacks
*This section to be filled in once we decide which alternative to use*
Renaming can double the size of the type graph, with the result that nested type aliases can result in exponential blowup.
This algorithm doesn't cope with examples which mix concrete and generic types
```
type T<a, b> = { this: a, that; b, children : {T<number, b>} }
```
This algorithm does not support type graphs with infinite expansions.
## Alternatives