mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Pick a lane
This commit is contained in:
parent
a67bce4b48
commit
4cac2eea03
1 changed files with 80 additions and 10 deletions
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue