I'm having trouble understanding discriminated unions in typescript in the context of an array. Using the example from the documentation, I would expect the below to be perfectly valid.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
const getShapes = (): Shape[] => {
const data = [
{ kind: "circle", radius: 1 },
{ kind: "square", sideLength: 3}
]
return data;
}
My understanding is this is saying "a Shape must be either a Circle or a Square".
Instead it gives this error:
Type '({ kind: string; radius: number; sideLength?: undefined; } | { kind: string; sideLength: number; radius?: undefined; })[]' is not assignable to type 'Shape[]'. Type '{ kind: string; radius: number; sideLength?: undefined; } | { kind: string; sideLength: number; radius?: undefined; }' is not assignable to type 'Shape'. Type '{ kind: string; radius: number; sideLength?: undefined; }' is not assignable to type 'Shape'. Type '{ kind: string; radius: number; sideLength?: undefined; }' is not assignable to type 'Square'. Types of property 'kind' are incompatible. Type 'string' is not assignable to type '"square"'.
I'm specifically confused about seeing kind: string as how typescript interpreted that, when it matches the literal. Based on how I'm reading the docs, this seems like a perfectly valid use for a union
When presented with a variable declaration
const data = ⋯with no type annotation, TypeScript infers the type of the variable from the initializer, based on heuristic inference rules that work well in a wide range of scenarios. It does not currently have the ability to defer that and "look ahead" to see how the variable is used later. Giventhe compiler infers
because that's what inference rules say to do. In particular, string literal properties get widened to
string, since it's very common for people to modify the values of properties. The compiler does not see that you intend to returndataasShape[], so it doesn't know that you intend for thekindproperty to have string literal types. By the time youreturn datais encountered, it is too late. The type ofdatais already set.If you want to fix this you should either annotate
datalikeconst data: Shape[] = ⋯, or you could use thesatisfiesoperator on the initializer to give the compiler the context it is missing:Playground link to code