Заставьте switch по union не компилироваться при добавлении варианта
По union Shape идёт switch с дискриминантом kind. Сейчас коллега может добавить в union новый вариант, и все существующие switch продолжат компилироваться, молча проваливаясь в неверную ветку по умолчанию.
Ограничения: без сторонних библиотек и без рефлексии. Сборка обязана падать на каждом неполном switch в момент добавления варианта, а ошибка должна указывать на switch, а не на union.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.side ** 2;
// ваш код здесь
}
}
Допишите реализацию.
В ветке default присвойте предмет switch переменной типа never. Когда все case разобраны, предмет сужен до never, и присваивание проходит проверку. Добавьте вариант — остаток перестанет быть never, и присваивание не скомпилируется.
- ✗Обходиться одним
default: throw, который спокойно компилируется при новом варианте - ✗Типизировать дискриминант как
string, из-за чего предмет не сужается доnever - ✗Ждать срабатывания проверки во время выполнения, а не при компиляции
- →Почему только
neverзаставляет это присваивание работать? - →Как переиспользовать эту проверку во многих switch, не копируя её?
Решение
never — пустой тип: у него нет ни одного значения, поэтому в never присваивается только то, чего не бывает. Ровно это и делает его детектором полноты.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.side ** 2;
default: {
const exhaustive: never = shape; // ✅ здесь shape сужен до never
throw new Error(`Unhandled shape: ${JSON.stringify(exhaustive)}`);
}
}
}
Добавьте вариант — и сборка падает на самом switch:
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number }; // новый вариант
// error TS2322: Type '{ kind: "triangle"; ... }' is not assignable to type 'never'.
⚠️ Один default: throw такой ошибки не даст: он компилируется при любом union и обнаружит пробел лишь во время выполнения, у пользователя. Дискриминант обязан быть литеральным типом — kind: string не сузится до never никогда.