Почему TypeScript сужает тип для некоторых объявлений const, но не для других?

ОБНОВЛЕННЫЙ ВОПРОС (один основной вопрос)

Учитывая приведенный ниже фрагмент кода, почему тип сужается при объявлении константы как Version, но не для той, которая объявлена ​​как number?

type Version = 1 | 2 | 3;
const v: Version = 3;
if (v === 2) console.info('v equals 2'); // Fails compilation with "This comparison appears to be unintentional
                                        // because the types '3' and '2' have no overlap."
const n: number = 3;
if (n === 2) console.info('n equals 2'); // No compile error

ОРИГИНАЛЬНЫЙ ПОСТ

Учитывая фрагмент кода ниже:

  1. Почему первое сравнение (v > 2) проходит компиляцию, а последнее (v === 2) — нет?
  2. Почему ошибка компиляции для (v === 2) говорит ...types '3' and '2'..., а не ...types 'Version' and '2'?
type Version = 1 | 2 | 3;

const v: Version = 3;

if (v > 2) console.info('v is greater than 2');

if (v === 3) console.info('v equals 3');

if (v === 2) console.info('v equals 2'); // Fails compilation with "This comparison appears to be unintentional 
                                        // because the types '3' and '2' have no overlap."

1
57
1

Ответ:

Решено

Когда TypeScript видит const v: Version = 3;, он игнорирует Version и определяет тип как тип числового литерала 3.

Если v есть 3:

  • (3 > 2) Никаких ошибок
  • (3 === 3) Никаких ошибок
  • (3 === 2) Ошибка «3» и «2» не пересекаются.

В следующем примере v — это параметр функции, который выводится как Version, а не 3. Все работает так, как и следовало ожидать, без ошибок.

type Version = 1 | 2 | 3;

function test(v: Version) {
  if (v > 2) console.info('v is greater than 2'); // No errors
  if (v === 3) console.info('v equals 3'); // No errors
  if (v === 2) console.info('v equals 2'); // No errors
}

test(3);
// Logs:
// "v is greater than 2" 
// "v equals 3" 

Обновление: О сужении

При использовании const v: Version = 3 TS сужается от Version до 3. Тогда анализ потока управления в каждом операторе if в некоторых случаях сужается до never.

const v: Version = 3;

if (v > 2) {
    type t = typeof v; // 3
}

if (v === 3) {
    type t = typeof v; // 3
}

if (v === 2) {
    type t = typeof v; // never
}

if (v !== 3) {
    type t = typeof v; // never
}

if (v !== 2) {
    type t = typeof v; // 3
}

При использовании const v: number = 3; TS НЕ сужается от number до 3. Тогда анализ потока управления не сможет сузиться number до более конкретного типа.

const v: number = 3;

if (v > 2) {
    type t = typeof v; // number
}

if (v === 3) {
    type t = typeof v; // number
}

if (v === 2) {
    type t = typeof v; // number
}

if (v !== 3) {
    type t = typeof v; // number
}

if (v !== 2) {
    type t = typeof v; // number
}