TypeScript 中 undefined 的协方差

Mar*_*bst 5 undefined typechecking typescript

下面的代码显然输入错误,但我不能在上面犯 TypeScript 错误。我打开了strictstrictNullChecks并且strictFunctionTypes很好地衡量了,但 TS 仍然坚定不移地相信这段代码很好而且很花哨。

abstract class A {
    // You can pass an undefined to any A
    public abstract foo(_x: number | undefined);
}

class B extends A {
    // B is an A, but it prohibits passing in an undefined.
    // Note that if we did `x: string`, TS would flag it as
    // an error.
    public foo(x: number) {
        if (x === undefined) {
            throw new Error("Type error!");
        }
    }
}

function makeAnA(): A {
    // This typechecks correct, so B is clearly an A, in
    // TS's opinion.
    return new B();
}

function test() {
    const b = makeAnA();
    // b is a B, so this should not be possible
    b.foo(undefined);
}
Run Code Online (Sandbox Code Playgroud)

这是预期的行为吗?有没有我可以打开的选项将其标记为错误?我不止一次被这个咬过。

Tit*_*mir 3

这是一个设计决定。所有方法参数的行为都是双变量的。这意味着就 ts 而言,for 方法(_x: number) => void是 to 的子类型(_x: number | number) => void(反之亦然)。这显然是不妥当的。

最初,不仅方法参数具有双变行为,所有函数签名参数也具有双变行为。为了解决这个问题,strictFuctionTypes在 typescript 2.6 中添加了该标志。来自公关

在这个 PR 中,我们引入了 --strictFunctionTypes 模式,在该模式中,函数类型参数位置以逆变方式而不是双变方式进行检查。更严格的检查适用于所有函数类型,除了源自方法或构造函数声明的函数类型。专门排除方法是为了确保泛型类和接口(例如 Array)继续大部分协变相关。严格检查方法的影响将是一个更大的破坏性变化,因为大量泛型类型将变得不变(即便如此,我们可能会继续探索这种更严格的模式)。

(已添加突出显示)

因此,在这里我们可以一睹让方法参数继续双变量关联的决定。是为了方便。如果没有这种不健全性,大多数类将是不变的。例如,如果Array是 不变的,Array<Dog>则不会是 的子类型Array<Animal>,从而在非常基本的代码中创建各种痛点。

虽然绝对不等价,但如果我们使用函数字段而不是方法(打开strictFunctionTypes),我们确实会收到一个错误:Type '(x: number) => void' is not assignable to type '(_x: number | undefined) => void'

abstract class A {
    // You can pass an undefined to any A
    public foo!: (_x: number | undefined) => void;
}

class B extends A {
    // Error here
    public foo: (x: number) => void = x => {
        if (x === undefined) {
            throw new Error("Type error!");
        }
    }
}

function makeAnA(): A {
    //And here 
    return new B();
}

function test() {
    const b = makeAnA();
    // b is a B, so this should not be possible
    b.foo(undefined);
}

Run Code Online (Sandbox Code Playgroud)

游乐场链接

注意:上面的代码仅在没有它的情况下给出错误,strictFunctionTypes所有函数参数继续表现为双变量。

  • @PatrickRoberts 啊,用方法覆盖字段。是的,那可行。我有点厌倦将两者混合在一起..尽管我承认我没有充分的理由 (2认同)