Hello,
As I mentioned in a previous email, GHC seems currently somewhat uncertain about whether or not matching on GADTs is permitted in arrow notation. Sam Derbyshire suggested on Twitter that we probably do, ideally speaking, want to support them. Unfortunately, I am confident that the existing implementation is not up to this task, as I have now demonstrated in issues #20469 and #20470.
The latter of those two issues is particularly challenging to solve, as it highlights the sorts of tricky interactions that can arise when arrow notation is mixed with existential quantification. To give an example, suppose we have the following datatypes:
data A where
A :: a -> B a -> A
data B a where
B :: B ()
And suppose we have the following proc
expression:
proc a -> case a of
A x B -> id -< x
The match on the A
constructor introduces a locally-scoped skolem, and even though the use of id
appears on the RHS, it is not actually within the match’s scope—rather, its scope is that of the overall proc
expression.
This makes it tricky to maintain typechecker invariants, as
introduced metavariables must not accidentally leak into the outer scope
via these strangely-scoped expressions. For example, when typechecking
the above expression, we’ll increment tcl_tclevel
before typechecking the id -< x
command, and we may introduce fresh metavariables while doing so. This means we may end up with an expected type for id
that looks like this:
a1[tau:1] -> a2[tau:1]
However, when we actually check id
against that type, we must do so in a context where tcl_tclevel
is 0
, not 1
.
This violates WantedInv from Note [TcLevel invariants] in
GHC.Tc.Utils.TcType. This means we need to do a three-step process to
properly check id
’s type:
Synthesize a new metavariable a3[tau:0]
.
Emit [W] a3[tau:0] ~ (a1[tau:1] -> a2[tau:1])
within the arrow scope, i.e. where tcl_tclevel = 1
.
Check id
against a3[tau:0]
.
Discerning readers may note that this looks awfully similar to the process by which we promote a type via promoteTcType
,
as described in Note [Promoting a type] in GHC.Tc.Utils.TcMType.
However, in this case, we aren’t really promoting a type, but demoting
it—we ultimately want to decrease its level, not increase it. However, it
seems to me that the process of doing this demotion is in fact handled
perfectly fine by promoteTcType
. So my question is twofold:
Is my reasoning about what to do here correct?
Is there any harm in using promoteTcType
to do this demotion?
Thanks,
Alexis