Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-widening explicit literal types #11126

Merged
merged 12 commits into from Sep 26, 2016

Conversation

@ahejlsberg
Copy link
Member

ahejlsberg commented Sep 24, 2016

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:

  • String and numeric literals occurring in expressions have widening literal types.
  • String and numeric literals occurring in types have non-widening literal types.

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.

@Igorbek

This comment has been minimized.

Copy link
Contributor

Igorbek commented Sep 24, 2016

Is that gonna be a two separate kinds of literal types? would this type flow through?

function f<T>(value: T): T { return value; }
const a1: "a" = "a";
const a2 = "a"; // widening "a"
let v1 = f(a1); // "a" ?
let v2 = f(a2); // string ?
const c3 =f(a2); // widening "a" ?
@ahejlsberg

This comment has been minimized.

Copy link
Member Author

ahejlsberg commented Sep 24, 2016

@Igorbek Yes, the widening and non-widening literal types for a particular literal are distinct types. Your example behaves as you expected:

function f<T>(value: T): T { return value; }
const a1: "a" = "a";  // "a"
const a2 = "a";  // widening "a"
let v1 = f(a1);  // "a"
let v2 = f(a2);  // string
const c3 = f(a2);  // widening "a"
@wycats

This comment has been minimized.

Copy link

wycats commented Sep 26, 2016

@ahejlsberg thanks so much for taking my feedback into consideration. I'm glad my proposal proved more-or-less tractable, and after a bit of a scare in our codebase, I'm looking forward to continuing to use improved literal types in Glimmer.

I want to add that I really appreciate the stewardship of the TypeScript project, both in efforts to explain the rationale for changes and taking considered feedback into consideration in response to those rationales. All in all, this back-and-forth was pretty healthy, and a good example of what I like about the project. 😄

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants
You can’t perform that action at this time.