Перейти к содержимому

Assignability & Variance

Зачем знать: ровно тут TS становится unsound по дизайну (массивы и методы — bivariant). Просят: «Почему Cat[] присваивается в Animal[], и почему это опасно?» — классический вопрос про covariant arrays. Красный флаг — путать assignability с identity.

Правила структурной совместимости для функций / массивов / readonly:

  Параметры функции — CONTRAVARIANT  (входы расширяются)
Возврат функции   — COVARIANT      (выходы сужаются)
Массивы / методы — BIVARIANT       (TS unsound by design)
readonly tuple   — COVARIANT, mutable tuple — INVARIANT
class Animal { name = ''; }
class Cat extends Animal { meow() {} }
// Covariant arrays — assignable, но unsound:
const cats: Cat[] = [new Cat()];
const animals: Animal[] = cats; // ок
animals.push(new Animal()); // компилируется, ломает cats[i].meow() в runtime!
// strictFunctionTypes делает параметры функций контравариантными
type Handler<T> = (x: T) => void;
let h1: Handler<Animal>;
let h2: Handler<Cat> = (c) => c.meow();
// h1 = h2; // ошибка под strictFunctionTypes — h1 могут вызвать с Animal

Excess property check vs assignability:

interface Btn { label: string }
function render(b: Btn) {}
// Присваиваемость через переменную работает (структура совпадает)
const opt = { label: 'Ok', size: 'lg' };
render(opt); // ок
// Литерал напрямую — excess property check ругается
// render({ label: 'Ok', size: 'lg' });

readonly-кортеж присваивается в covariant позицию шире, чем mutable; обратно — нельзя.

type RT = readonly [number, number];
type MT = [number, number];
const m: MT = [1, 2];
const r: RT = m; // ок: mutable -> readonly
// const m2: MT = r; // ошибка: нельзя «забыть» readonly

Итог: Параметры — контравариантны (под strictFunctionTypes), методы и массивы — бивариантны (unsound), readonly расширяет совместимость.