Skip to content

Commit 3f103f5

Browse files
committed
Fix typeof switch narrowing
1 parent 83fe1ea commit 3f103f5

File tree

5 files changed

+556
-221
lines changed

5 files changed

+556
-221
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15261,14 +15261,19 @@ namespace ts {
1526115261
boolean. We know that number cannot be selected
1526215262
because it is caught in the first clause.
1526315263
*/
15264+
const isTypeUnknown = type.flags & TypeFlags.Unknown;
15265+
const getTypeFromName = (text: string) => text === "function" ? globalFunctionType :
15266+
(isTypeUnknown && text === "object") ? getUnionType([nonPrimitiveType, nullType]) :
15267+
(typeofTypesByName.get(text) || neverType);
1526415268
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
15265-
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
15269+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(getTypeFromName)), switchFacts);
1526615270
if (impliedType.flags & TypeFlags.Union) {
1526715271
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
1526815272
}
1526915273
if (!(impliedType.flags & TypeFlags.Never)) {
1527015274
if (isTypeSubtypeOf(impliedType, type)) {
15271-
return impliedType;
15275+
// Intersection to handle `string` being a subtype of `keyof T`
15276+
return isTypeAny(type) ? impliedType : getIntersectionType([type, impliedType]);
1527215277
}
1527315278
if (type.flags & TypeFlags.Instantiable) {
1527415279
const constraint = getBaseConstraintOfType(type) || anyType;

tests/baselines/reference/narrowingByTypeofInSwitch.js

Lines changed: 80 additions & 0 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
}
@@ -210,6 +214,31 @@ 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<T>(k: keyof T) {
231+
function assertKeyofT(k1: keyof T) { }
232+
switch (typeof k) {
233+
case 'number': assertNumber(k); assertKeyofT(k); return;
234+
case 'symbol': assertSymbol(k); assertKeyofT(k); return;
235+
case 'string': assertString(k); assertKeyofT(k); return;
236+
case 'boolean': assertNever(k);
237+
case 'function': assertNever(k);
238+
case 'object': assertNever(k);
239+
case 'undefined': assertNever(k); return;
240+
}
241+
}
213242

214243

215244
//// [narrowingByTypeofInSwitch.js]
@@ -234,6 +263,9 @@ function assertFunction(x) {
234263
function assertObject(x) {
235264
return x;
236265
}
266+
function assertObjectOrNull(x) {
267+
return x;
268+
}
237269
function assertUndefined(x) {
238270
return x;
239271
}
@@ -470,3 +502,51 @@ function fallThroughTest(x) {
470502
break;
471503
}
472504
}
505+
function unknownNarrowing(x) {
506+
switch (typeof x) {
507+
case 'number':
508+
assertNumber(x);
509+
return;
510+
case 'boolean':
511+
assertBoolean(x);
512+
return;
513+
case 'function':
514+
assertFunction(x);
515+
return;
516+
case 'symbol':
517+
assertSymbol(x);
518+
return;
519+
case 'object':
520+
assertObjectOrNull(x);
521+
return;
522+
case 'string':
523+
assertString(x);
524+
return;
525+
case 'undefined':
526+
assertUndefined(x);
527+
return;
528+
}
529+
}
530+
function keyofNarrowing(k) {
531+
function assertKeyofT(k1) { }
532+
switch (typeof k) {
533+
case 'number':
534+
assertNumber(k);
535+
assertKeyofT(k);
536+
return;
537+
case 'symbol':
538+
assertSymbol(k);
539+
assertKeyofT(k);
540+
return;
541+
case 'string':
542+
assertString(k);
543+
assertKeyofT(k);
544+
return;
545+
case 'boolean': assertNever(k);
546+
case 'function': assertNever(k);
547+
case 'object': assertNever(k);
548+
case 'undefined':
549+
assertNever(k);
550+
return;
551+
}
552+
}

0 commit comments

Comments
 (0)