TypeScript为何判定这两种类型无重叠?
一、getUnary中类型无重叠报错的原因
在泛型函数getUnary<T extends Unary>里,参数c的类型是UnaryConstructorWithDefault<T>——这是一个针对任意Unary子类型T的通用构造器类型。而typeof AddN是一个具体的构造器类型,它只能生成AddN实例(属于Unary的子类型)。
TypeScript在泛型上下文里会严格校验类型关系:由于T可以是任何符合Unary约束的类型(比如你可以定义另一个MultiplyN类传入getUnary),UnaryConstructorWithDefault<T>并不必然包含typeof AddN的类型信息。TypeScript认为这种跨任意泛型类型和具体类型的比较很可能是代码失误,因此抛出“类型无重叠”的错误。
二、赋值给宽泛类型消除错误的合理性
在getUnaryWithHack里,我们把AddN赋值给类型为UnaryConstructorWithDefault(即默认的UnaryConstructorWithDefault<Unary>)的变量hack。此时:
- 参数
c的类型UnaryConstructorWithDefault<T>是UnaryConstructorWithDefault<Unary>的子类型(因为T extends Unary,子类型的构造器集合自然包含在父类型中) - 子类型和父类型存在明确的类型重叠关系,TypeScript认为这个比较是合理的,因此不再报错。
这种行为是符合TypeScript类型系统设计预期的——通过将具体类型向上转型到更宽泛的父类型,消除了泛型带来的“任意性”,让类型关系变得明确。但代价是hack丢失了AddN构造器的具体参数类型信息,所以new hack(5)会出现类型提示缺失,但运行逻辑是正常的。
三、移除泛型后错误消失的核心原因
移除泛型后,c的类型固定为UnaryConstructorWithDefault<Unary>,而typeof AddN是这个类型的具体实现(子类型),两者的重叠关系是明确的。TypeScript能直接识别出c有可能等于AddN,因此不会触发无重叠的错误提示。
内容的提问来源于stack exchange,提问作者Zachiah

