Exhaustiveness checking

Тренажер по TypeScript для пользователей с начальным уровнем подготовки.

Тренажер по TypeScript

При работе с объединениями типов (Discriminated Unions) важно убедиться, что мы обработали все возможные варианты. Это называется исчерпывающей проверкой (Exhaustiveness checking).

Для этого в TypeScript используется тип never. В конструкции switch мы можем добавить ветку default, в которой присвоим непроверенное значение переменной типа never. Если мы забыли обработать какой-то вариант (case), TypeScript увидит, что в default попадает что-то отличное от never, и подсветит ошибку.

Эта техника позволяет защитить код от багов при расширении системы типов: добавили новый статус заказа — компилятор сразу напомнит, где его нужно обработать.

Список тем

1. Присвоение never

id: 40756_ts_exh_replace_never_assign

В этом задании вам нужно дополнить фрагмент кода на TypeScript, реализующий исчерпывающую проверку (exhaustiveness checking) для размеченного объединения (discriminated union). В блоке `default` конструкции `switch` необходимо присвоить необработанное значение переменной типа `never`, чтобы TypeScript мог проверить, что все возможные варианты объединения обработаны. Заполните пропуск именем аргумента функции, чтобы код стал синтаксически корректным и типобезопасным.

Заполните пропуски
type Circle = { kind: 'circle'; radius: number };
type Square = { kind: 'square'; side: number };
type Shape = Circle | Square;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.side ** 2;
    default:
      const exhaustiveCheck: never = input1S;
      return exhaustiveCheck;
  }
}
Сообщения
Проверить
Показать решение на 3 сек.
Показать подсказку

2. Логика проверки

id: 40756_ts_exh_analyze_switch_flow

Проанализируйте TypeScript-код сверху и восстановите последовательность логических шагов выполнения программы. Шаги снизу перемешаны — расставьте их в правильном порядке, чтобы отразить, как программа обрабатывает входную фигуру и выполняет проверку на полноту обработки всех вариантов.

type Circle = { kind: 'circle'; radius: number };
type Square = { kind: 'square'; side: number };
type Shape = Circle | Square;

function processShape(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius * shape.radius;
    case 'square':
      return shape.side * shape.side;
    default:
      const _exhaustiveCheck: never = shape;
      throw new Error(`Unknown shape: ${_exhaustiveCheck}`);
  }
}

// Входные данные
const myShape: Shape = { kind: 'circle', radius: 5 };
const area = processShape(myShape);
console.log(area);
Расположите элементы в логичном порядке
Определение discriminated union (типы Circle, Square) и функции processShape с switch
Вызов processShape, проверка shape.kind, попадание в case 'circle'
Default ветка не выполняется, так как shape.kind равен 'circle' (exhaustive checking)
Вычисление площади круга (Math.PI * 5 * 5)
Возврат вычисленного значения из функции
Создание фигуры Circle с радиусом 5 (входные данные)
Сообщения
Проверить
Показать подсказку

3. Необработанный вариант

id: 40756_ts_exh_error_missing_case

В этом фрагменте TypeScript-кода используется discriminated union для геометрических фигур. В union был добавлен новый тип 'Triangle', но в switch-операторе отсутствует соответствующий case для его обработки. Из-за этого возникает ошибка TypeScript в строке с exhaustive check. Найдите и исправьте строку, чтобы добавить обработку треугольника и обеспечить полноту проверки всех вариантов union.

Найдите ошибку и исправьте
type Circle = { kind: 'Circle'; radius: number };
type Square = { kind: 'Square'; sideLength: number };
type Triangle = { kind: 'Triangle'; base: number; height: number };
type Shape = Circle | Square | Triangle;
 
function getArea(shape: Shape): string {
  switch (shape.kind) {
    case 'Circle':
      return `Circle area: ${Math.PI * shape.radius ** 2}`;
    case 'Square':
      return `Square area: ${shape.sideLength ** 2}`;
    // case 'Triangle': return `Triangle area: ${0.5 * shape.base * shape.height}`;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}
Сообщения
Проверить
Показать решение на 3 сек.
Показать подсказку

4. Поиск дискриминанта

id: 40756_ts_exh_highlight_discriminant

В этом задании вам нужно разметить код TypeScript, используя discriminated unions. Найдите и отметьте дискриминантное свойство (общее свойство для различения типов) и его значения (конкретные значения, указывающие на тип). Доступные типы: 'Дискриминантное свойство' и 'Значение дискриминанта'.

Кликните по каждому выделенному фрагменту и выберите для него подходящий тип из списка под текстом.
interface Circle {
  {{kind~|~t1}}: '{{circle~|~t2}}';
  radius: number;
}

interface Square {
  {{kind~|~t3}}: '{{square~|~t4}}';
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
  }
}
Дискриминантное свойство
Значение дискриминанта
Сообщения
Проверить
Показать подсказку

5. Тип ловушки

id: 40756_ts_exh_select_never_type

В этом задании вам нужно выбрать правильный тип TypeScript для переменной, используемой в проверке исчерпываемости (exhaustiveness checking) дискриминированных объединений (discriminated unions). Код содержит пропуск, куда следует подставить тип из выпадающего списка, чтобы TypeScript мог статически проверить, что все возможные случаи обработаны. Вы увидите фрагмент кода с оператором switch, и в ветке default необходимо указать тип, который гарантирует исчерпывающую проверку.

Нужно правильно расставить в пропуски предложенные варианты
type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    default:
      const exhaustiveCheck: input1S = shape;
      throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
  }
}
Сообщения
Проверить
Показать решение на 3 сек.
Показать подсказку

6. Блок защиты

id: 40756_ts_exh_build_default_block

Из предложенных строк соберите default ветку оператора switch, которая реализует проверку на полноту обработки (exhaustiveness checking) для discriminated union в TypeScript. Ветка default должна присваивать значение переменной типа never и возвращать её, чтобы компилятор гарантировал обработку всех вариантов. Одна из строк лишняя и не должна входить в решение.

Перетяните в правильном порядке строки из одного блока в другой
default:
    const _exhaustiveCheck: never = val;
    return _exhaustiveCheck;
}
    break;
Сообщения
Проверить
Показать решение на 3 сек.
Показать подсказку

7. Результат при расширении

id: 40756_ts_exh_predict_behavior

Проанализируйте приведённый код на TypeScript. Тип Shape был расширен новым вариантом Triangle, но функция getArea не была обновлена. Что произойдёт при попытке компиляции этого кода? Обратите внимание, что функция getArea объявлена как возвращающая число, и в ней используется switch по свойству kind.

Выберите правильный вариант ответа
type Shape = Circle | Square | Triangle;

interface Circle {
  kind: 'circle';
  radius: number;
}

interface Square {
  kind: 'square';
  sideLength: number;
}

interface Triangle {
  kind: 'triangle';
  base: number;
  height: number;
}

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
  }
}
Сообщения
Проверить
Показать подсказку

TypeScript: компиляция и запуск

id: 40756_compiler
TS
Запустить тренажёр (TypeScript)
НайтиКурс.Ру