Can a type match contain more general types, later in the match statement?

125 Views Asked by At

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

0

There are 0 best solutions below