См. код:
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
? Почему нет?
@jcalz В любом случае это не должно быть принято в Test3
.
Хорошо, я сузил круг происходящего, и все работает так, как задумано. Если у вас есть условный тип формы 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[]
. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то чего не хватает?
Почему U & V
, есть ли у них отношения? Разве это не совершенно разные типы?
@jcalz И еще вопрос, молодец! stackoverflow.com/questions/78800128/…
Извините, я оговорился... это T extends U ? T : W
должно давать вам T & U
... это означает, что T
должно быть присвоено U
, потому что T extends U
, поэтому T
в дальнейшем рассматривается как T & U
. Имеет ли это смысл сейчас? Мне написать ответ?
Это имеет смысл. Спасибо.@jcalz
Обратите внимание, что это не характерно для 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
.
Это имеет мало общего с
ReadonlyArray<T>
. Вы можете продемонстрировать то же самое практически с любой функцией типа, которая зависит отT
, как показано в этой ссылке на игровую площадку. И поведение, по-видимому, таково: как только вы используете небезопасный типany
в условном выражении, «истинный» регистр теперь приемлем, потому чтоany
считается назначаемым как для всего, так и для всего, тогда как «ложный» регистр по существу сужается доnever
, но это похоже на ошибку или ограничение TS. Я пытаюсь сузить круг (изменено между TS3.8 и TS3.9)