Pre-SIP Inline Refinement Captures in Match Type Patterns

Summary

This Pre-SIP proposes allowing match type case patterns to capture abstract type members of refined types, without a separate type alias.

Currently:

type BoxOf[c] = Box { type Content = c }

type Unbox[B <: Box] = B match
  case BoxOf[c] => c

After:

type Unbox[B <: Box] = B match
  case Box { type Content = c } => c

Motivation

Match types already support capturing type arguments via var-patterns — bare lowercase identifiers in type-argument position:

type Elem[X] = X match
  case List[a] => a // `a` is a pattern-bound type variable

However, capturing abstract type members requires an intermediate type alias, which is a pure syntactic tax: BoxOf[c] carries no information beyond what Box { type Content = c }
already expresses. The workaround has several drawbacks:

  • It pollutes the namespace of the enclosing type or object.
  • In a library, it leaks an implementation detail into the public API.
  • It requires the reader to jump to a separate definition to understand the pattern.

Proposed Solution

Allow a bare lowercase identifier on the right-hand side of a type-member refinement inside a match type case pattern to act as a pattern-bound type variable — the same scoping rules as var-patterns in type-argument position.

User-visible syntax

case ParentType { type MemberName = varIdent } => Body

varIdent must be:

  • a bare lowercase identifier (same rule as var-patterns everywhere in match types), and
  • not already in scope as a named type.

It is then bound as a fresh pattern-bound type variable for the duration of Body, with bounds inherited from the corresponding member of ParentType.

Examples

Basic extraction:

trait Box:
  type Content

type Unbox[B <: Box] = B match
  case Box { type Content = c } => c

class StringBox extends Box:
  type Content = String

val _: Unbox[StringBox] = "hello" // OK

Multiple captures in one pattern:

trait KV:
  type Key
  type Value

type PairOf[K <: KV] = K match
  case KV { type Key = k; type Value = v } => (k, v)

Mixed captured and fixed refinements:

type MT[x <: SomeTrait] = x match
  case SomeTrait { type Concrete = Int; type Abstract = a } => a

I will submit a PR if this proposal receives the green light. The implementation seems fairly straightforward: likely around 15 lines in Typer.scala . However, since I am far from being a compiler expert, I might be underestimating the effort. I will link a draft PR with the implementation soon.

4 Likes

Draft implementation Pre-SIP Desugar inline refinement type captures in match type patterns. by halotukozak · Pull Request #26381 · scala/scala3 · GitHub

If it can be made to work, IMO this is worth it.

4 Likes