Question

In the example below, in the second case I would expect the same compile error as in the first case, but it compiles. Why?

object CaseMatching extends App {

  case class Id(value: Long)
  object Id { val zero = Id(0) }
  case class Name(value: String)
  case class IdName(id: Id, name: Name)

  IdName(Id(0), Name("A")) match {
    case IdName(_, Id(0)  ) => // does not compile (as expected)
    case IdName(_, Id.zero) => // does compile (but should not ?)
    case IdName(Id.zero, _) => println("OK") // this is OK and will match
    case _ =>
  }

}

Why is it relevant? - It took me the larger part of an hour to find out why the following case was never met: case TreeEntry(_, Some(child), _, _, NodeType.DIR, _, _) That was, because the NodeType is in the 4th field and not in the 5th field. I would have appreciated if the compiler had told me!

Was it helpful?

Solution

Shortest answer: making Name final suffices to persuade the compiler that zero is not one. See this issue and environs.

It would warn on a type test, which is an isInstanceOf:

<console>:15: warning: fruitless type test: a value of type CaseMatching.Name cannot also be a CaseMatching.Id
           case IdName(_, _: Id) =>
                             ^

but not when testing equality, since equality is universal.

Here's another good one, case IdName(_, Id) =>

<console>:15: error: pattern type is incompatible with expected type;
 found   : CaseMatching.Id.type
 required: CaseMatching.Name
Note: if you intended to match against the class, try `case _: Id`
           case IdName(_, Id) =>
                          ^

What you want is:

scala> IdName(Id(0), Name("A")) match { case IdName(_, id: Id.zero.type) => }
<console>:21: warning: fruitless type test: a value of type Name cannot also be a Id (the underlying of Id.zero.type)
              IdName(Id(0), Name("A")) match { case IdName(_, id: Id.zero.type) => }
                                                                         ^

The singleton type contains only that value, so it uses eq for the test; and as a type test, it also warns. (It uses eq instead of equals as of this week.)

Not sure how far this goes for you, but:

scala> :pa
// Entering paste mode (ctrl-D to finish)

sealed trait Id { def value: Long }
case class Nonzero(value: Long) extends Id
case object Zero extends Id { val value = 0L }
case class Name(value: String)
case class IdName(id: Id, name: Name)

// Exiting paste mode, now interpreting.

scala> IdName(Zero, Name("A")) match { case IdName(_, Zero) => 1 }
<console>:14: error: pattern type is incompatible with expected type;
 found   : Zero.type
 required: Name
              IdName(Zero, Name("A")) match { case IdName(_, Zero) => 1 }
                                                             ^
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top