Skip to content
Closed
54 changes: 34 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15215,6 +15215,32 @@ namespace ts {
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
}

function getTypeFromName(type: Type, text: string) {
switch (text) {
case "function":
return type.flags & TypeFlags.Any ? type : globalFunctionType;
case "object":
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
default:
return typeofTypesByName.get(text) || type;
}
}

function narrowTypeForTypeofSwitch(candidate: Type) {
return (type: Type) => {
if (isTypeSubtypeOf(candidate, type)) {
return candidate;
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(candidate, constraint)) {
return getIntersectionType([type, candidate]);
}
}
return type;
};
}

function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
if (!switchWitnesses.length) {
Expand All @@ -15232,7 +15258,7 @@ namespace ts {
// that we don't have to worry about undefined
// in the witness array.
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
// The adjust clause start and end after removing the `default` statement.
// The adjusted clause start and end after removing the `default` statement.
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
Expand All @@ -15242,6 +15268,9 @@ namespace ts {
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
}
if (hasDefaultClause) {
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
}
/*
The implied type is the raw type suggested by a
value being caught in this clause.
Expand Down Expand Up @@ -15270,26 +15299,11 @@ namespace ts {
boolean. We know that number cannot be selected
because it is caught in the first clause.
*/
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
if (impliedType.flags & TypeFlags.Union) {
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
}
if (!(impliedType.flags & TypeFlags.Never)) {
if (isTypeSubtypeOf(impliedType, type)) {
return impliedType;
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(impliedType, constraint)) {
return getIntersectionType([type, impliedType]);
}
}
}
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getTypeFromName(type, text))), switchFacts);
if (impliedType.flags & TypeFlags.Union) {
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
}
return hasDefaultClause ?
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
getTypeWithFacts(type, switchFacts);
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
}

function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
Expand Down
119 changes: 117 additions & 2 deletions tests/baselines/reference/narrowingByTypeofInSwitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ function assertObject(x: object) {
return x;
}

function assertObjectOrNull(x: object | null) {
return x;
}

function assertUndefined(x: undefined) {
return x;
}
Expand All @@ -35,11 +39,11 @@ function assertAll(x: Basic) {
return x;
}

function assertStringOrNumber(x: string | number) {
function assertStringOrNumber(x: string | number) {
return x;
}

function assertBooleanOrObject(x: boolean | object) {
function assertBooleanOrObject(x: boolean | object) {
return x;
}

Expand Down Expand Up @@ -210,6 +214,41 @@ function fallThroughTest(x: string | number | boolean | object) {
break;
}
}

function unknownNarrowing(x: unknown) {
switch (typeof x) {
case 'number': assertNumber(x); return;
case 'boolean': assertBoolean(x); return;
case 'function': assertFunction(x); return;
case 'symbol': assertSymbol(x); return;
case 'object': assertObjectOrNull(x); return;
case 'string': assertString(x); return;
case 'undefined': assertUndefined(x); return;
}
}

function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
function assertKeyofS(k1: keyof S) { }
switch (typeof k) {
case 'number': assertNumber(k); assertKeyofS(k); return;
case 'symbol': assertSymbol(k); assertKeyofS(k); return;
case 'string': assertString(k); assertKeyofS(k); return;
}
}

function narrowingNarrows(x: {} | undefined) {
switch (typeof x) {
case 'number': assertNumber(x); return;
case 'boolean': assertBoolean(x); return;
case 'function': assertFunction(x); return;
case 'symbol': assertSymbol(x); return;
case 'object': const _: {} = x; return;
case 'string': assertString(x); return;
case 'undefined': assertUndefined(x); return;
case 'number': assertNever(x); return;
default: const _y: {} = x; return;
}
}


//// [narrowingByTypeofInSwitch.js]
Expand All @@ -234,6 +273,9 @@ function assertFunction(x) {
function assertObject(x) {
return x;
}
function assertObjectOrNull(x) {
return x;
}
function assertUndefined(x) {
return x;
}
Expand Down Expand Up @@ -470,3 +512,76 @@ function fallThroughTest(x) {
break;
}
}
function unknownNarrowing(x) {
switch (typeof x) {
case 'number':
assertNumber(x);
return;
case 'boolean':
assertBoolean(x);
return;
case 'function':
assertFunction(x);
return;
case 'symbol':
assertSymbol(x);
return;
case 'object':
assertObjectOrNull(x);
return;
case 'string':
assertString(x);
return;
case 'undefined':
assertUndefined(x);
return;
}
}
function keyofNarrowing(k) {
function assertKeyofS(k1) { }
switch (typeof k) {
case 'number':
assertNumber(k);
assertKeyofS(k);
return;
case 'symbol':
assertSymbol(k);
assertKeyofS(k);
return;
case 'string':
assertString(k);
assertKeyofS(k);
return;
}
}
function narrowingNarrows(x) {
switch (typeof x) {
case 'number':
assertNumber(x);
return;
case 'boolean':
assertBoolean(x);
return;
case 'function':
assertFunction(x);
return;
case 'symbol':
assertSymbol(x);
return;
case 'object':
var _ = x;
return;
case 'string':
assertString(x);
return;
case 'undefined':
assertUndefined(x);
return;
case 'number':
assertNever(x);
return;
default:
var _y = x;
return;
}
}
Loading