TypeScript 中泛型的不安全隐式转换
Unsafe implicit conversion of generics in TypeScript
TypeScript 编译器 tsc
毫无怨言地编译了以下代码,即使带有 --strict
标志。但是,该代码包含一个基本错误,该错误在 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();
能否配置 tsc
以识别此类错误?
TypeScript 并没有很好的通用方法来处理 contravariance or invariance。粗略地说,如果你正在输出一些东西(函数输出,只读属性),你可以输出比预期类型(协方差)更窄但不更宽的东西,如果你正在输入一些东西(函数输入,只写属性) ) 你可以接受比预期类型更宽但不能更窄的东西(逆变)。如果您正在读取和写入相同的值,则不允许缩小或扩大类型(不变性)。
这个问题在 Java(不确定 C#)中没有那么多,主要是因为您不能轻松地创建类型的联合或交集,并且因为在泛型中有 extends
和 super
约束作为协变和逆变的标记。你确实在 Java 数组中看到了这一点,至少,它们被认为是协变的(尝试上面的 Object[]
和 Integer[]
你会看到有趣的事情发生)。
TypeScript 在将函数输出视为协变方面通常做得很好。在 TypeScript v2.6 之前,编译器将函数输入视为 bivariant, which is unsound (but has some useful effects; read the linked FAQ entry). Now there is a --strictFunctionTypes 编译器标志,使您可以为独立函数(而非方法)的函数输入强制执行逆变。
目前,TypeScript 将 属性 值和泛型类型视为协变的,这意味着它们适合阅读但不适合写作。这直接导致您所看到的问题。请注意,对于 属性 值也是如此,因此您可以在没有泛型的情况下重现此问题:
let numberBox: { value: number } = { value: 1 };
function insertString(items: { value: string | number }): void {
items.value = 'Test';
}
insertString(numberBox);
numberBox.value.toExponential();
除了 "be careful",我没有什么好的建议。 TypeScript 是 not intended to have a strictly sound type system (see non-goal #3); instead, the language maintainers tend to address issues only to the extent that they cause real-world bugs in programs. If this kind of thing affects you a lot, maybe head over to Microsoft/TypeScript#10717 或类似问题,如果您认为它有说服力,请给它一个或描述您的用例。
希望对您有所帮助。祝你好运!
因为 TypeScript 2.6 tsc
有一个命令行选项 --strictFunctionTypes
(自动包含在 --strict
中)。如果给定,这将强制对函数类型的参数进行逆变类型检查。出于兼容性原因,方法和构造函数被设计排除在该规则之外。所以要使用逆变,似乎需要使用函数类型:
interface IBox<T> {
getValue: () => T;
setValue: (value: T) => void;
}
class Box<T> implements IBox<T> {
private value: T;
public constructor(value: T) {
this.value = value;
}
public getValue() {
return this.value;
}
public setValue(value: T) {
this.value = value;
};
}
const numberBox: IBox<number> = new Box<number>(1);
function insertString(items: IBox<string | number>): void {
items.setValue('Test');
}
// this call does not compile anymore
insertString(numberBox);
如果在最后 3 个语句中 IBox
被替换为 Box
,逆变类型检查不适用。所以为了防止有人不小心引用这个 class 类型,Box<T>
和 IBox<T>
可以放在一个单独的 ES6 模块中,只暴露接口和工厂函数。
TypeScript 编译器 tsc
毫无怨言地编译了以下代码,即使带有 --strict
标志。但是,该代码包含一个基本错误,该错误在 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();
能否配置 tsc
以识别此类错误?
TypeScript 并没有很好的通用方法来处理 contravariance or invariance。粗略地说,如果你正在输出一些东西(函数输出,只读属性),你可以输出比预期类型(协方差)更窄但不更宽的东西,如果你正在输入一些东西(函数输入,只写属性) ) 你可以接受比预期类型更宽但不能更窄的东西(逆变)。如果您正在读取和写入相同的值,则不允许缩小或扩大类型(不变性)。
这个问题在 Java(不确定 C#)中没有那么多,主要是因为您不能轻松地创建类型的联合或交集,并且因为在泛型中有 extends
和 super
约束作为协变和逆变的标记。你确实在 Java 数组中看到了这一点,至少,它们被认为是协变的(尝试上面的 Object[]
和 Integer[]
你会看到有趣的事情发生)。
TypeScript 在将函数输出视为协变方面通常做得很好。在 TypeScript v2.6 之前,编译器将函数输入视为 bivariant, which is unsound (but has some useful effects; read the linked FAQ entry). Now there is a --strictFunctionTypes 编译器标志,使您可以为独立函数(而非方法)的函数输入强制执行逆变。
目前,TypeScript 将 属性 值和泛型类型视为协变的,这意味着它们适合阅读但不适合写作。这直接导致您所看到的问题。请注意,对于 属性 值也是如此,因此您可以在没有泛型的情况下重现此问题:
let numberBox: { value: number } = { value: 1 };
function insertString(items: { value: string | number }): void {
items.value = 'Test';
}
insertString(numberBox);
numberBox.value.toExponential();
除了 "be careful",我没有什么好的建议。 TypeScript 是 not intended to have a strictly sound type system (see non-goal #3); instead, the language maintainers tend to address issues only to the extent that they cause real-world bugs in programs. If this kind of thing affects you a lot, maybe head over to Microsoft/TypeScript#10717 或类似问题,如果您认为它有说服力,请给它一个或描述您的用例。
希望对您有所帮助。祝你好运!
因为 TypeScript 2.6 tsc
有一个命令行选项 --strictFunctionTypes
(自动包含在 --strict
中)。如果给定,这将强制对函数类型的参数进行逆变类型检查。出于兼容性原因,方法和构造函数被设计排除在该规则之外。所以要使用逆变,似乎需要使用函数类型:
interface IBox<T> {
getValue: () => T;
setValue: (value: T) => void;
}
class Box<T> implements IBox<T> {
private value: T;
public constructor(value: T) {
this.value = value;
}
public getValue() {
return this.value;
}
public setValue(value: T) {
this.value = value;
};
}
const numberBox: IBox<number> = new Box<number>(1);
function insertString(items: IBox<string | number>): void {
items.setValue('Test');
}
// this call does not compile anymore
insertString(numberBox);
如果在最后 3 个语句中 IBox
被替换为 Box
,逆变类型检查不适用。所以为了防止有人不小心引用这个 class 类型,Box<T>
和 IBox<T>
可以放在一个单独的 ES6 模块中,只暴露接口和工厂函数。