type T<A> = A[never];
const tuple = ['1', '2'] as const
const array = ['1', '2']
type UninType = T<typeof tuple> // "1" | "2"
type StringType = T<typeof array> // string
Why is it done this way, what's the point of this behavior
type T<A> = A[never];
const tuple = ['1', '2'] as const
const array = ['1', '2']
type UninType = T<typeof tuple> // "1" | "2"
type StringType = T<typeof array> // string
Why is it done this way, what's the point of this behavior
Short answer:
This behavior is just an unintended consequence of how indexed access types were implemented. It was brought up at some point that T[never] should be never no matter what T is; this was partially fixed; for T with no index signatures.
The fix for index signatures was put off because at the time it would have broken other things, and having T[never] resolve to the index signature type was accidentally preventing another problem from showing up. Nowadays it probably would be fine to implement the full fix, but I guess nobody ever got around to it, probably because it's not really a critical issue. You might want to open a new issue asking for this to be addressed, if you care a lot.
Long answer:
If you look at microsoft/TypeScript#11929, the pull request that implemented indexed access types, it describes the (then) process for determining the type T[K]:
- If
Kis a type assignable tonumberandTcontains a numeric index signature,T[K]is the type of that numeric index signature.- If
Kis a type assignable tostringandTcontains a string index signature,T[K]is the type of that string index signature.
In your case, since array and tuple types have a numeric index signature, and since the never type is assignable to every type, then indexing into an array or tuple type with never will yield the type of the numeric index signature.
That's consistent with your results anyway.
Now at some point people noticed that indexing into a type like {a: string} with never was resulting in any and erroring out. This was not desirable; it seems that you should be able to index with never and the result should also be never, like this:
type SomeObj = {a: string}[never]
// type SomeObj = never
So this was filed as an issue in microsoft/TypeScript#22042 and it was implemented in microsoft/TypeScript/pull/22787. (which is why the above SomeObj is never and not any).
In the comments on that fix, someone noted that:
this change only affects types with no index signature. So for example:
{[x: string]: boolean}[never]isboolean(unchanged behaviour){foo: boolean}[never]isnever(new behaviour, previously was an error)With that qualification, this is a non-breaking change because it only affects cases that are currently errors.
One of the TS team then replies that
After discussing it in person with @ahejlsberg, we didn't like the fact that
({ [n: number]: boolean})[never]is inconsistent: it's notneverlike everything else...
but they couldn't fix it. At the time, keyof ({[n: number]: boolean}) was never instead of number. I think we can all agree that if XXX is {[n: number]: boolean}, then we want XXX[keyof XXX] to be boolean. But since keyof XXX was never, then this sort of forced them to have XXX[never] also be boolean and not never. They couldn't fix it without breaking something that was accidentally working.
Since then, TypeScript 2.9 introduced a change to allow number and symbol types to come out of keyof, implemented in microsoft/TypeScript#23592. So now we have the right behavior:
type KeyofNumericIdxSig = keyof { [x: number]: boolean }
// type KeyofNumericIdxSig = number
But at the time microsoft/TypeScript#22787 was implemented, this had not been done. And so the language architect @ahejlsberg himself said:
[This PR] moves us closer to where we eventually want to be, i.e. that
T[never]is alwaysneverregardless ofT. Longer term, if and when we fixkeyof { [x: number]: T }to producenumeric-string(or whatever we call it) instead ofnever, we can then also change{ [x: number]: T }[never]to benever.
But it looks like they never came back and completed this.
So there you are. It's possible that if someone were to open a feature request issue linking to both microsoft/TypeScript#22787 and microsoft/TypeScript#23592 and pointing to this comment in particular, they would say "oh gee, yeah we never did that" and implement it. Or they might say "oh yeah we tried that and it did bad things elsewhere." Or even "we don't want to worry too much about this unless a lot more people complain about it". I can't really predict which.