Skip to content

Commit

Permalink
Merge pull request #11587 from Microsoft/narrowStringAndNumber
Browse files Browse the repository at this point in the history
Narrow string and number types in literal equality checks
  • Loading branch information
ahejlsberg authored Oct 13, 2016
2 parents 31a55e6 + 8bcb22c commit 17c2ab2
Show file tree
Hide file tree
Showing 14 changed files with 551 additions and 22 deletions.
28 changes: 26 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8461,6 +8461,28 @@ namespace ts {
return f(type) ? type : neverType;
}

function mapType(type: Type, f: (t: Type) => Type): Type {
return type.flags & TypeFlags.Union ? getUnionType(map((<UnionType>type).types, f)) : f(type);
}

function extractTypesOfKind(type: Type, kind: TypeFlags) {
return filterType(type, t => (t.flags & kind) !== 0);
}

// Return a new type in which occurrences of the string and number primitive types in
// typeWithPrimitives have been replaced with occurrences of string literals and numeric
// literals in typeWithLiterals, respectively.
function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) {
if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) ||
isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral)) {
return mapType(typeWithPrimitives, t =>
t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral) :
t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) :
t);
}
return typeWithPrimitives;
}

function isIncomplete(flowType: FlowType) {
return flowType.flags === 0;
}
Expand Down Expand Up @@ -8796,7 +8818,7 @@ namespace ts {
}
if (assumeTrue) {
const narrowedType = filterType(type, t => areTypesComparable(t, valueType));
return narrowedType.flags & TypeFlags.Never ? type : narrowedType;
return narrowedType.flags & TypeFlags.Never ? type : replacePrimitivesWithLiterals(narrowedType, valueType);
}
if (isUnitType(valueType)) {
const regularType = getRegularTypeOfLiteralType(valueType);
Expand Down Expand Up @@ -8843,7 +8865,9 @@ namespace ts {
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
const discriminantType = getUnionType(clauseTypes);
const caseType = discriminantType.flags & TypeFlags.Never ? neverType : filterType(type, t => isTypeComparableTo(discriminantType, t));
const caseType =
discriminantType.flags & TypeFlags.Never ? neverType :
replacePrimitivesWithLiterals(filterType(type, t => isTypeComparableTo(discriminantType, t)), discriminantType);
if (!hasDefaultClause) {
return caseType;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ namespace ts {
}

function scanBinaryOrOctalDigits(base: number): number {
Debug.assert(base !== 2 || base !== 8, "Expected either base 2 or base 8");
Debug.assert(base === 2 || base === 8, "Expected either base 2 or base 8");

let value = 0;
// For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b.
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2644,7 +2644,7 @@ namespace ts {
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | StructuredType | TypeParameter | StringLike | NumberLike | BooleanLike | ESSymbol,
NotUnionOrUnit = Any | String | Number | ESSymbol | ObjectType,
NotUnionOrUnit = Any | ESSymbol | ObjectType,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
/* @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function foo(a: string): string | number {

return a.length;
>a.length : number
>a : string
>a : "hello"
>length : number
}

Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/literalTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function f4(x: 0 | 1 | true | string) {
>"def" : "def"

x;
>x : string
>x : "abc" | "def"

break;
case null:
Expand Down Expand Up @@ -163,7 +163,7 @@ function f5(x: string | number | boolean) {
>"abc" : "abc"

x;
>x : string
>x : "abc"

break;
case 0:
Expand All @@ -173,7 +173,7 @@ function f5(x: string | number | boolean) {
>1 : 1

x;
>x : number
>x : 0 | 1

break;
case true:
Expand All @@ -190,7 +190,7 @@ function f5(x: string | number | boolean) {
>123 : 123

x;
>x : string | number
>x : "hello" | 123

break;
default:
Expand Down
125 changes: 125 additions & 0 deletions tests/baselines/reference/literalTypes3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//// [literalTypes3.ts]

function f1(s: string) {
if (s === "foo") {
s; // "foo"
}
if (s === "foo" || s === "bar") {
s; // "foo" | "bar"
}
}

function f2(s: string) {
switch (s) {
case "foo":
case "bar":
s; // "foo" | "bar"
case "baz":
s; // "foo" | "bar" | "baz"
break;
default:
s; // string
}
}

function f3(s: string) {
return s === "foo" || s === "bar" ? s : undefined; // "foo" | "bar" | undefined
}

function f4(x: number) {
if (x === 1 || x === 2) {
return x; // 1 | 2
}
throw new Error();
}

function f5(x: number, y: 1 | 2) {
if (x === 0 || x === y) {
x; // 0 | 1 | 2
}
}

function f6(x: number, y: 1 | 2) {
if (y === x || 0 === x) {
x; // 0 | 1 | 2
}
}

function f7(x: number | "foo" | "bar", y: 1 | 2 | string) {
if (x === y) {
x; // "foo" | "bar" | 1 | 2
}
}

function f8(x: number | "foo" | "bar") {
switch (x) {
case 1:
case 2:
x; // 1 | 2
break;
case "foo":
x; // "foo"
break;
default:
x; // number | "bar"
}
}

//// [literalTypes3.js]
function f1(s) {
if (s === "foo") {
s; // "foo"
}
if (s === "foo" || s === "bar") {
s; // "foo" | "bar"
}
}
function f2(s) {
switch (s) {
case "foo":
case "bar":
s; // "foo" | "bar"
case "baz":
s; // "foo" | "bar" | "baz"
break;
default:
s; // string
}
}
function f3(s) {
return s === "foo" || s === "bar" ? s : undefined; // "foo" | "bar" | undefined
}
function f4(x) {
if (x === 1 || x === 2) {
return x; // 1 | 2
}
throw new Error();
}
function f5(x, y) {
if (x === 0 || x === y) {
x; // 0 | 1 | 2
}
}
function f6(x, y) {
if (y === x || 0 === x) {
x; // 0 | 1 | 2
}
}
function f7(x, y) {
if (x === y) {
x; // "foo" | "bar" | 1 | 2
}
}
function f8(x) {
switch (x) {
case 1:
case 2:
x; // 1 | 2
break;
case "foo":
x; // "foo"
break;
default:
x; // number | "bar"
}
}
137 changes: 137 additions & 0 deletions tests/baselines/reference/literalTypes3.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
=== tests/cases/conformance/types/literal/literalTypes3.ts ===

function f1(s: string) {
>f1 : Symbol(f1, Decl(literalTypes3.ts, 0, 0))
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))

if (s === "foo") {
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))

s; // "foo"
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))
}
if (s === "foo" || s === "bar") {
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))

s; // "foo" | "bar"
>s : Symbol(s, Decl(literalTypes3.ts, 1, 12))
}
}

function f2(s: string) {
>f2 : Symbol(f2, Decl(literalTypes3.ts, 8, 1))
>s : Symbol(s, Decl(literalTypes3.ts, 10, 12))

switch (s) {
>s : Symbol(s, Decl(literalTypes3.ts, 10, 12))

case "foo":
case "bar":
s; // "foo" | "bar"
>s : Symbol(s, Decl(literalTypes3.ts, 10, 12))

case "baz":
s; // "foo" | "bar" | "baz"
>s : Symbol(s, Decl(literalTypes3.ts, 10, 12))

break;
default:
s; // string
>s : Symbol(s, Decl(literalTypes3.ts, 10, 12))
}
}

function f3(s: string) {
>f3 : Symbol(f3, Decl(literalTypes3.ts, 21, 1))
>s : Symbol(s, Decl(literalTypes3.ts, 23, 12))

return s === "foo" || s === "bar" ? s : undefined; // "foo" | "bar" | undefined
>s : Symbol(s, Decl(literalTypes3.ts, 23, 12))
>s : Symbol(s, Decl(literalTypes3.ts, 23, 12))
>s : Symbol(s, Decl(literalTypes3.ts, 23, 12))
>undefined : Symbol(undefined)
}

function f4(x: number) {
>f4 : Symbol(f4, Decl(literalTypes3.ts, 25, 1))
>x : Symbol(x, Decl(literalTypes3.ts, 27, 12))

if (x === 1 || x === 2) {
>x : Symbol(x, Decl(literalTypes3.ts, 27, 12))
>x : Symbol(x, Decl(literalTypes3.ts, 27, 12))

return x; // 1 | 2
>x : Symbol(x, Decl(literalTypes3.ts, 27, 12))
}
throw new Error();
>Error : Symbol(Error, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
}

function f5(x: number, y: 1 | 2) {
>f5 : Symbol(f5, Decl(literalTypes3.ts, 32, 1))
>x : Symbol(x, Decl(literalTypes3.ts, 34, 12))
>y : Symbol(y, Decl(literalTypes3.ts, 34, 22))

if (x === 0 || x === y) {
>x : Symbol(x, Decl(literalTypes3.ts, 34, 12))
>x : Symbol(x, Decl(literalTypes3.ts, 34, 12))
>y : Symbol(y, Decl(literalTypes3.ts, 34, 22))

x; // 0 | 1 | 2
>x : Symbol(x, Decl(literalTypes3.ts, 34, 12))
}
}

function f6(x: number, y: 1 | 2) {
>f6 : Symbol(f6, Decl(literalTypes3.ts, 38, 1))
>x : Symbol(x, Decl(literalTypes3.ts, 40, 12))
>y : Symbol(y, Decl(literalTypes3.ts, 40, 22))

if (y === x || 0 === x) {
>y : Symbol(y, Decl(literalTypes3.ts, 40, 22))
>x : Symbol(x, Decl(literalTypes3.ts, 40, 12))
>x : Symbol(x, Decl(literalTypes3.ts, 40, 12))

x; // 0 | 1 | 2
>x : Symbol(x, Decl(literalTypes3.ts, 40, 12))
}
}

function f7(x: number | "foo" | "bar", y: 1 | 2 | string) {
>f7 : Symbol(f7, Decl(literalTypes3.ts, 44, 1))
>x : Symbol(x, Decl(literalTypes3.ts, 46, 12))
>y : Symbol(y, Decl(literalTypes3.ts, 46, 38))

if (x === y) {
>x : Symbol(x, Decl(literalTypes3.ts, 46, 12))
>y : Symbol(y, Decl(literalTypes3.ts, 46, 38))

x; // "foo" | "bar" | 1 | 2
>x : Symbol(x, Decl(literalTypes3.ts, 46, 12))
}
}

function f8(x: number | "foo" | "bar") {
>f8 : Symbol(f8, Decl(literalTypes3.ts, 50, 1))
>x : Symbol(x, Decl(literalTypes3.ts, 52, 12))

switch (x) {
>x : Symbol(x, Decl(literalTypes3.ts, 52, 12))

case 1:
case 2:
x; // 1 | 2
>x : Symbol(x, Decl(literalTypes3.ts, 52, 12))

break;
case "foo":
x; // "foo"
>x : Symbol(x, Decl(literalTypes3.ts, 52, 12))

break;
default:
x; // number | "bar"
>x : Symbol(x, Decl(literalTypes3.ts, 52, 12))
}
}
Loading

0 comments on commit 17c2ab2

Please sign in to comment.