Name-based pattern matching and exhaustivity

I have an example that compile well up to 2.13.3, but result with a warning " match may not be exhaustive" in 2.13.4

  class Node(id: String) {
    val isEmpty = false
    def get = id
  }

  object Node {
    def apply(id: String) = new Node(id)
    def unapply(node: Node) = node
  }

  val Alice = Node("Alice")
  val Node(alice) = Alice

I understand that a solution can bu to redefine “unapply”

    def unapply(node: Node) = Some(node.get)

But there is no more use of “name-based pattern matching”.

Do you have any proposal for another solution?

Not sure what you mean with this.

I am also actually surprised that this works:

def unapply(node: Node) = node

AFAIK, a custom extractor needs to return an Option.

Also, I do not set the purpose of that custom extractor, why returning the same node again?

Finally, public methods without explicit return type are a code smell. BTW, any reason for not using a case class?

Does this help?

val isEmpty: false = false

Looks like that doesn’t work (yet).

However you can add @unchecked.

val Node(alice) = Alice : @unchecked

For me it also goes away if I disable strict-unsealed-patmat even though this doesn’t seem related to sealed.

scalacOptions += "-Xlint:_,-strict-unsealed-patmat"

The def unapply(node: Node) = node is possible, thanks to the “isEmpty” and “get” attributes.

Thanks, but already tried without success.

After 5 years using Scala TIL that you could return something that was not an Option or a Boolean in a custom extractor; interesting but, I would certainly not use it.

Do you expect someone to inherit that Node and override those get and isEmpty methods? Because if not, and if they are always constants then I would just.

final class Node(id: String)

object Node {
  def apply(id: String): Node = new Node(id)
  def unapply(node: Node): Some[String] = Some(node.id)
}

I think one of the use cases is that you could write an extractor/matcher that doesn’t box.

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

class Even(val get: Int) extends AnyVal {
  def isEmpty = get % 2 != 0
}
object Even {
  def unapply(i: Int) = new Even(i)
}


// Exiting paste mode, now interpreting.

scala> 3 match { case Even(i) => println(s"$i is even") }
scala.MatchError: 3 (of class java.lang.Integer)
  ... 40 elided

scala> 2 match { case Even(i) => println(s"$i is even") }
2 is even

Note that this avoids 2 levels of boxing compared to Option[Int].