Non-widening explicit literal types by ahejlsberg · Pull Request #11126 · microsoft/TypeScript (original) (raw)

This PR expands upon #10676 by making explicitly denoted literal types non-widening. String and numeric literal types now come in two flavors: Widening and non-widening. The two flavors are completely interchangeable in type relationships and control flow type analysis, but only one widens to the base primitive type when inferred as the type for a mutable location:

The net effect of this change is that literal type widening can be controlled through explicit type annotations. Specifically, when an expression of a literal type is inferred for a const location without a type annotation, that const variable gets a widening literal type inferred. But when a const location has an explicit literal type annotation, the const variable gets a non-widening literal type.

const c1 = "hello"; // Widening type "hello" let v1 = c1; // Type string const c2 = c1; // Widening type "hello" let v2 = c2; // Type string const c3: "hello" = "hello"; // Type "hello" let v3 = c3; // Type "hello" const c4: "hello" = c1; // Type "hello" let v4 = c4; // Type "hello"

The flavor of a literal type is preserved in union types, but the widening form disappears if the union also contains the non-widening form. Effectively, the non-widening form is given preference.

declare let cond: boolean; const c1 = cond ? "foo" : "bar"; // widening "foo" | widening "bar" const c2: "foo" | "bar" = c1; // "foo" | "bar" const c3 = cond ? c1 : c2; // "foo" | "bar" const c4 = cond ? c3 : "baz"; // "foo" | "bar" | widening "baz" const c5: "foo" | "bar" | "baz" = c4; // "foo" | "bar" | "baz" let v1 = c1; // string let v2 = c2; // "foo" | "bar" let v3 = c3; // "foo" | "bar" let v4 = c4; // string let v5 = c5; // "foo" | "bar" | "baz"

Fixes #10898.