匹配可能不详尽警告不正确

Match may not be exhaustive warning is incorrect

所以 scala 编译器抱怨说模式匹配对于方法 foo 可能并不详尽,我想知道为什么。这是代码:

abstract class Foo {
    def foo(that: Foo): Unit = (this, that) match {
        case (Foo_1(), Foo_1()) => //case 1
        case (Foo_1(), Foo_2()) => //case 2
        case (Foo_2(), Foo_1()) => //case 3
        case (Foo_2(), Foo_2()) => //case 4
            // Compiler warning
    }

    def fooThis(): Unit = this match {
        case Foo_1() => //do something
        case Foo_2() => //do something
            // Works fine
    }

    def fooThat(that: Foo): Unit = that match {
        case Foo_1() => //do something
        case Foo_2() => //do something
            // Works fine
    }
}
case class Foo_1() extends Foo
case class Foo_2() extends Foo

这是错误:

Warning:(5, 32) match may not be exhaustive.
It would fail on the following inputs: (Foo(), _), (Foo_1(), _), (Foo_2(), _), (_, Foo()), (_, Foo_1()), (_, Foo_2()), (_, _)
    def foo(that: Foo): Unit = (this, that) match {

由于thisthatFoo类型,而Foo只能是Foo_1Foo_2类型,所以foo 中的情况都是可能的组合。

为了完整起见,我添加了 fooThisfooThat,并表明匹配 Foo_1Foo_2 就足够了。编译器消息表明还有其他类型可以匹配(即 Foo_)。

为什么会显示这个警告?

相关:


编辑

一旦您使用元组,编译器似乎就会报错。如果我们向 fooThis 添加一个虚拟变量,如下所示

def fooThis(): Unit = (this, Foo_1()) match {
    case (Foo_1(),_) => //do something
    case (Foo_2(),_) => //do something
}

我们收到以下编译器警告

Warning:(13, 27) match may not be exhaustive.
It would fail on the following input: (_, _)
    def fooThis(): Unit = (this, Foo_1()) match {

似乎使抽象 class 密封至少使编译器警告消失:

sealed abstract class Foo {

虽然我不太清楚为什么。它可能与以下内容有关:https://issues.scala-lang.org/browse/SI-9351

Scala 编译器不会针对非密封特征(例如您的 Foo)给出详尽的匹配警告。这解释了为什么 fooThisfooThat 编译时没有警告。

如果你想在这里收到警告(你应该这样做,因为它们在运行时比 MatchError 异常要好)你有几个选择:

  1. Foo。这会创建一个 ADT,它可以安全地进行模式匹配,因为当您忘记一个案例时,您会收到详尽警告。 Option 是您可能从标准库中熟悉的 ADT。在这里,您已经获得 Foo_1Foo_2 的个案,因此您不会收到穷举警告。但是如果你忘记了这两种情况,你会的。您可能希望在使用时将 Foo_1Foo_2 设为最终版本。
  2. 保持 Foo 未密封,使用 Typelevel Scala 并启用其 -Xlint:strict-unsealed-patmat 警告。

另一方面,Scala 编译器 为最终情况 类 提供详尽的匹配警告,例如 Tuple2,这就是您要匹配的内容反对你的 foo 方法。

要回答 "why is the warning shown?",请考虑如果我们这样做会发生什么:

case class Foo3() extends Foo
val foo3 = Foo3()
foo3.foo(foo3)

(答案:它在运行时抛出一个 MatchError。)

警告是 Scala 编译器帮助您在运行时避免异常的方式。如果你想让警告消失,你可以:

  1. 密封 Foo(再次创建一个 ADT),防止 Foo3 偷偷溜进别处。
  2. 添加通配符case _ => ...
  3. 取消选中匹配项:((this, that): @unchecked) match { ...

不要做第 3 点,因为当有人引入 Foo3.

时,它会让你在运行时容易受到 MatchErrors 的攻击

所以,也许问题不是真正的 "why does the match in foo generate a warning",而是 "why doesn't the match in fooThis and fooThat generate a warning"。

找出一个class的所有子class称为Class层次分析,用动态代码加载的语言做静态CHA相当于解决停机问题.

此外,Scala 的目标之一是单独编译和部署独立模块,因此编译器根本无法知道 class 是否在另一个模块中被子classed,因为它从不查看多个模块。 (毕竟,您可以针对某个其他模块的接口编译一个模块,而该模块甚至不存在于您的系统中!)这就是为什么 sealed 需要在同一个编译中定义所有子classes单位。 这就是编译器不显示警告的原因,因为它知道现有的 subclasses.