E108: Unapply Invalid Return Type

This error is emitted when an extractor's unapply or unapplySeq method has an invalid return type.

To be used as an extractor, an unapply method must return a type that either:

  • Has members isEmpty: Boolean and get: S (usually an Option[S])
  • Is a Boolean
  • Is a Product (like a Tuple2[T1, T2])

Example

object MyExtractor:
  def unapply(s: String): String = s

def example(s: String) = s match
  case MyExtractor(_) => "ok"
  case _ => "no match"

Error

-- [E108] Declaration Error: example.scala:5:18 --------------------------------
5 |  case MyExtractor(_) => "ok"
  |       ^^^^^^^^^^^^^^
  |   String is not a valid result type of an unapply method of an extractor.
  |-----------------------------------------------------------------------------
  | Explanation (enabled by `-explain`)
  |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  |
  | To be used as an extractor, an unapply method has to return a type that either:
  |  - has members isEmpty: Boolean and get: S (usually an Option[S])
  |  - is a Boolean
  |  - is a Product (like a Tuple2[T1, T2]) of arity i with i >= 1, and has members _1 to _i
  |
  | See: https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html#fixed-arity-extractors
  |
  | Examples:
  |
  | class A(val i: Int)
  |
  | object B {
  |   def unapply(a: A): Option[Int] = Some(a.i)
  | }
  |
  | object C {
  |   def unapply(a: A): Boolean = a.i == 2
  | }
  |
  | object D {
  |   def unapply(a: A): (Int, Int) = (a.i, a.i)
  | }
  |
  | object Test {
  |   def test(a: A) = a match {
  |     case B(1) => 1
  |     case a @ C() => 2
  |     case D(3, 3) => 3
  |   }
  | }
  |
   -----------------------------------------------------------------------------

Solution

// Return Option[T] for single value extraction
object MyExtractor:
  def unapply(s: String): Option[String] = Some(s)

def example(s: String) = s match
  case MyExtractor(x) => x
  case _ => "no match"
// Or return Boolean for test-only extraction
object IsEmpty:
  def unapply(s: String): Boolean = s.isEmpty

def example(s: String) = s match
  case IsEmpty() => "empty"
  case _ => "not empty"
// Or return a tuple for multiple values
object Split:
  def unapply(s: String): Option[(String, String)] =
    val mid = s.length / 2
    Some((s.take(mid), s.drop(mid)))

def example(s: String) = s match
  case Split(a, b) => s"$a | $b"
  case _ => "no match"