# Loosening the recursive type restriction
## Summary
Luau supports recursive type aliases, but with an important
restriction: users can declare functions of recursive types, but *not*
recursive type functions. This has problems with sophisticated uses of
types, for example ones which mix recursive types and nested generics.
This RFC proposes loosening this restriction.
## Motivation
Luau supports recursive type aliases, but with an important restriction:
users can declare functions of recursive types, such as:
```lua
type Tree = { data: a, children: {Tree} }
```
but *not* recursive type functions, such as:
```lua
type TreeWithMap = { ..., map: (a -> b) -> Tree }
```
These examples cope up naturally in OO code bases with generic types.
## Design
*This section to be filled in once we decide which alternative to use*
## Drawbacks
*This section to be filled in once we decide which alternative to use*
## Alternatives
### Lazy recursive type instantiations
The most permissive change would be to make recursive type
instantiation lazy rather than strict. In this approach `T` would
not be instantiated immediately, but only when the body is needed. In
particular, during unification we can unify `T` with `T` by
first trying to unify `U` and `V`, and only if that fails try to unify
the instantiations.
*Advantages*: this allows recursive types with infinite expansions like:
```lua
type Foo = { ..., promises: {Foo>} }
```
### Lazy recursive type instantiations with a cache
As above, but keep a cache for each type function.
*Advantages*: reduces the size of the type graph.
### Strict recursive type instantiations with a cache
Rather than lazily instantiating type functions when they are used, we
could carry on instantiating them when they are defined, and use a
cache to reuse them. In particular, the cache would be populated when the
recursive types are defined, and used when types are used recursively.
For example:
```
type T = { foo: T? }
```
would result in cache entries:
```
T = { foo: T? }
T = { foo: T? }
T = { foo: T? }
```
This can result in exponential blowup, for example:
```
type T = { foo: T?, bar: T? }
```
would result in cache entries:
```
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
T = { foo: T?, bar: T? }
```
Applying this to a type function with N type variables results in more than 2^N
types. Because of blowup, we would need a bound on cache size.
This can also result in the cache being exhausted, for example:
```
type T = { foo: T>? }
```
results in an infinite type graph with cache:
```
T = { foo: T>? }
T> = { foo: T>>? }
T>> = { foo: T>>>? }
...
```
*Advantages*: types are computed strictly, so we don't have to worry about lazy types
producing unbounded type graphs during unification.
### Strict recursive type instantiations with a cache and an occurrence check
We can use occurrence checks to ensure there's no blowup. We can restrict
a recursive use `T` in the definition of `T` so that either `UI` is `aI`
or contains none of `a1...aN`. For example this bans
```
type T = { foo: T>? }
```
since `Promise` is not `a` but contains `a`, and bans
```
type T = { foo: T? }
```
since `a` is not `b`, but allows:
```
type T = { foo: T? }
```
*Advantages*: types are computed strictly, and may produce better error messages if the occurs check fails.