Skip to content

Commit b4344f7

Browse files
committed
Narrow union members aswell as filter
1 parent 356bba1 commit b4344f7

File tree

5 files changed

+195
-25
lines changed

5 files changed

+195
-25
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15242,6 +15242,9 @@ namespace ts {
1524215242
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
1524315243
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
1524415244
}
15245+
if (hasDefaultClause) {
15246+
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
15247+
}
1524515248
/*
1524615249
The implied type is the raw type suggested by a
1524715250
value being caught in this clause.
@@ -15252,11 +15255,11 @@ namespace ts {
1525215255

1525315256
Example:
1525415257
switch (typeof x) {
15255-
case 'number':
15256-
case 'string': break;
15257-
default: break;
15258-
case 'number':
15259-
case 'boolean': break
15258+
case 'number':
15259+
case 'string': break;
15260+
default: break;
15261+
case 'number':
15262+
case 'boolean': break
1526015263
}
1526115264

1526215265
In the first clause (case `number` and `string`) the
@@ -15270,7 +15273,13 @@ namespace ts {
1527015273
boolean. We know that number cannot be selected
1527115274
because it is caught in the first clause.
1527215275
*/
15273-
const getTypeFromName = (text: string) => {
15276+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(getTypeFromName)), switchFacts);
15277+
if (impliedType.flags & TypeFlags.Union) {
15278+
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
15279+
}
15280+
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
15281+
15282+
function getTypeFromName(text: string) {
1527415283
switch (text) {
1527515284
case "function":
1527615285
return type.flags & TypeFlags.Any ? type : globalFunctionType;
@@ -15279,25 +15288,21 @@ namespace ts {
1527915288
default:
1528015289
return typeofTypesByName.get(text) || type;
1528115290
}
15282-
};
15283-
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
15284-
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(getTypeFromName)), switchFacts);
15285-
if (impliedType.flags & TypeFlags.Union) {
15286-
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
15287-
}
15288-
if (isTypeSubtypeOf(impliedType, type)) {
15289-
return impliedType;
15290-
}
15291-
if (type.flags & TypeFlags.Instantiable) {
15292-
const constraint = getBaseConstraintOfType(type) || anyType;
15293-
if (isTypeSubtypeOf(impliedType, constraint)) {
15294-
return getIntersectionType([type, impliedType]);
15291+
}
15292+
function narrowTypeForTypeofSwitch(candidate: Type) {
15293+
return (type: Type) => {
15294+
if (isTypeSubtypeOf(candidate, type)) {
15295+
return candidate;
1529515296
}
15296-
}
15297+
if (type.flags & TypeFlags.Instantiable) {
15298+
const constraint = getBaseConstraintOfType(type) || anyType;
15299+
if (isTypeSubtypeOf(candidate, constraint)) {
15300+
return getIntersectionType([type, candidate]);
15301+
}
15302+
}
15303+
return type;
15304+
};
1529715305
}
15298-
return hasDefaultClause ?
15299-
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
15300-
getTypeWithFacts(type, switchFacts);
1530115306
}
1530215307

1530315308
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {

tests/baselines/reference/narrowingByTypeofInSwitch.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,20 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
235235
case 'string': assertString(k); assertKeyofS(k); return;
236236
}
237237
}
238+
239+
function narrowingNarrows(x: {} | undefined) {
240+
switch (typeof x) {
241+
case 'number': assertNumber(x); return;
242+
case 'boolean': assertBoolean(x); return;
243+
case 'function': assertFunction(x); return;
244+
case 'symbol': assertSymbol(x); return;
245+
case 'object': const _: {} = x; return;
246+
case 'string': assertString(x); return;
247+
case 'undefined': assertUndefined(x); return;
248+
case 'number': assertNever(x); return;
249+
default: const _y: {} = x; return;
250+
}
251+
}
238252

239253

240254
//// [narrowingByTypeofInSwitch.js]
@@ -540,3 +554,34 @@ function keyofNarrowing(k) {
540554
return;
541555
}
542556
}
557+
function narrowingNarrows(x) {
558+
switch (typeof x) {
559+
case 'number':
560+
assertNumber(x);
561+
return;
562+
case 'boolean':
563+
assertBoolean(x);
564+
return;
565+
case 'function':
566+
assertFunction(x);
567+
return;
568+
case 'symbol':
569+
assertSymbol(x);
570+
return;
571+
case 'object':
572+
var _ = x;
573+
return;
574+
case 'string':
575+
assertString(x);
576+
return;
577+
case 'undefined':
578+
assertUndefined(x);
579+
return;
580+
case 'number':
581+
assertNever(x);
582+
return;
583+
default:
584+
var _y = x;
585+
return;
586+
}
587+
}

