Skip to content

Commit 88a4fdf

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

File tree

5 files changed

+201
-30
lines changed

5 files changed

+201
-30
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15215,6 +15215,32 @@ namespace ts {
1521515215
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
1521615216
}
1521715217

15218+
function getTypeFromName(type: Type, text: string) {
15219+
switch (text) {
15220+
case "function":
15221+
return type.flags & TypeFlags.Any ? type : globalFunctionType;
15222+
case "object":
15223+
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
15224+
default:
15225+
return typeofTypesByName.get(text) || type;
15226+
}
15227+
}
15228+
15229+
function narrowTypeForTypeofSwitch(candidate: Type) {
15230+
return (type: Type) => {
15231+
if (isTypeSubtypeOf(candidate, type)) {
15232+
return candidate;
15233+
}
15234+
if (type.flags & TypeFlags.Instantiable) {
15235+
const constraint = getBaseConstraintOfType(type) || anyType;
15236+
if (isTypeSubtypeOf(candidate, constraint)) {
15237+
return getIntersectionType([type, candidate]);
15238+
}
15239+
}
15240+
return type;
15241+
};
15242+
}
15243+
1521815244
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
1521915245
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
1522015246
if (!switchWitnesses.length) {
@@ -15232,7 +15258,7 @@ namespace ts {
1523215258
// that we don't have to worry about undefined
1523315259
// in the witness array.
1523415260
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
15235-
// The adjust clause start and end after removing the `default` statement.
15261+
// The adjusted clause start and end after removing the `default` statement.
1523615262
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
1523715263
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
1523815264
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
@@ -15242,6 +15268,9 @@ namespace ts {
1524215268
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
1524315269
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
1524415270
}
15271+
if (hasDefaultClause) {
15272+
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
15273+
}
1524515274
/*
1524615275
The implied type is the raw type suggested by a
1524715276
value being caught in this clause.
@@ -15270,34 +15299,11 @@ namespace ts {
1527015299
boolean. We know that number cannot be selected
1527115300
because it is caught in the first clause.
1527215301
*/
15273-
const getTypeFromName = (text: string) => {
15274-
switch (text) {
15275-
case "function":
15276-
return type.flags & TypeFlags.Any ? type : globalFunctionType;
15277-
case "object":
15278-
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
15279-
default:
15280-
return typeofTypesByName.get(text) || type;
15281-
}
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]);
15295-
}
15296-
}
15302+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getTypeFromName(type, text))), switchFacts);
15303+
if (impliedType.flags & TypeFlags.Union) {
15304+
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
1529715305
}
15298-
return hasDefaultClause ?
15299-
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
15300-
getTypeWithFacts(type, switchFacts);
15306+
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
1530115307
}
1530215308

1530315309
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)