The current type inference scheme is such that we first introduce the
types for annotation functions, then check their bodies without
additional re-generalization. As part of generalization, we also perform
occurs checks to fix-up recursive tag unions.
However, type annotations can contain type inference variables that are
neither part of the generalization scheme, nor are re-generalized later
on, and in fact end up forming a closure of a recursive type. If we do
not catch and break such closures into recursive types, things go bad
soon after in later stages of the compiler.
To deal with this, re-introduce the values of recursive values after we
check their definitions, forcing an occurs check. This introduction is
benign because we already generalized appropriate type variables anyway.
Though, the introduction is somewhat unnecessary, and I have ideas on
how to make all of this simpler and more performant. That will come in
the future.
When constraining a recursive function like
```
f : _ -> {}
f : \_ -> f {}
```
our first step is to solve the value type of `f` relative to its
annotation. We have to be careful that the inference variable in the
signature of `f` is not generalized until after the body of `f` is
solved. Otherwise, we end up admitting polymorphic recursion.
With fixpoint-fixing, we don't want to re-unify type variables that were
just fixed, because doing so may change their shapes in ways that we
explicitly just set them up not to be changed (as fixpoint-fixing
clobbers type variable contents).
However, this restriction need only apply when we re-unify two type
variables that were both involved in the same fixpoint-fixing cycle. If
we have a type variable T that was involved in fixpoint-fixing, and we
unify it with U that wasn't, we know that the $U \notin \bar{T}$, where
$\bar{T}$ is the recursive closure of T. In these cases, we do want to
permit the usual in-band unification of $T \sim U$.
When we attempt to bind a type argument to an ability in an alias/opaque
instantiation, we create a fresh flex var to represent satisfaction of
the ability, and then unify the type argument with that flex var.
Previously, we did not register this fresh var in the appropriate rank
pool.
Usually this is not a problem; however, our generalization algorithm is
such that we skip adjusting the rank of redundant variables. Redundant
variables are those that are in the same unification tree, but are not
the root of the unification trees.
This means that if such a flex able var becomes the root of a
unification tree with the type argument, and the type argument is itself
generalized, we will have missed generalization of the argument.
The fix is simple - make sure to register the flex able var into the
appropriate rank pool.
Closes#4408
Programs like
```
after : ({} -> a), ({} -> b) -> ({} -> b)
fx = after (\{} -> {}) \{} -> if Bool.true then fx {} else {}
```
are legal because they always decay to functions, even if they may not
look like functions syntactically. Rather than using a syntactic check
to check for illegally-recursive functions, we should only perform such
checks after we know the types of values.
Closes#4291
The mutual-recursion checks does not admit types that are not function
types; because Roc is strict, only functional values can be involved in
mutual recursion. However, this check was exercised by checking the head
constructor of a type, which is not the correct way to do it. Aliases
and opaque types may in fact be function types as well, so we must chase
their actual contents.
Closes#4246
We have this idea of "rigid optional" fields to annotate record fields
that must necessarily be optional. That avoids the admission of programs
we cannot faithfully compile, like
```
f : {a: Str, b ? U64}
f = {a: "b", b: 1}
```
We want to lose the rigidity restriction when a generalized symbol is
used as at a specialized site; for example it should be possible to call
`f : {x ? Str} -> {}` with both `{}` and `{x : Str}`, neither of which
have a rigidly optional field unless they were to be annotated.
Prior to this commit we would loosen the rigidity restriction upon
specialization of a generalized type at a use site. However, what we
really want to do is apply the loosening during calculation of
generalization. The reason is that otherwise, we must make types that
would be ground (like `{x ? Str} -> {}`) generalized just for the sake
of the optional field annotation. But since the rigidity constraint is
irrelevant after an annotated body has been checked, we can loosen the
rigidity restriction then, which conveniently happens to coincide with
the generalization calculation.
Closes#3955
Closure captures can be transient, but previously, we did not handle
that correctly. For example, in
```
x = ""
inner = \{} -> x
outer = \{} -> inner {}
```
`outer` captures `inner`, but `inner` captures `x`, and in the body of
`outer`, we would not construct the closure data for `inner` correctly
before calling it.
There are a couple ways around this.
1. Update mono to do something when we are passed the captured
environment of a closure, rather than attempting to construct a
call-by-name's captured environment before callign it.
2. Fix-up closures during canonicalization to remove captured closures
that themselves capture, and replace them with their captures.
This patch does (2), since (1) is much more involved and is not likely
to bring a lot of wins. In general I think it's reasonable to expect
captured environments, even if transient, to be fairly shallow, so I
don't think this will produce very large closure environments.
Closes#2894