Странное поведение при проверке `extends ReadonlyArray<any>`

См. код:

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAglC8UBKECGATA9gOwDYhgCdDUQAeAchgoD4AoUSKAIQWTSzwONMudobhoMAMYiIYYDDIAVKBAAewCNnQBnWDTYzBTGRDXAAjG1HjJ0mFoD011GInL0u6P SMAMUw4tlmN6xDEMIQuUG7AAMxe5lK+8koq6uwYOPhEJOSo2CBaAPwsUABcLP5Q9jEQ6EA

type A = ReadonlyArray<'A'>
type B = ReadonlyArray<'B'>
type AcceptA<T extends A> = T
type Test1 = AcceptA<A> //accepted
type Test2 = AcceptA<B> //error
type Test3 = AcceptA<B extends ReadonlyArray<any> ? B : B> // accepted

Его можно было принять в Test3, но в B он все тот же Test2, в котором есть ошибка.

Должна ли быть ошибка в Test3? Почему нет?

Это имеет мало общего с ReadonlyArray<T>. Вы можете продемонстрировать то же самое практически с любой функцией типа, которая зависит от T, как показано в этой ссылке на игровую площадку. И поведение, по-видимому, таково: как только вы используете небезопасный тип any в условном выражении, «истинный» регистр теперь приемлем, потому что any считается назначаемым как для всего, так и для всего, тогда как «ложный» регистр по существу сужается до never, но это похоже на ошибку или ограничение TS. Я пытаюсь сузить круг (изменено между TS3.8 и TS3.9)

jcalz 26.07.2024 18:54

@jcalz В любом случае это не должно быть принято в Test3.

lei li 26.07.2024 19:37

Хорошо, я сузил круг происходящего, и все работает так, как задумано. Если у вас есть условный тип формы T extends U ? V : W, истинному случаю присваивается тип замены, аналогичный пересечению U & V. Таким образом, B extends F<any> ? B : B в конечном итоге становится типом замены, таким как F<any> & B. Итак, AcceptA<F<any> & B> работает, потому что F<any> можно назначить F<A>. any некорректен. Если вы хотите «правильного» поведения, вам следует использовать readonly unknown[], а не readonly any[]. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то чего не хватает?

jcalz 26.07.2024 23:17

Почему U & V, есть ли у них отношения? Разве это не совершенно разные типы?

lei li 27.07.2024 00:51

@jcalz И еще вопрос, молодец! stackoverflow.com/questions/78800128/…

lei li 27.07.2024 01:11

Извините, я оговорился... это T extends U ? T : W должно давать вам T & U... это означает, что T должно быть присвоено U, потому что T extends U, поэтому T в дальнейшем рассматривается как T & U. Имеет ли это смысл сейчас? Мне написать ответ?

jcalz 27.07.2024 01:34

Это имеет смысл. Спасибо.@jcalz

lei li 27.07.2024 02:02
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
7
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обратите внимание, что это не характерно для ReadonlyArray, но можно увидеть практически для любой функции типа F<T>, которая зависит от T:

type F<T> = ReadonlyArray<T>;
// or type F<T> = {x: T}
// or type F<T> = () => T
// or type F<T> = (x: T) => void

type A = F<'A'>
type B = F<'B'>
type AcceptA<T extends A> = T
type Test1 = AcceptA<A> // okay
type Test2 = AcceptA<B> // error
type Test3 = AcceptA<B extends F<any> ? B : B> // okay

Такое поведение предусмотрено microsoft/TypeScript#59430.

Условные типы создают так называемый «тип замены» в истинной ветви: см. microsoft/TypeScript#43582:

Мы создаем «типы замены» в условных типах — при написании T extends U ? TrueBranch : FalseBranch мы заменяем T чем-то, что эквивалентно T & U в TrueBranch.

И любой тип намеренно небезопасен и считается, что его можно назначить как любому другому типу, так и от него. Таким образом, хотя F<'A'> и F<'B'> не являются взаимоназначаемыми напрямую, оба F<'A'> и F<'B'> можно назначать F<any> и из F<any>. Это означает, что вы можете поставить F<'B'> между F<'A'> и B extends F<any> ? B : B, чтобы получить цепочку назначаемых типов.

Итак, B extends F<any> ? (B & F<any>) : B оценивается как B. (Обратите внимание, что до TypeScript 3.9 там не было типа замены, потому что B не является универсальным. Но microsoft/TypeScript#37348 изменил его так, что вы получаете типы замены даже для определенных типов, таких как B extends F<any>).


И теперь вы можете увидеть, что пошло не так. F<'B'> extends F<any> — то же самое, что проверить B & F<any>, и это правда. Таким образом, мы получаем истинную ветвь, эквивалентную AcceptA. И это приемлемо для B & F<any>:

type Test4 = AcceptA<B & F<any>> // okay

поскольку пересечения можно назначить обоим членам пересечения, а это означает, что F<any> можно назначить F<A>, который можно назначить F<T>.

Все это работает так, как задумано. Хотя это определенно странно.

Детская площадка, ссылка на код

Хотя вы объяснили, почему так произошло. Но почему это не ошибка? В результате это не должно быть принято в Test3.

lei li 27.07.2024 13:14

Другие вопросы по теме

Похожие вопросы

Аргумент типа «Число» нельзя назначить параметру типа «строка | номер | ноль | неопределенный'. [плагин angular-компилятора]
Переход с Angular JS на Angular 18
Как сделать цвет фона прозрачным с помощью Phaser 3
TypeScript: [Страна, Штат], где параметры автозаполнения штата меняются в зависимости от введенной страны
После недопустимого входа на сервер ASP.NET MVC, как я могу записать текст ошибки в возвращенном ответе Json и отобразить его в клиенте Angular?
Angular – почему мой раскрывающийся список не работает
Angular V18 PM [vite] Внутренняя ошибка сервера: страница /home не отображается в течение 30 секунд
Как выполнить проверку Type Guard для индексированного свойства
Angular 18 @для возврата ОШИБКИ TypeError: newCollection[Symbol.iterator] не является функцией
Сохранение информации о типе при обеспечении связи типов между членами кортежа при использовании в параметре функции сопоставленного типа