Home > Software engineering >  TypeScript inference of string literals
TypeScript inference of string literals

Time:12-15

My question is pretty simple. Given this code:

const array = [1, 2, 3, 4, 5];
const map01 = array.map((v) => v % 2 ? "" : "");
//    ^? string[]
const map02 = array.map((v) => v % 2 ? "odd" : "even");
//    ^? ("odd" | "even")[]
const map03 = array.map((v) => v % 2 ? "!" : "");
//    ^? ("" | "!")[]

Why such odd inference by TypeScript? Shouldn't map01 be inferred as ""[]?

Playground

enter image description here

CodePudding user response:

The details for how and when literal type widening happens are laid out in microsoft/TypeScript#10676. In particular, it says:

  • In a function with no return type annotation, if the inferred return type is a literal type (but not a literal union type) and the function does not have a contextual type with a return type that includes literal types, the return type is widened to its widened literal type.

So this implies that in the case of "odd" | "even", you have a literal union type, which is not widened, while in the case of "", you have a non-union literal type, which is widened.

As for why this rule is used as opposed to a simpler "never widen" rule, it's a heuristic that was chosen to minimize surprise in "normal" circumstances. Literal types are generally more useful in unions than standing alone, so the compiler assumes that function foo() {return "abc"} is more likely intended to return string, while function bar() {return Math.random<0.5 ? "abc" : "def"} is more likely intended to return a union of literals. As a heuristic it's not perfect, but it has apparently caused less surprise than alternatives would.

There are various issues in GitHub where the current behavior is deemed to be working as intended; for example, see microsoft/TypeScript#51551 with this comment:

The widening rules are in place to heuristically attempt to infer "better" types and different code is allowed to behave differently under these heuristics

  • Related