Patterns and GADTs (was: Reviewing feedback on patterns in switch)

Brian Goetz brian.goetz at
Tue Jan 25 19:49:08 UTC 2022

> 3.  Type refinements for GADTs

There are a number of unsatisfying aspects to how we currently handle GADTs; specifically, we are missing the type refinement process outlined in "Simple unification-based type inference for GADTs” (SPJ et al, 2005).  Here are some examples of where we fall down.  

Suppose we have

    sealed interface Node<T> { }
    record A<T>(T t) extends Node<T> { }
    record B(String s) extends Node<String> { } 

and we want to write:

    <T> T unbox(Node<T> n) { 
        return switch (n) { 
            case A<T> n -> n.t;
            case B n -> n.s;

The RHS of all the arrows must be compatible with the return type, which is T.  Clearly that is true for the first case, but not for the second; the compiler doesn’t know that T has to be String if we’ve matched a Node<T> to B.  What is missing is to refine the type of T based on matches.  Here, we would gather an additional constraint on `case B` for T=String; if we had a case which was covariant in T:

    record C<T extends B>(T t)

then a `case C<T>` would gather an additional constraint of T <: B for its RHS.  

More generally, any code dominated by a match that provides additional bounds for type variables could benefit from those bounds.  For example, we’d probably want the same in:

    if (n instanceof B b) { /* T is String here */ }

and (as with flow scoping): 

    if (! (n instanceof B b))
        throw …
    // T is String here

We can probably piggy back the specification of this on flow scoping, appealing to “wherever a binding introduced by this pattern would be in scope.”  

More information about the amber-spec-experts mailing list