I'm learning Scala 3, and I'm trying to better learn about match types and literal types.
The short version
I'm encountering a compile-time typing error when I'm using a type match that contains a case type that is more general than an earlier case type. Basically, I've got a match type that looks like this:
type MyMatchType[Index, +T0, +T1] = Index match {
case 0 => T0
case 1 => T1
case Int => T0 | T1
}
When matching on a type literal of 0, the result correctly types as T0. When matching on a type literal of 1, the result correctly types as T1. However, when matching on a type of Int, I would like the inferred type to be (T0 | T1), but it is actually MyMatchType[Index, T0, T1].
The longer version
For an example, I'd like to create my own Tuple2. I'd like MyTuple2 to behave as you'd expect:
val x = MyTuple2[Int, String](99, "Luftballoons")
x(0) // 99: Int
x(1) // "Luftballoons": String
I'd like to support some kind of dynamic access with Ints that are not known at compile-time:
val zero: Int = 0
val two: Int = 2
x(zero) // 99
x(two) // java.lang.IllegalArgumentException
I've gotten a lot of this working with a match type and a case class for the tuple:
type MyTuple2Apply[Index, +T0, +T1] = Index match {
case 0 => T0
case 1 => T1
case Int => T0 | T1
}
case class MyTuple2[+T0, +T1](private val v0: T0, private val v1: T1):
def apply[Index](i: Index): MyTuple2Apply[i.type, T0, T1] =
i match
case _: 0 => v0
case _: 1 => v1
case _: Int =>
// This logic isn't 100% right but don't worry about it for this example.
throw new IllegalArgumentException
Here, everything works correctly except for the types. In particular, I would like this to work:
// This should type as (Int | String), because all the compiler knew was that I
// passed an Int to apply. MyTuple2Apply here should map Int => (Int | String).
x(zero): (Int | String)
However, I get this error message:
1 |x(zero): (Int | String)
|^^^^^^^
|Found: MyTuple2Apply[(zero : Int), Int, String]
|Required: Int | String
|
|Note: a match type could not be fully reduced:
|
| trying to reduce MyTuple2Apply[(zero : Int), Int, String]
| failed since selector (zero : Int)
| does not match case (0 : Int) => Int
| and cannot be shown to be disjoint from it either.
| Therefore, reduction cannot advance to the remaining cases
|
| case (1 : Int) => String
| case Int => Int | String
I'm not sure how to get this to typecheck. Would love some advice!
Here is a Scastie link that replicates the above snippets: Scastie