TypeScript 中泛型的不安全隐式转换

omi*_*nug 4 generics types type-conversion typescript tsc

tsc即使带有--strict标志,TypeScript 编译器也毫无怨言地编译了以下代码。但是,该代码包含一个基本错误,该错误在 Java 或 C# 等语言中是被阻止的。

interface IBox<T> {
  value: T;
}

const numberBox: IBox<number> = { value: 1 };

function insertString(items: IBox<string | number>): void {
  items.value = 'Test';
}

// this call is problematic
insertString(numberBox);

// throws at runtime:
// "TypeError: numberBox.value.toExponential is not a function"
numberBox.value.toExponential();
Run Code Online (Sandbox Code Playgroud)

可以tsc配置以便识别这样的错误吗?

jca*_*alz 6

TypeScript 并没有一个很好的通用方法来处理逆变或不变性。粗略地说,如果您要输出某些内容(函数输出、只读属性),您可以输出比预期类型(协方差)更窄但不宽的内容,如果您正在输入某些内容(函数输入、只写属性) ) 你可以接受比预期类型更宽但不窄的东西(逆变)。如果您正在读取和写入相同的值,则不允许您缩小或扩大类型(不变性)。

这个问题在 Java 中没有出现(对 C# 不确定)主要是因为您不能轻松创建类型的联合或交集,并且因为在泛型中存在extendssuper约束充当协变和逆变的标记。您确实在 Java 数组中看到了这一点,至少,它们被不合理地认为是协变的(尝试上面的Object[]Integer[],你会看到有趣的事情发生)。


TypeScript 通常在将函数输出视为协变方面做得很好。在 TypeScript v2.6 之前,编译器将函数输入视为bivariant,这是不合理的(但有一些有用的效果;请阅读链接的 FAQ 条目)。现在有一个--strictFunctionTypes编译器标志,可让您对独立函数(而非方法)的函数输入强制执行逆变。

目前,TypeScript 将属性值和泛型类型视为协变,这意味着它们适合阅读但不适合写作。这直接导致了您所看到的问题。请注意,对于属性值也是如此,因此您可以在没有泛型的情况下重现此问题:

let numberBox: { value: number } = { value: 1 };
function insertString(items: { value: string | number }): void {
  items.value = 'Test';
}
insertString(numberBox);
numberBox.value.toExponential();
Run Code Online (Sandbox Code Playgroud)

除了“小心”之外,我没有什么好的建议。TypeScript并不打算拥有严格健全的类型系统(参见非目标 #3);相反,语言维护人员倾向于仅在它们导致程序中的实际错误的程度上解决问题。如果这种事情对您影响很大,请转至Microsoft/TypeScript#10717或类似问题,如果您认为它具有说服力,请给出或描述您的用例。

希望这有帮助。祝你好运!