Issue
Why, given that both nums and nums2 are of type (0 | 1 | 2)[], do tuple and tuple2 have different types?
// const nums: (0 | 1 | 2)[]
const nums: (0 | 1 | 2)[] = [];
// let tuple: (0 | 1 | 2)[]
let tuple = [nums[0], nums[1]];
// const nums2: (0 | 1 | 2)[]
const nums2 = ([] as {num?: 1 | 2}[]).
map(n => typeof n.num === "undefined" ? 0 : n.num)
// let tuple2: number[]
let tuple2 = [nums2[0], nums2[1]];
Solution
This is a consequence of the difference between widening and non-widening literal types, as implemented in microsoft/TypeScript#11126. It's understandably confusing, because IntelliSense does not display these types any differently; it's an invisible attribute of the type. For example, you can't tell if a type displayed as 0 | 1 | 2 is widening or not.
Generally speaking, a literal type that only occurs in an expression (and not in a type) will automatically widen to its corresponding base primitive type (so 0 will become number) when inferred in a place where the value could be reassigned (such as the element of a non-readonly array or tuple). So for this:
const nums = ([] as { num?: 1 | 2 }[]).map(n => typeof n.num === "undefined" ? 0 : n.num)
// const nums: (0 | 1 | 2)[]
The type of nums is an array of widening literal types, because the type 0 comes from the expression 0 and the type 1 | 2 comes from the expression n.num, but nowhere in that map() method call are such types explicitly written as types. And so 0 | 1 | 2 widens to number when inferred in a place that can be reassigned, such as an array literal:
let tuple = [nums[0], nums[1]];
// let tuple: number[]
On the other hand, a literal type that occurs explicitly in a type (not just an expression*) will not automatically widen in a similar situation. So for this:
const nums: (0 | 1 | 2)[] = [];
// const nums: (0 | 1 | 2)[];
The type of nums is an array of non-widening literal types, because the type 0 | 1 | 2 comes from the type annotation for nums. And so 0 | 1 | 2 does not widen to number when inferred in a place that can be reassigned, such as an array literal:
let tuple = [nums[0], nums[1]];
// let tuple: (0 | 1 | 2)[];
If you want a widening literal type to become non-widening, you can add an explicit type somewhere as a hint. Like explicitly typing one of the values in the expression:
const nums = ([] as { num?: 1 | 2 }[]).map(n => typeof n.num === "undefined" ? 0 as 0 : n.num)
// const nums: (0 | 1 | 2)[]
let tuple = [nums[0], nums[1]];
// let tuple: (0 | 1 | 2)[];
or explicitly specifying the type parameter for the call to map()
const nums = ([] as { num?: 1 | 2 }[]).map<0 | 1 | 2>(n => typeof n.num === "undefined" ? 0 : n.num)
// const nums: (0 | 1 | 2)[]
let tuple = [nums[0], nums[1]];
// let tuple: (0 | 1 | 2)[];
or any of the methods discussed in this comment inside microsoft/TypeScript#12267.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.