Missing type annotations to patterns in Scala 3.3

Summary

Type annotations in pattern matching have been restricted in Scala 3.3 as part of PR #16150. This causes source-code incompatibility vs 3.2 for statements like this:

case h *: _: *:[?, ?] => h

As I understand, this is seen as an easy-to-fix, syntax-only change.
I believe, that when match types are involved, this restriction effectively precludes certain solutions, which were possible in Scala 3.2.
I would like to restore type annotation to the form available in Scala 3.2.
Or, if at all possible, enhance match types to work with patterns without type annotations.

Context

Please see this code snippet:

type Key = String with Singleton

type Select[K <: Key, KS <: Tuple, VS <: Tuple] = KS match
  case K *: t => SelectH[VS]
  case h *: t => SelectT[K, t, VS]

type SelectT[K <: Key, KS <: Tuple, VS <: Tuple] = VS match
  case ? *: tv => Select[K, KS, tv]

type SelectH[VS <: Tuple] = VS match
  case h *: ? => h

def get[K <: Key, KS <: Tuple, VS <: Tuple](key: K, keys: KS, values: VS): Select[K, KS, VS] =
  (keys: @unchecked) match
    case `key` *: _: *:[K @unchecked, ?] => getH(values)   // not achievable in Scala 3.3
    case _ *: tk: *:[?, ?] => getT(key, tk, values)

def getT[K <: Key, KS <: Tuple, VS <: Tuple](key: K, keys: KS, values: VS): SelectT[K, KS, VS] =
  (values: @unchecked) match
    case _ *: t: *:[?, ?] => get(key, keys, t)

def getH[VS <: Tuple](values: VS): SelectH[VS] =
  (values: @unchecked) match
    case h *: _: *:[?, ?] => h

val keys: ("x", "y") = ("x", "y")
val values = (1, "a")
val v: String = get("y", keys, values)
println(v)

This prints a while executed with Scala 3.2 and doesn’t compile with 3.3.
Most of the code may be adjusted to 3.3, except for

case `key` *: _: *:[K @unchecked, ?] => getH(values)

Before I found out that this was a planned change, I opened issue #17289, with a simplified code example. It was really quickly addressed, with an explanation and proposed solution. Unfortunately, the proposed solution doesn’t cover all cases.

Thank you in advance for taking this request into account.

1 Like

You might as well do this, which is just as type safe (aka it wasn’t before)

def get[K <: Key, KS <: Tuple, VS <: Tuple](key: K, keys: KS, values: VS): Select[K, KS, VS] =
  ((keys: @unchecked) match
    case `key` *: _ => getH(values)
    case _ *: tk => getT(key, tk, values)
  ).asInstanceOf[Select[K, KS, VS]]

def getT[K <: Key, KS <: Tuple, VS <: Tuple](key: K, keys: KS, values: VS): SelectT[K, KS, VS] =
  (values: @unchecked) match
    case _ *: t => get(key, keys, t).asInstanceOf[SelectT[K, KS, VS]]

def getH[VS <: Tuple](values: VS): SelectH[VS] =
  (values: @unchecked) match
    case h *: _ => h.asInstanceOf[SelectH[VS]]

Thanks for the proposed solution. I didn’t think about using asInstanceOf.

Still, type casting is somehow less elegant and the compiler doesn’t guide you inside the casted part. You may write:

def get[K <: Key, KS <: Tuple, VS <: Tuple](key: K, keys: KS, values: VS): Select[K, KS, VS] =
  ((keys: @unchecked) match
    case `key` *: _ => getH(values)
  ).asInstanceOf[Select[K, KS, VS]]

It type checks but causes runtime exception, which wasn’t possible with the original design.
Is the restriction of type annotations the only solution to avoid errors linked to PR #16150? Is this a permanent change in Scala syntax?

It seems that this case is not a real issue for others.
As a final thought:

  • Restricted type annotations make the syntax less regular and thus harder to learn and understand.
  • In certain situations, when match types are involved, they force type casting, which is unpleasant / less safe.

I understand this is a necessary compromise to make the language more stable. Good for us. Thank you.

Your example will now produce a warning, instead of a cryptic syntax error: Just warn on type ascription on a pattern by prolativ · Pull Request #17454 · lampepfl/dotty · GitHub

1 Like

Wow, that’s great! Thanks a lot.