Skip to content

Commit b12ed40

Browse files
committed
Bring typeof switch inline with if
- Narrow unknown - Narrow union members (in addition to filtering)
1 parent 07966dc commit b12ed40

File tree

5 files changed

+705
-248
lines changed

5 files changed

+705
-248
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15317,6 +15317,32 @@ namespace ts {
1531715317
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
1531815318
}
1531915319

15320+
function getTypeFromName(type: Type, text: string) {
15321+
switch (text) {
15322+
case "function":
15323+
return type.flags & TypeFlags.Any ? type : globalFunctionType;
15324+
case "object":
15325+
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
15326+
default:
15327+
return typeofTypesByName.get(text) || type;
15328+
}
15329+
}
15330+
15331+
function narrowTypeForTypeofSwitch(candidate: Type) {
15332+
return (type: Type) => {
15333+
if (isTypeSubtypeOf(candidate, type)) {
15334+
return candidate;
15335+
}
15336+
if (type.flags & TypeFlags.Instantiable) {
15337+
const constraint = getBaseConstraintOfType(type) || anyType;
15338+
if (isTypeSubtypeOf(candidate, constraint)) {
15339+
return getIntersectionType([type, candidate]);
15340+
}
15341+
}
15342+
return type;
15343+
};
15344+
}
15345+
1532015346
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
1532115347
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
1532215348
if (!switchWitnesses.length) {
@@ -15334,7 +15360,7 @@ namespace ts {
1533415360
// that we don't have to worry about undefined
1533515361
// in the witness array.
1533615362
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
15337-
// The adjust clause start and end after removing the `default` statement.
15363+
// The adjusted clause start and end after removing the `default` statement.
1533815364
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
1533915365
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
1534015366
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
@@ -15344,6 +15370,9 @@ namespace ts {
1534415370
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
1534515371
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
1534615372
}
15373+
if (hasDefaultClause) {
15374+
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
15375+
}
1534715376
/*
1534815377
The implied type is the raw type suggested by a
1534915378
value being caught in this clause.
@@ -15372,26 +15401,11 @@ namespace ts {
1537215401
boolean. We know that number cannot be selected
1537315402
because it is caught in the first clause.
1537415403
*/
15375-
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
15376-
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
15377-
if (impliedType.flags & TypeFlags.Union) {
15378-
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
15379-
}
15380-
if (!(impliedType.flags & TypeFlags.Never)) {
15381-
if (isTypeSubtypeOf(impliedType, type)) {
15382-
return impliedType;
15383-
}
15384-
if (type.flags & TypeFlags.Instantiable) {
15385-
const constraint = getBaseConstraintOfType(type) || anyType;
15386-
if (isTypeSubtypeOf(impliedType, constraint)) {
15387-
return getIntersectionType([type, impliedType]);
15388-
}
15389-
}
15390-
}
15404+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getTypeFromName(type, text))), switchFacts);
15405+
if (impliedType.flags & TypeFlags.Union) {
15406+
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
1539115407
}
15392-
return hasDefaultClause ?
15393-
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
15394-
getTypeWithFacts(type, switchFacts);
15408+
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
1539515409
}
1539615410

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

tests/baselines/reference/narrowingByTypeofInSwitch.js

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ function assertObject(x: object) {
2727
return x;
2828
}
2929

30+
function assertObjectOrNull(x: object | null) {
31+
return x;
32+
}
33+
3034
function assertUndefined(x: undefined) {
3135
return x;
3236
}
@@ -35,11 +39,11 @@ function assertAll(x: Basic) {
3539
return x;
3640
}
3741

38-
function assertStringOrNumber(x: string | number) {
42+
function assertStringOrNumber(x: string | number) {
3943
return x;
4044
}
4145

42-
function assertBooleanOrObject(x: boolean | object) {
46+
function assertBooleanOrObject(x: boolean | object) {
4347
return x;
4448
}
4549

@@ -210,6 +214,41 @@ function fallThroughTest(x: string | number | boolean | object) {
210214
break;
211215
}
212216
}
217+
218+
function unknownNarrowing(x: unknown) {
219+
switch (typeof x) {
220+
case 'number': assertNumber(x); return;
221+
case 'boolean': assertBoolean(x); return;
222+
case 'function': assertFunction(x); return;
223+
case 'symbol': assertSymbol(x); return;
224+
case 'object': assertObjectOrNull(x); return;
225+
case 'string': assertString(x); return;
226+
case 'undefined': assertUndefined(x); return;
227+
}
228+
}
229+
230+
function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
231+
function assertKeyofS(k1: keyof S) { }
232+
switch (typeof k) {
233+
case 'number': assertNumber(k); assertKeyofS(k); return;
234+
case 'symbol': assertSymbol(k); assertKeyofS(k); return;
235+
case 'string': assertString(k); assertKeyofS(k); return;
236+
}
237+
}
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+
}
213252

214253

215254
//// [narrowingByTypeofInSwitch.js]
@@ -234,6 +273,9 @@ function assertFunction(x) {
234273
function assertObject(x) {
235274
return x;
236275
}
276+
function assertObjectOrNull(x) {
277+
return x;
278+
}
237279
function assertUndefined(x) {
238280
return x;
239281
}
@@ -470,3 +512,76 @@ function fallThroughTest(x) {
470512
break;
471513
}
472514
}
515+
function unknownNarrowing(x) {
516+
switch (typeof x) {
517+
case 'number':
518+
assertNumber(x);
519+
return;
520+
case 'boolean':
521+
assertBoolean(x);
522+
return;
523+
case 'function':
524+
assertFunction(x);
525+
return;
526+
case 'symbol':
527+
assertSymbol(x);
528+
return;
529+
case 'object':
530+
assertObjectOrNull(x);
531+
return;
532+
case 'string':
533+
assertString(x);
534+
return;
535+
case 'undefined':
536+
assertUndefined(x);
537+
return;
538+
}
539+
}
540+
function keyofNarrowing(k) {
541+
function assertKeyofS(k1) { }
542+
switch (typeof k) {
543+
case 'number':
544+
assertNumber(k);
545+
assertKeyofS(k);
546+
return;
547+
case 'symbol':
548+
assertSymbol(k);
549+
assertKeyofS(k);
550+
return;
551+
case 'string':
552+
assertString(k);
553+
assertKeyofS(k);
554+
return;
555+
}
556+
}
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+
}

0 commit comments

Comments
 (0)