Relax switch-case narrowing restrictions (#23522)

* Allow switch case narrowing even when individual clauses are nonunit

* And remove unit type restriction

* Rename
This commit is contained in:
Wesley Wigham 2018-04-27 16:30:43 -07:00 committed by GitHub
parent 47385b29e0
commit 8c5ad2429a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 280 additions and 14 deletions

View file

@ -11570,6 +11570,10 @@ namespace ts {
return !!getPropertyOfType(type, "0" as __String);
}
function isNeitherUnitTypeNorNever(type: Type): boolean {
return !(type.flags & (TypeFlags.Unit | TypeFlags.Never));
}
function isUnitType(type: Type): boolean {
return !!(type.flags & TypeFlags.Unit);
}
@ -13049,8 +13053,7 @@ namespace ts {
function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) {
if (clause.kind === SyntaxKind.CaseClause) {
const caseType = getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression));
return isUnitType(caseType) ? caseType : undefined;
return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression));
}
return neverType;
}
@ -13058,15 +13061,9 @@ namespace ts {
function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] {
const links = getNodeLinks(switchStatement);
if (!links.switchTypes) {
// If all case clauses specify expressions that have unit types, we return an array
// of those unit types. Otherwise we return an empty array.
links.switchTypes = [];
for (const clause of switchStatement.caseBlock.clauses) {
const type = getTypeOfSwitchClause(clause);
if (type === undefined) {
return links.switchTypes = emptyArray;
}
links.switchTypes.push(type);
links.switchTypes.push(getTypeOfSwitchClause(clause));
}
}
return links.switchTypes;
@ -19170,7 +19167,7 @@ namespace ts {
return false;
}
const switchTypes = getSwitchClauseTypes(node);
if (!switchTypes.length) {
if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) {
return false;
}
return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes);

View file

@ -61,19 +61,19 @@ function f2(x: 0 | 1 | 2) {
>zero : 0
x;
>x : 0 | 1 | 2
>x : 0
break;
case oneOrTwo:
>oneOrTwo : 1 | 2
x;
>x : 0 | 1 | 2
>x : 1 | 2
break;
default:
x;
>x : 0 | 1 | 2
>x : 1 | 2
}
}

View file

@ -51,7 +51,7 @@ switch (x) {
>"baz" : "baz"
x;
>x : "foo"
>x : never
y;
>y : "foo" | "bar"

View file

@ -0,0 +1,83 @@
//// [switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts]
export const narrowToLiterals = (str: string) => {
switch (str) {
case 'abc': {
// inferred type as `abc`
return str;
}
default:
return 'defaultValue';
}
};
export const narrowToString = (str: string, someOtherStr: string) => {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someOtherStr: {
// `string`
return str;
}
default:
return 'defaultValue';
}
};
export const narrowToStringOrNumber = (str: string | number, someNumber: number) => {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someNumber: {
// inferred type should be `number`
return str;
}
default:
return 'defaultValue';
}
};
//// [switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.js]
"use strict";
exports.__esModule = true;
exports.narrowToLiterals = function (str) {
switch (str) {
case 'abc': {
// inferred type as `abc`
return str;
}
default:
return 'defaultValue';
}
};
exports.narrowToString = function (str, someOtherStr) {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someOtherStr: {
// `string`
return str;
}
default:
return 'defaultValue';
}
};
exports.narrowToStringOrNumber = function (str, someNumber) {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someNumber: {
// inferred type should be `number`
return str;
}
default:
return 'defaultValue';
}
};

View file

