打字稿可能的代码路径与打字漏洞?

时间:2019-03-16 14:08:47

标签: typescript fall-through

给出代码

class X {a:string = "a"}
class Y {b:number = 1}

// does not compile
function what(v: X|Y) {
  if (v instanceof X) {
    return "its an X";
  }
  if (v instanceof Y) {
    return "its a Y";
  }
  //missing return
}

打字稿编译器说:

error TS7030: Not all code paths return a value.

是正确的,因为由于结构化键入,我可以将what称为

let v = {b:1}
what(v)

因为v在结构上与Y兼容。现在,我将what更改为

function what(v: X|Y) {
  if ("a" in v) {
    return "its an X";
  }
  if ("b" in v) {
    return "its a Y";
  }
  // missing return
}

,仍然会出现编译错误。

我想知道编译器是否无法得出if分支之一,还是存在漏洞,仍然允许我传递与两个{ {1}}分支。

2 个答案:

答案 0 :(得分:2)

这是TypeScript中的design limitation。您可以合理地期望exhaustiveness checking将使编译器意识到最后一个if语句之后的代码不可访问。但这并没有发生,而且看起来他们也没有计划很快解决这个问题。

让我们看看我们可以自己做些什么。 在以下代码中:

class X { a: string = "a" }
class Y { b: number = 1 }

// does not compile
function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  if (v instanceof Y) {
    return "its a Y";
  }
  v // never
}

实际上,当您通过那些v语句时,编译器确实会缩小return的类型……它抱怨,但这不是因为它认为v可能仍然是{{ 1}}或X(当您跌落函数底部时)。它抱怨是因为它分析代码路径与Y的缩小无关,因此它看到一个代码路径最终隐式返回v。解决此问题的唯一方法是确保所有代码路径中都有undefinedreturn

再次注意,throw首先缩小到v,然后缩小到Y。解决错误的一种方法是利用缩小到never的优势,然后在那里进行Y而不是第二次return测试:

if

现在没有错误。如果您信任function what(v: X | Y): string { if (v instanceof X) { return "its an X"; } v // Y return "its a Y"; } 支票,那么这可能是可行的方法。到达此处的另一种标准方法是使用穷举检查帮助程序功能instanceof X

assertNever()

如果调用function assertNever(x: never): never { throw new Error("OH NOES!!"); } 函数会引发错误,因此它将返回assertNever()。但是它要求其参数的类型为never,因此除非代码不可访问,否则调用它应该是编译器错误:

never

现在已知function what(v: X | Y): string { if (v instanceof X) { return "its an X"; } v // Y if (v instanceof Y) { return "its a Y"; } v // never return assertNever(v); } 返回what()而不是string | neverstring | undefined就是string | never。而且您可以看到它是如何进行详尽检查的,因为如果您不将string缩小为v,则会引发错误:

never

好的,希望有帮助。祝你好运!

答案 1 :(得分:1)

我知道使用接口和标记联合的其他版本。并且此版本可以正确编译,并且所有代码路径都返回一个值。

interface IX {kind: "a"; a: string }
interface IY {kind: "b"; b: number }

function what2(v: IX|IY): string {
    switch (v.kind) { 
        case "a":
         return "its an X";
         case "b":
         return "its an Y";
    }
}

var r1 = what2({kind: "a", a: "hello"});
var r2 = what2({kind: "b", b: 33});