tests/baselines/reference/narrowingByTypeofInSwitch.symbols

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,48 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
670670
}
671671
}
672672

673+
function narrowingNarrows(x: {} | undefined) {
674+
>narrowingNarrows : Symbol(narrowingNarrows, Decl(narrowingByTypeofInSwitch.ts, 235, 1))
675+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
676+
677+
switch (typeof x) {
678+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
679+
680+
case 'number': assertNumber(x); return;
681+
>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1))
682+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
683+
684+
case 'boolean': assertBoolean(x); return;
685+
>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1))
686+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
687+
688+
case 'function': assertFunction(x); return;
689+
>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1))
690+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
691+
692+
case 'symbol': assertSymbol(x); return;
693+
>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1))
694+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
695+
696+
case 'object': const _: {} = x; return;
697+
>_ : Symbol(_, Decl(narrowingByTypeofInSwitch.ts, 243, 28))
698+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
699+
700+
case 'string': assertString(x); return;
701+
>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1))
702+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
703+
704+
case 'undefined': assertUndefined(x); return;
705+
>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 30, 1))
706+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
707+
708+
case 'number': assertNever(x); return;
709+
>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0))
710+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
711+
712+
default: const _y: {} = x; return;
713+
>_y : Symbol(_y, Decl(narrowingByTypeofInSwitch.ts, 247, 22))
714+
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
715+
}
716+
}
717+

tests/baselines/reference/narrowingByTypeofInSwitch.types

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ function multipleGenericFuse<X extends L | number, Y extends R | number>(xy: X |
564564

565565
case 'number': return [xy]
566566
>'number' : "number"
567-
>[xy] : [X | Y]
568-
>xy : X | Y
567+
>[xy] : [(X & number) | (Y & number)]
568+
>xy : (X & number) | (Y & number)
569569
}
570570
}
571571

@@ -808,3 +808,64 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
808808
}
809809
}
810810

811+
function narrowingNarrows(x: {} | undefined) {
812+
>narrowingNarrows : (x: {} | undefined) => void
813+
>x : {} | undefined
814+
815+
switch (typeof x) {
816+
>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
817+
>x : {} | undefined
818+
819+
case 'number': assertNumber(x); return;
820+
>'number' : "number"
821+
>assertNumber(x) : number
822+
>assertNumber : (x: number) => number
823+
>x : number
824+
825+
case 'boolean': assertBoolean(x); return;
826+
>'boolean' : "boolean"
827+
>assertBoolean(x) : boolean
828+
>assertBoolean : (x: boolean) => boolean
829+
>x : boolean
830+
831+
case 'function': assertFunction(x); return;
832+
>'function' : "function"
833+
>assertFunction(x) : Function
834+
>assertFunction : (x: Function) => Function
835+
>x : Function
836+
837+
case 'symbol': assertSymbol(x); return;
838+
>'symbol' : "symbol"
839+
>assertSymbol(x) : symbol
840+
>assertSymbol : (x: symbol) => symbol
841+
>x : symbol
842+
843+
case 'object': const _: {} = x; return;
844+
>'object' : "object"
845+
>_ : {}
846+
>x : {}
847+
848+
case 'string': assertString(x); return;
849+
>'string' : "string"
850+
>assertString(x) : string
851+
>assertString : (x: string) => string
852+
>x : string
853+
854+
case 'undefined': assertUndefined(x); return;
855+
>'undefined' : "undefined"
856+
>assertUndefined(x) : undefined
857+
>assertUndefined : (x: undefined) => undefined
858+
>x : undefined
859+
860+
case 'number': assertNever(x); return;
861+
>'number' : "number"
862+
>assertNever(x) : never
863+
>assertNever : (x: never) => never
864+
>x : never
865+
866+
default: const _y: {} = x; return;
867+
>_y : {}
868+
>x : {}
869+
}
870+
}
871+

tests/cases/compiler/narrowingByTypeofInSwitch.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,17 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
237237
case 'string': assertString(k); assertKeyofS(k); return;
238238
}
239239
}
240+
241+
function narrowingNarrows(x: {} | undefined) {
242+
switch (typeof x) {
243+
case 'number': assertNumber(x); return;
244+
case 'boolean': assertBoolean(x); return;
245+
case 'function': assertFunction(x); return;
246+
case 'symbol': assertSymbol(x); return;
247+
case 'object': const _: {} = x; return;
248+
case 'string': assertString(x); return;
249+
case 'undefined': assertUndefined(x); return;
250+
case 'number': assertNever(x); return;
251+
default: const _y: {} = x; return;
252+
}
253+
}

0 commit comments

Comments
 (0)