nauchikus/typescript-definitive-guide

Модификаторы вариантности могут изменять правила совместимости типов

bgenia opened this issue · 2 comments

В разделе "Обобщения / Модификаторы вариантности параметров типа in и out" говорится:

in указывает, что параметр типа ковариантен, а out контрвариантен. Но стоит сделать акцент на том, что с помощью этих модификаторов невозможно изменить правила по которым TypeScript производит вычисления совместимости, а можно лишь их конкретизировать.

На самом же деле, модификаторы вариантности могут сужать вариантность, но не могут её расширять или менять на противоположную. То есть, если параметр типа автоматически выводится ковариантным, мы можем сделать его инвариантным, но не можем сделать бивариантным или контравариантным. Бивариантному параметру можно установить любую желаемую вариантность.

Пример:

// Здесь параметр T является бивариантным
type Example<T> = {}

declare let vsub: Example<1>
declare let vsuper: Example<1 | 2>

// Присваивание в обе стороны возможно
vsub = vsuper
vsuper = vsub
// Здесь параметр T является контравариантным
type Example<in T> = {}

declare let vsub: Example<1>
declare let vsuper: Example<1 | 2>

vsub = vsuper
vsuper = vsub // Ошибка

Если нужно, могу привести реальные примеры в которых это позволяет избежать ошибок.

@bgenia а это разве не баг? Сейчас вечер и я жутко устал, но мне кажется, что компилятор просто не берет в расчет не задействованный параметр. a | b не может быть совместимым с a поскольку может быть b, который не a.

@nauchikus Это не баг, это бивариантное поведение. Незадействовааный параметр считается бивариантным, так как его тип не влияет на структурную совместимость результирующих типов. Далее я показал пример что с помощью аннотаций можно сузить вариантность такого параметра.

Вот другой пример действия аннотаций:

// Как известно, параметры методов являются бивариантными даже при включенной strictFunctionTypes
// Такое поведение может приводить к ошибкам
type Example<T> = { f(arg: T): void }

declare let vsub: Example<1>
declare let vsuper: Example<1 | 2>

// Присваивание в обе стороны возможно
vsub = vsuper
vsuper = vsub
// Контравариантность можно задать искусственно с помощью аннотации in
type Example<in T> = { f(arg: T): void }

declare let vsub: Example<1>
declare let vsuper: Example<1 | 2>

vsub = vsuper
vsuper = vsub // ошибка