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

Typescript possible code paths vs. typing loophole?

给定代码

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 分支之一将被采用,或者是否存在漏洞仍然允许我传入与任何分支都不匹配的兼容对象两个 if 分支。

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

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});

这是一个design limitation in TypeScript. You, reasonably, expect that 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
}

编译器确实在你传递那些 return 语句时缩小了 v 的类型......它抱怨,但不是因为它认为 v 可能仍然是一个X 或者 Y 当你从函数的底部掉下来的时候。它抱怨是因为它分析代码路径与 v 的缩小隔离开来,所以它看到代码路径在末尾隐含 returns undefined。解决此问题的唯一方法是确保所有代码路径中都有 returnthrow

再次注意,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() 函数会抛出错误,因此它 returns 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() 为 return string | never 而不是 string | undefined 所知。 string | never 就是 string。你可以看到它是如何作为详尽检查的,因为如果你不将 v 缩小到 never:

它会抛出一个错误
function oops(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  return assertNever(v); // error, v is Y, not never
}

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