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.