@ -0,0 +1,67 @@
=== tests/cases/compiler/switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts ===
export const narrowToLiterals = (str: string) => {
>narrowToLiterals : Symbol(narrowToLiterals, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 0, 12))
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 0, 33))
switch (str) {
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 0, 33))
case 'abc': {
// inferred type as `abc`
return str;
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 0, 33))
}
default:
return 'defaultValue';
}
};
export const narrowToString = (str: string, someOtherStr: string) => {
>narrowToString : Symbol(narrowToString, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 14))
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 33))
>someOtherStr : Symbol(someOtherStr, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 45))
switch (str) {
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 33))
case 'abc': {
// inferred type should be `abc`
return str;
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 33))
}
case someOtherStr: {
>someOtherStr : Symbol(someOtherStr, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 45))
// `string`
return str;
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 11, 33))
}
default:
return 'defaultValue';
}
};
export const narrowToStringOrNumber = (str: string | number, someNumber: number) => {
>narrowToStringOrNumber : Symbol(narrowToStringOrNumber, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 14))
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 41))
>someNumber : Symbol(someNumber, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 62))
switch (str) {
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 41))
case 'abc': {
// inferred type should be `abc`
return str;
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 41))
}
case someNumber: {
>someNumber : Symbol(someNumber, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 62))
// inferred type should be `number`
return str;
>str : Symbol(str, Decl(switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts, 26, 41))
}
default:
return 'defaultValue';
}
};

View file

@ -0,0 +1,79 @@
=== tests/cases/compiler/switchCaseNarrowsMatchingClausesEvenWhenNonMatchingClausesExist.ts ===
export const narrowToLiterals = (str: string) => {
>narrowToLiterals : (str: string) => "abc" | "defaultValue"
>(str: string) => { switch (str) { case 'abc': { // inferred type as `abc` return str; } default: return 'defaultValue'; } } : (str: string) => "abc" | "defaultValue"
>str : string
switch (str) {
>str : string
case 'abc': {
>'abc' : "abc"
// inferred type as `abc`
return str;
>str : "abc"
}
default:
return 'defaultValue';
>'defaultValue' : "defaultValue"
}
};
export const narrowToString = (str: string, someOtherStr: string) => {
>narrowToString : (str: string, someOtherStr: string) => string
>(str: string, someOtherStr: string) => { switch (str) { case 'abc': { // inferred type should be `abc` return str; } case someOtherStr: { // `string` return str; } default: return 'defaultValue'; } } : (str: string, someOtherStr: string) => string
>str : string
>someOtherStr : string
switch (str) {
>str : string
case 'abc': {
>'abc' : "abc"
// inferred type should be `abc`
return str;
>str : "abc"
}
case someOtherStr: {
>someOtherStr : string
// `string`
return str;
>str : string
}
default:
return 'defaultValue';
>'defaultValue' : "defaultValue"
}
};
export const narrowToStringOrNumber = (str: string | number, someNumber: number) => {
>narrowToStringOrNumber : (str: string | number, someNumber: number) => number | "abc" | "defaultValue"
>(str: string | number, someNumber: number) => { switch (str) { case 'abc': { // inferred type should be `abc` return str; } case someNumber: { // inferred type should be `number` return str; } default: return 'defaultValue'; } } : (str: string | number, someNumber: number) => number | "abc" | "defaultValue"
>str : string | number
>someNumber : number
switch (str) {
>str : string | number
case 'abc': {
>'abc' : "abc"
// inferred type should be `abc`
return str;
>str : "abc"
}
case someNumber: {
>someNumber : number
// inferred type should be `number`
return str;
>str : number
}
default:
return 'defaultValue';
>'defaultValue' : "defaultValue"
}
};

View file

@ -0,0 +1,40 @@
export const narrowToLiterals = (str: string) => {
switch (str) {
case 'abc': {
// inferred type as `abc`
return str;
}
default:
return 'defaultValue';
}
};
export const narrowToString = (str: string, someOtherStr: string) => {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someOtherStr: {
// `string`
return str;
}
default:
return 'defaultValue';
}
};
export const narrowToStringOrNumber = (str: string | number, someNumber: number) => {
switch (str) {
case 'abc': {
// inferred type should be `abc`
return str;
}
case someNumber: {
// inferred type should be `number`
return str;
}
default:
return 'defaultValue';
}
};