Почему TypeScript только иногда рассматривает невозможное пересечение как «никогда»?

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 ведет себя так?


person Ryan Cavanaugh    schedule 19.05.2020    source источник


Ответы (1)


Это предполагаемое поведение, поскольку пути к свойствам в 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;

Таким образом, уже для произвольного типа существует практически бесконечное количество свойств, которые «могут» существовать, и любое из них может иметь какой-то новый тип, никогда ранее не встречавшийся в вашей программе.

Это важно для 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.

На самом деле, если бы вы могли решить это «правильно» для любой произвольной глубины доступа к свойствам, вы могли бы делать такие вещи, как доказать/опровергнуть гипотезу Коллатца путем структурирования типов, выполняющих арифметические действия (что уже возможно). Очевидно, что это невозможно, поэтому TypeScript не пытается выйти за пределы легко разрешаемого случая свойств верхнего уровня созданного типа.

person Ryan Cavanaugh    schedule 19.05.2020
comment
При этом имея тривиальное доказательство последней теоремы Ферма :) .com/2011/02/24/никогда-не говори-никогда-часть-вторая - person SWeko; 20.05.2020