Skip to content

Commit abce9ae

Browse files
jack-williamsweswigham
authored andcommitted
Bring typeof switch inline with if (microsoft#27680)
- Narrow unknown - Narrow union members (in addition to filtering)
1 parent 77d8e15 commit abce9ae

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
@@ -15441,6 +15441,32 @@ namespace ts {
1544115441
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
1544215442
}
1544315443

15444+
function getImpliedTypeFromTypeofCase(type: Type, text: string) {
15445+
switch (text) {
15446+
case "function":
15447+
return type.flags & TypeFlags.Any ? type : globalFunctionType;
15448+
case "object":
15449+
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
15450+
default:
15451+
return typeofTypesByName.get(text) || type;
15452+
}
15453+
}
15454+
15455+
function narrowTypeForTypeofSwitch(candidate: Type) {
15456+
return (type: Type) => {
15457+
if (isTypeSubtypeOf(candidate, type)) {
15458+
return candidate;
15459+
}
15460+
if (type.flags & TypeFlags.Instantiable) {
15461+
const constraint = getBaseConstraintOfType(type) || anyType;
15462+
if (isTypeSubtypeOf(candidate, constraint)) {
15463+
return getIntersectionType([type, candidate]);
15464+
}
15465+
}
15466+
return type;
15467+
};
15468+
}
15469+
1544415470
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
1544515471
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
1544615472
if (!switchWitnesses.length) {
@@ -15458,7 +15484,7 @@ namespace ts {
1545815484
// that we don't have to worry about undefined
1545915485
// in the witness array.
1546015486
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
15461-
// The adjust clause start and end after removing the `default` statement.
15487+
// The adjusted clause start and end after removing the `default` statement.
1546215488
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
1546315489
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
1546415490
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
@@ -15468,6 +15494,9 @@ namespace ts {
1546815494
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
1546915495
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
1547015496
}
15497+
if (hasDefaultClause) {
15498+
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
15499+
}
1547115500
/*
1547215501
The implied type is the raw type suggested by a
1547315502
value being caught in this clause.
@@ -15496,26 +15525,11 @@ namespace ts {
1549615525
boolean. We know that number cannot be selected
1549715526
because it is caught in the first clause.
1549815527
*/
15499-
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
15500-
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
15501-
if (impliedType.flags & TypeFlags.Union) {
15502-
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
15503-
}
15504-
if (!(impliedType.flags & TypeFlags.Never)) {
15505-
if (isTypeSubtypeOf(impliedType, type)) {
15506-
return impliedType;
15507-
}
15508-
if (type.flags & TypeFlags.Instantiable) {
15509-
const constraint = getBaseConstraintOfType(type) || anyType;
15510-
if (isTypeSubtypeOf(impliedType, constraint)) {
15511-
return getIntersectionType([type, impliedType]);
15512-
}
15513-
}
15514-
}
15528+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts);
15529+
if (impliedType.flags & TypeFlags.Union) {
15530+
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
1551515531
}
15516-
return hasDefaultClause ?
15517-
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
15518-
getTypeWithFacts(type, switchFacts);
15532+
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
1551915533
}
1552015534

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