为什么 TypeScript 有时只会将不可能的交集视为 'never'?

Why does TypeScript only sometimes treat an impossible intersection as 'never'?

TypeScript 有时会决定两种类型(如果相交)没有任何兼容的值。这个空交集称为 never,这意味着您不能提供满足两种类型的值:

type Bread = {
  shape: "loafy"
};
type Car = {
  shape: "carish"
};

// Contradiction1: Immediately resolved to 'never'
type Contradiction1 = Bread & Car;

但是,这似乎工作不一致。如果冲突的属性不在类型的顶层,TypeScript 会错过它并且不会按我预期的方式运行:

// Wrap the contradicting types
type Garage = { contents: Car };
type Breadbox = { contents: Bread };

// Contradiction2: Garage & Breadbox
// Expected: Should immediately reduce to never
type Contradiction2 = Garage & Breadbox;

这是一个错误吗?为什么 TypeScript 会这样?

这是预期的行为,因为 TypeScript 中的 属性 路径可以 任意深度 ,同时沿途更改类型。例如,这样写是完全合法的:

declare class Boxed<T> {
  contents: T;
  doubleBoxed: Boxed<this>
};
declare const b: Boxed<string>
// m: Boxed<Boxed<Boxed<Boxed<Boxed<string>>>>>
const m = b.doubleBoxed.doubleBoxed.doubleBoxed.doubleBoxed;

因此,对于任意类型,"can" 存在实际上无限数量的属性,其中任何一个都可能具有您的程序中从未见过的新类型。

这对 never 很重要,因为您可能会这样写:

// I am here to cause trouble.
type M<T, S> = T extends { nested: { nested: { nested: any } } } ?
  S :
  { el: T, nested: M<{ nested: T }, S> };

type F1 = {
  prop: M<F1, "foo">
};
type F2 = {
  prop: M<F2, "bar">
};

declare const f1: F1;
// f1.prop.nested.nested.nested: "foo"
f1.prop.nested.nested.nested;

declare const f12: F1 & F2;

// OK, infinitely
f12.prop.el.prop.el.prop.el.prop.el.prop;
// 'never' at depth 4...
f12.prop.nested.nested.nested;

确实无法预测 中的 ,您需要寻找可能导致 never 的表达式 - M 的定义没有给我们任何提示;你必须作为一个人真正理解这段代码,才能知道去哪里探索以找到嵌套的 never.

事实上,如果您可以针对任意深度的 属性 访问解决此 "correctly",您可以通过构造执行算术的类型(这已经是可能的)。显然这是不可能的,因此 TypeScript 不会尝试超越生成类型的顶级属性的容易解决的情况。