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:
parent
47385b29e0
commit
8c5ad2429a
7 changed files with 280 additions and 14 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ switch (x) {
|
|||
>"baz" : "baz"
|
||||
|
||||
x;
|
||||
>x : "foo"
|
||||
>x : never
|
||||
|
||||
y;
|
||||
>y : "foo" | "bar"
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
};
|
|
@ -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';
|
||||
}
|
||||
};
|
|
@ -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"
|
||||
}
|
||||
};
|
|
@ -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';
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue