You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This PR expands upon #9163 by implementing number, enum, and boolean literal types and adding more comprehensive checking and type guard constructs for literal types. The PR supercedes #6196 and #7480, and fixes#2983, #6149, #6155, #7447, and #7642.
It is now possible to use string, numeric, and boolean literals (true and false) as literal types:
The predefined boolean type is now equivalent to the union type true | false.
When each member of an enum type has either an automatically assigned value, an initializer that specifies a numeric literal, or an initializer that specifies a single identifier naming another enum member, that enum type is considered a union enum type. The members of a union enum type can be used both as constants and as types, and the enum type is equivalent to a union of the declared member types.
All literal types as well as the null and undefined types are considered unit types. A unit type is a type that has only a single value.
Literal types have the following type relationships:
A string literal type is a subtype of and assignable to type string.
A numeric literal type is a subtype of and assignable to type number.
The boolean literal types true and false are subtypes of and assignable to type boolean.
A union enum member type is a subtype of and assignable to the containing enum type.
Certain expression locations are now considered literal type locations. In a literal type location, a literal expression has a literal type (e.g. "hello", 0, true, ShapeKind.Circle) instead of a regular type (e.g. string, number, boolean, ShapeKind). The following expression locations are considered literal type locations:
Operands of the ===, !==, ==, and != operators.
An expression in a case clause of a switch statement.
An expression within parentheses in a literal type location.
The true and false expressions of a conditional operator (?:) in a literal type location.
An expression that is contextually typed by a literal type or a union of one or more literal types.
When both operands of ===, !==, ==, or != are unit types or unions of unit types, the operands are checked using those types and an error is reported if the types are not comparable. Otherwise the operands are checked with their full types.
constfoo: "foo"="foo";constbar: "bar"="bar";lets: string="abc";foo===bar;// Error, "foo" and "bar" not comparablefoo===s;// Okbar===s;// Ok
The equality comparison operators now narrow each operand based on the type of the other operand. For example:
When all case expressions in a switch statement have unit types, the switch expression variable is narrowed in each case block and in the default block based on the listed cases. For example:
functionf3(x: 0|1|2|3){switch(x){case0:
x;// Type of x is 0break;case1:
case2:
x;// Type of x is 1 | 2break;default:
x;// Type of x is 3}}
For an equality, truthiness, or switch type guard on a reference x.y, we not only narrow x.y but also narrow x itself based on the narrowed type of x.y. For example:
typeResult<T>={success: true,value: T}|{success: false};functionfoo(): Result<number>{if(someTest){return{success: true,value: 42};}else{return{success: false};}}functionunwrap<T>(x: Result<T>){switch(x.success){casetrue:
returnx.value;casefalse:
thrownewError("Missing value");}}letx=foo();lety1=x.success===true ? x.value : -1;// Type guard allows x.value to be accessedlety2=!x.success ? -1 : x.value;lety3=x.success&&x.value||-1;lety4=unwrap(x);
The && and || operators now understand that 0, false, and "" are falsy unit types and stricter typing of these operators is implemented as described here.
("foo"==="bar");// is this an error in this case, since a literal// is inferred as a literal type at that location?
These are similar to the "this branch is always true"/false warnings you'd get in some languages (which we now have more of with --stictNullChecks I believe(?)). I'm still torn about whether they're errors or warnings..
@Eyas Yes, "foo" === "bar" is an error since the two string literals have incompatible literal types. This error is effectively the mechanism for detecting misspelled string literals. For example, see discussion in #6149.
Comments based on my experience testing my PR (which you should steal tests from):
I don't see any NaN or (+/-)Infinity numeric types - should they be there? NaN at least probably should - and it should be one of the Falsy types (and needs to be handled like 0). NaN also potentially needs to have some really funky and unique comparability rules - as NaN === NaN is false. Additionally, parsing -Infinity in a type position is likely to throw a wrench in the current literal type parsing scheme.
Are equivalent numbers with different text equivalent, eg 0x1 vs 1 vs 0o1 vs 01 (octal literals are super fun), and do they preserve their original text? I don't see anything in the type relationship arithmetic to account for this textual inequality (yet value equality).
@weswigham Unless someone has a compelling scenario, I'm strongly inclined not to include NaN and Infinity as literal types. Literal types intended for scenarios where you pick a (small) set of specific values to match on in type guards and overloads, and I simply can't imagine meaningful uses of NaN or Infinity as a discriminant values or literal values for overloading.
Yes, 1, 0x1, 1.0, and 1e0 are all the same type, but the canonical representation is always used when displaying types (i.e. the string you get by converting the number to text using "" + value).
The RWC baseline differences look good except for a few cases where our optimistic control flow analysis is fooled by hidden side effects in function calls. We're discussing that in #9998. Otherwise I think we're finally ready to merge this PR.
@mhegazy this doesn't have a milestone and isn't listed on the Roadmap... Is this going into TypeScript 2.0 or do we have to wait until TypeScript 2.1?
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
None yet
10 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR expands upon #9163 by implementing number, enum, and boolean literal types and adding more comprehensive checking and type guard constructs for literal types. The PR supercedes #6196 and #7480, and fixes #2983, #6149, #6155, #7447, and #7642.
It is now possible to use string, numeric, and boolean literals (
true
andfalse
) as literal types:The predefined
boolean
type is now equivalent to the union typetrue | false
.When each member of an enum type has either an automatically assigned value, an initializer that specifies a numeric literal, or an initializer that specifies a single identifier naming another enum member, that enum type is considered a union enum type. The members of a union enum type can be used both as constants and as types, and the enum type is equivalent to a union of the declared member types.
All literal types as well as the
null
andundefined
types are considered unit types. A unit type is a type that has only a single value.Literal types have the following type relationships:
string
.number
.true
andfalse
are subtypes of and assignable to typeboolean
.Certain expression locations are now considered literal type locations. In a literal type location, a literal expression has a literal type (e.g.
"hello"
,0
,true
,ShapeKind.Circle
) instead of a regular type (e.g.string
,number
,boolean
,ShapeKind
). The following expression locations are considered literal type locations:===
,!==
,==
, and!=
operators.case
clause of aswitch
statement.?:
) in a literal type location.When both operands of
===
,!==
,==
, or!=
are unit types or unions of unit types, the operands are checked using those types and an error is reported if the types are not comparable. Otherwise the operands are checked with their full types.The equality comparison operators now narrow each operand based on the type of the other operand. For example:
When all
case
expressions in aswitch
statement have unit types, theswitch
expression variable is narrowed in eachcase
block and in thedefault
block based on the listed cases. For example:For an equality, truthiness, or switch type guard on a reference
x.y
, we not only narrowx.y
but also narrowx
itself based on the narrowed type ofx.y
. For example:The
&&
and||
operators now understand that0
,false
, and""
are falsy unit types and stricter typing of these operators is implemented as described here.