10

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

4
  • 2
    Looks like this happens whenever both values are the same. ? "odd" : "odd" also results in string[] Commented Dec 14, 2022 at 16:57
  • 1
    Given what @CertainPerformance has discovered it's almost certainly a bug in TypeScript (not intended behavior). I'd file an issue with them to make sure. Commented Dec 14, 2022 at 17:23
  • The same behaviour occurs with simple functions that have two different paths typescriptlang.org/play?#code/… Commented Dec 14, 2022 at 17:30
  • Or when just a constant const map04 = array.map(() => "b"); // string[]. I have a hunch this is the same to the compiler as (v) => v ? 'b' : 'b'. But that's a pretty nonsensical ternary Commented Dec 14, 2022 at 17:31

1 Answer 1

10

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.