Skip to content

Commit

Permalink
Narrow union members aswell as filter
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-williams committed Sep 25, 2018
1 parent 356bba1 commit b4344f7
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 25 deletions.
51 changes: 28 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15242,6 +15242,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 All @@ -15252,11 +15255,11 @@ namespace ts {

Example:
switch (typeof x) {
case 'number':
case 'string': break;
default: break;
case 'number':
case 'boolean': break
case 'number':
case 'string': break;
default: break;
case 'number':
case 'boolean': break
}

In the first clause (case `number` and `string`) the
Expand All @@ -15270,7 +15273,13 @@ namespace ts {
boolean. We know that number cannot be selected
because it is caught in the first clause.
*/
const getTypeFromName = (text: string) => {
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(getTypeFromName)), switchFacts);
if (impliedType.flags & TypeFlags.Union) {
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
}
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);

function getTypeFromName(text: string) {
switch (text) {
case "function":
return type.flags & TypeFlags.Any ? type : globalFunctionType;
Expand All @@ -15279,25 +15288,21 @@ namespace ts {
default:
return typeofTypesByName.get(text) || type;
}
};
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(getTypeFromName)), switchFacts);
if (impliedType.flags & TypeFlags.Union) {
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
}
if (isTypeSubtypeOf(impliedType, type)) {
return impliedType;
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(impliedType, constraint)) {
return getIntersectionType([type, impliedType]);
}
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;
};
}
return hasDefaultClause ?
filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
getTypeWithFacts(type, switchFacts);
}

function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
Expand Down
45 changes: 45 additions & 0 deletions tests/baselines/reference/narrowingByTypeofInSwitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,20 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
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 Down Expand Up @@ -540,3 +554,34 @@ function keyofNarrowing(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;
}
}
45 changes: 45 additions & 0 deletions tests/baselines/reference/narrowingByTypeofInSwitch.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,48 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
}
}

function narrowingNarrows(x: {} | undefined) {
>narrowingNarrows : Symbol(narrowingNarrows, Decl(narrowingByTypeofInSwitch.ts, 235, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

switch (typeof x) {
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'number': assertNumber(x); return;
>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'boolean': assertBoolean(x); return;
>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'function': assertFunction(x); return;
>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'symbol': assertSymbol(x); return;
>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'object': const _: {} = x; return;
>_ : Symbol(_, Decl(narrowingByTypeofInSwitch.ts, 243, 28))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'string': assertString(x); return;
>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'undefined': assertUndefined(x); return;
>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 30, 1))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

case 'number': assertNever(x); return;
>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))

default: const _y: {} = x; return;
>_y : Symbol(_y, Decl(narrowingByTypeofInSwitch.ts, 247, 22))
>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 237, 26))
}
}

65 changes: 63 additions & 2 deletions tests/baselines/reference/narrowingByTypeofInSwitch.types
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ function multipleGenericFuse<X extends L | number, Y extends R | number>(xy: X |

case 'number': return [xy]
>'number' : "number"
>[xy] : [X | Y]
>xy : X | Y
>[xy] : [(X & number) | (Y & number)]
>xy : (X & number) | (Y & number)
}
}

Expand Down Expand Up @@ -808,3 +808,64 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
}
}

function narrowingNarrows(x: {} | undefined) {
>narrowingNarrows : (x: {} | undefined) => void
>x : {} | undefined

switch (typeof x) {
>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : {} | undefined

case 'number': assertNumber(x); return;
>'number' : "number"
>assertNumber(x) : number
>assertNumber : (x: number) => number
>x : number

case 'boolean': assertBoolean(x); return;
>'boolean' : "boolean"
>assertBoolean(x) : boolean
>assertBoolean : (x: boolean) => boolean
>x : boolean

case 'function': assertFunction(x); return;
>'function' : "function"
>assertFunction(x) : Function
>assertFunction : (x: Function) => Function
>x : Function

case 'symbol': assertSymbol(x); return;
>'symbol' : "symbol"
>assertSymbol(x) : symbol
>assertSymbol : (x: symbol) => symbol
>x : symbol

case 'object': const _: {} = x; return;
>'object' : "object"
>_ : {}
>x : {}

case 'string': assertString(x); return;
>'string' : "string"
>assertString(x) : string
>assertString : (x: string) => string
>x : string

case 'undefined': assertUndefined(x); return;
>'undefined' : "undefined"
>assertUndefined(x) : undefined
>assertUndefined : (x: undefined) => undefined
>x : undefined

case 'number': assertNever(x); return;
>'number' : "number"
>assertNever(x) : never
>assertNever : (x: never) => never
>x : never

default: const _y: {} = x; return;
>_y : {}
>x : {}
}
}

14 changes: 14 additions & 0 deletions tests/cases/compiler/narrowingByTypeofInSwitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,17 @@ function keyofNarrowing<S extends { [K in keyof S]: string }>(k: keyof S) {
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;
}
}

0 comments on commit b4344f7

Please sign in to comment.