I love string literal union types in TypeScript. I came across a simple case where I was expecting the union type to be retained.
Here is a simple version:
That's because let and const are handled differently by TypeScript. Constants are always treated with the "narrower" type - in this case the literals. Variables (and non-readonly object properties) are treated with the "widened" type - string. This is a rule that comes along with literal types.
Now, while your second assignment may be a constant, the property of that constant is in fact mutable - it's a non-readonly property. If you don't provide a "contextual type", the narrow inference gets lost and you get the wider string type.
Here you can read more about literal types. I may quote:
The type inferred for a const variable or readonly property without a type annotation is the type of the initializer as-is.
The type inferred for a
letvariable,varvariable,parameter, ornon-readonly propertywith an initializer and no type annotation is the widened literal type of the initializer.
And even more clearly:
The type inferred for a property in an object literal is the widened literal type of the expression unless the property has a contextual type that includes literal types.
By the way, if you provide the contextual type for the constant, the type will be passed on to the variable:
const bar = foo ? 'foo' : 'bar';
let xyz = bar // xyz will be string
const bar: 'foo' | 'bar' = foo ? 'foo' : 'bar';
let xyz = bar // xyz will be 'foo' | 'bar'