Здравствуйте, меня зовут Дмитрий Карловский и недавно я, вместе с Артуром Мукминовым, проводил воркшоп, где показывал как разрабатывать сложные типофункции через тестирование. Это 2 часа сурового программирования на типах. Так что в качестве тизера, ловите разбор курьёзов тайпскриптовой системы типов.

Отношения — это сложно
Проверить является ли один тип подтипом другого очень просто, используя типотернарник:
type IsAExtendsB = A extends B ? true : false
На воркшопе мы разработали типофункцию Classify, принимающую 2 типа и возвращающую одно из 4 возможных значений:
[ A, '<:', B ]— A является строгим подтипом B.[ A, ':>', B ]— B является строгим подтипом A.[ A, '==', B ]— Оба типа являются подтипами друг друга (но не обязательно являются одинаковыми типами).[ A, '!=', B ]— Ни один тип не является подтипом другого.
Кроме того, мы запилили типофункции Equal и Assert, позволяющие сравнивать типы на равенство, независимо от того, считает ли компилятор два разных типа подтипами друг друга или нет. Assert при этом ещё и валит проверку типов, если типы вдруг не совпали.
Всё есть объекты! Но это не точно..
Ну и первый же прикол — Object и object — это определённо разные типы, ибо примитивные типы являются подтипами первого, но не второго:
type boolean_is_Object = Assert< boolean extends Object ? true : false, true > type boolean_is_not_object = Assert< boolean extends object ? true : false, false >
Однако, если мы сравним их, то выяснится, что они являются подтипами друг друга:
type Object_vs_object = Assert< Classify< Object, object >, [ Object, '==', object ] >
То есть отношение подтипизации в тайпскрипте не является транзитивным: если один тип (например, boolean) является подтипом другого (например, Object), а другой — третьего (например, object), то первый вовсе не обязательно является подтипом третьего — это надо проверять отдельно.
На диаграмме, все объектные типы раскрашены в голубой цвет. Они являются подтипами как Object, так и object.
Разные типы перечислений типов
Раз уж мы заговорили про булевый тип, то нельзя не упомянуть, что он — ничто иное, как кроткий алиас для объединения пары литеральных типов:
type boolean_is_true_or_false = Assert< boolean, true | false >
С числовыми перечислениями всё в принципе аналогично:
enum FL4 { Absurd, False, True, Unknown } type FL4_is_union = Assert< FL4, | FL4.Absurd | FL4.False | FL4.True | FL4.Unknown >
И состоят они вроде бы из чисел (даже не литералов):
type Absurd_is_number = Assert< Classify< FL4.Absurd, number >, [ FL4.Absurd, '==', number ] >
Но тут тайпскрипту внезапно сносит крышу:
type Absurd_is_never_wtf = Assert< Classify< FL4.Absurd, 0 >, [ never, '<:', 0 ] >
Эй, тайпскрипт, ты куда первый тип потерял? Верни, где взял!
type One_is_never_wtf = Assert< Classify< FL4.Absurd, 1 >, [ FL4.Absurd, ':>', never ] >
Вот, спасибо, совсем другое дело!
По всей видимости связано это с тем, что значения перечислений — это не простые литералы, а уникальные:
enum FL3 { Absurd, False, True } type Absurd_is_not_Absurd = Assert< Equal< FL3.Absurd, FL4.Absurd > | false, false >
Ну да ладно, у нас ещё остались не разобранными строковые перечисления. Может показаться, что ведут они себя как и числовые, однако, внезапно:
enum HappyDebugging { False = "True", True = "False", } type True_extends_string = Assert< Classify< HappyDebugging.True, string >, [ HappyDebugging.True, '<:', string ] >
Получается, что number является подтипом числового перечисления, а вот string подтипом строкового уже нет.
Призраки прошлого
В Тайпскрипте есть пара специальных типов, которые находятся на противоположных концах иерархии:
neverпредставляет из себя пустое множество значений. То есть он является подтипом любого типа, и никакой другой тип не может быть его подтипом.unknownже — это множество всех возможных значений. То есть это объединение вообще всех типов в один. Поэтому любой тип является подтипомunknown.
Но что это маячит рядом с ними? Да это же any! С одной стороны он полностью взаимозаменяем с unknown:
type unknown_is_any = Assert< unknown, any >
Но с другой же, он как кот Шрёдингера является подтипом never (и как следствие, любого другого типа до unknown) и не является таковым одновременно:
type any_maybe_extends_never = Assert< any extends never ? true : false, true | false >
Короче, any пробивает дно во всех возможных смыслах. Тяжела участь тех, кто столкнётся с ним лицом к лицу…

Счастливой отладки, ребята!
ссылка на оригинал статьи https://habr.com/ru/post/531030/
Добавить комментарий