Complications with multiple upper bounds #160
Description
@titzer's Jawa system made use of importing types with multiple upper-bound constraints. More generally, permitting type variables to have multiple upper-bound constraints is often quite useful for low-level type systems. For example, a user-level casting operation often takes the run-time representations of two unknown types (represented as type variables) and returns a boolean that, if equal to true
, guarantees that one type variable is a subtype of another, thereby imposing an additional upper bound on the former type variable. (Such an operation is often useful for, say, optimizing multiple assignments into a covariant array in reified polymorphic code: perform one cast of the type parameter to the array's unknown element type, after which all assignments are safe because the two unknowns are known to be subtypes.)
Unfortunately, having multiple upper bounds also causes a number of complications that one needs to plan ahead for if one wants to support this feature. For example, suppose $A
has both $B
and $C
as upper bounds. Suppose $B
has the upper bound [] -> [i32]
and $C
has the upper bound [] -> [f64]
. Then what type does call_ref
have when the input is a ref $A
? i32
or f64
? In this case, we might reason that any such function must necessarily be a [] -> ⊥
, as the return types are incompatible. But what if instead the upper bounds were [] -> [(ref $B)]
and [] -> [(ref $C)]
respectively? In that case, the return types are compatible (a function returning ref $A
would have both signatures), but there is no way to represent their combined return types. This means the type-checker would have to attempt to type-check the remainder using both possible return types.
This is one of the many subtleties that arises from designing a type system around structural subtyping. For a structural MVP, the likely viable solutions are the following (though I find neither of them to be great):
- Permanently annotate all type-directed instructions with the restricted type form(s) to upcast the relevant reference(s) to (like what
struct.get
currently does) or with the type(s) to upcast the resulting value(s) to. - Permanently disallow multiple upper bounds.
Edit: I had said lower bounds when I meant upper bounds. Post is now correct.