diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e6e3364840dc9..555c13f1f61e3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -737,6 +737,8 @@ namespace ts { return isNarrowingBinaryExpression(expr); case SyntaxKind.PrefixUnaryExpression: return (expr).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr).operand); + case SyntaxKind.TypeOfExpression: + return isNarrowingExpression((expr).expression); } return false; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f497ba82e47a8..ab2cc1f3b8519 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12840,6 +12840,21 @@ namespace ts { return links.switchTypes; } + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] { + const witnesses: (string | undefined)[] = []; + for (const clause of switchStatement.caseBlock.clauses) { + if (clause.kind === SyntaxKind.CaseClause) { + if (clause.expression.kind === SyntaxKind.StringLiteral) { + witnesses.push((clause.expression as StringLiteral).text); + continue; + } + return emptyArray; + } + witnesses.push(/*explicitDefaultStatement*/ undefined); + } + return witnesses; + } + function eachTypeContainedIn(source: Type, types: Type[]) { return source.flags & TypeFlags.Union ? !forEach((source).types, t => !contains(types, t)) : contains(types, source); } @@ -13253,6 +13268,9 @@ namespace ts { else if (isMatchingReferenceDiscriminant(expr, type)) { type = narrowTypeByDiscriminant(type, expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } return createFlowType(type, isIncomplete(flowType)); } @@ -13549,6 +13567,57 @@ namespace ts { return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); } + function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { + const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement); + if (!switchWitnesses.length) { + return type; + } + const clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd); + // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause + const hasDefaultClause = clauseStart === clauseEnd || contains(clauseWitnesses, /*explicitDefaultStatement*/ undefined); + const switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause); + // The implied type is the raw type suggested by a + // value being caught in this clause. + // - If there is a default the implied type is not used. + // - Otherwise, take the union of the types in the + // clause. We narrow the union using facts to remove + // types that appear multiple types and are + // unreachable. + // Example: + // + // switch (typeof x) { + // case 'number': + // case 'string': break; + // default: break; + // case 'number': + // case 'boolean': break + // } + // + // The implied type of the first clause number | string. + // The implied type of the second clause is string (but this doesn't get used). + // The implied type of the third clause is boolean (number has already be caught). + 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]); + } + } + } + } + return hasDefaultClause ? + filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) : + getTypeWithFacts(type, switchFacts); + } + function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { const left = getReferenceCandidate(expr.left); if (!isMatchingReference(reference, left)) { @@ -18944,10 +19013,60 @@ namespace ts { : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } + /** + * Collect the TypeFacts learned from a typeof switch with + * total clauses `witnesses`, and the active clause ranging + * from `start` to `end`. Parameter `hasDefault` denotes + * whether the active clause contains a default clause. + */ + function getFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[], hasDefault: boolean): TypeFacts { + let facts: TypeFacts = TypeFacts.None; + // When in the default we only collect inequality facts + // because default is 'in theory' a set of infinite + // equalities. + if (hasDefault) { + // Value is not equal to any types after the active clause. + for (let i = end; i < witnesses.length; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + // Remove inequalities for types that appear in the + // active clause because they appear before other + // types collected so far. + for (let i = start; i < end; i++) { + facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); + } + // Add inequalities for types before the active clause unconditionally. + for (let i = 0; i < start; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + } + // When in an active clause without default the set of + // equalities is finite. + else { + // Add equalities for all types in the active clause. + for (let i = start; i < end; i++) { + facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; + } + // Remove equalities for types that appear before the + // active clause. + for (let i = 0; i < start; i++) { + facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); + } + } + return facts; + } + function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { if (!node.possiblyExhaustive) { return false; } + if (node.expression.kind === SyntaxKind.TypeOfExpression) { + const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); + // Type is not equal to every type in the switch. + const notEqualFacts = getFactsFromTypeofSwitch(0, 0, getSwitchClauseTypeOfWitnesses(node), /*hasDefault*/ true); + const type = getBaseConstraintOfType(operandType) || operandType; + return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); + } const type = getTypeOfExpression(node.expression); if (!isLiteralType(type)) { return false; diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.js b/tests/baselines/reference/narrowingByTypeofInSwitch.js new file mode 100644 index 0000000000000..2cf4b9027c2c8 --- /dev/null +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.js @@ -0,0 +1,427 @@ +//// [narrowingByTypeofInSwitch.ts] +function assertNever(x: never) { + return x; +} + +function assertNumber(x: number) { + return x; +} + +function assertBoolean(x: boolean) { + return x; +} + +function assertString(x: string) { + return x; +} + +function assertSymbol(x: symbol) { + return x; +} + +function assertFunction(x: Function) { + return x; +} + +function assertObject(x: object) { + return x; +} + +function assertUndefined(x: undefined) { + return x; +} + +function assertAll(x: Basic) { + return x; +} + +type Basic = number | boolean | string | symbol | object | Function | undefined; + +function testUnion(x: Basic) { + 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': assertObject(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertNever(x); +} + +function testExtendsUnion(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + case 'object': assertAll(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertAll(x); +} + +function testAny(x: any) { + 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': assertObject(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertAll(x); // is any +} + +function a1(x: string | object | undefined) { + return x; +} + +function testUnionExplicitDefault(x: Basic) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertFunction(x); return; + case 'symbol': assertSymbol(x); return; + default: a1(x); return; + } +} + +function testUnionImplicitDefault(x: Basic) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertFunction(x); return; + case 'symbol': assertSymbol(x); return; + } + return a1(x); +} + +function testExtendsExplicitDefault(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + default: assertAll(x); return; + + } +} + +function testExtendsImplicitDefault(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + } + return assertAll(x); +} + +type L = (x: number) => string; +type R = { x: string, y: number } + +function exhaustiveChecks(x: number | string | L | R): string { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return x(42); + case 'object': return x.x; + } +} + +function exhaustiveChecksGenerics(x: T): string { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return (x as L)(42); // Can't narrow generic + case 'object': return (x as R).x; // Can't narrow generic + } +} + +function multipleGeneric(xy: X | Y): [X, string] | [Y, number] { + switch (typeof xy) { + case 'function': return [xy, xy(42)]; + case 'object': return [xy, xy.y]; + default: return assertNever(xy); + } +} + +function multipleGenericFuse(xy: X | Y): [X, number] | [Y, string] | [(X | Y)] { + switch (typeof xy) { + case 'function': return [xy, 1]; + case 'object': return [xy, 'two']; + case 'number': return [xy] + } +} + +function multipleGenericExhaustive(xy: X | Y): [X, string] | [Y, number] { + switch (typeof xy) { + case 'object': return [xy, xy.y]; + case 'function': return [xy, xy(42)]; + } +} + +function switchOrdering(x: string | number | boolean) { + switch (typeof x) { + case 'string': return assertString(x); + case 'number': return assertNumber(x); + case 'boolean': return assertBoolean(x); + case 'number': return assertNever(x); + } +} + +function switchOrderingWithDefault(x: string | number | boolean) { + function local(y: string | number | boolean) { + return x; + } + switch (typeof x) { + case 'string': + case 'number': + default: return local(x) + case 'string': return assertNever(x); + case 'number': return assertNever(x); + } +} + + +//// [narrowingByTypeofInSwitch.js] +function assertNever(x) { + return x; +} +function assertNumber(x) { + return x; +} +function assertBoolean(x) { + return x; +} +function assertString(x) { + return x; +} +function assertSymbol(x) { + return x; +} +function assertFunction(x) { + return x; +} +function assertObject(x) { + return x; +} +function assertUndefined(x) { + return x; +} +function assertAll(x) { + return x; +} +function testUnion(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': + assertObject(x); + return; + case 'string': + assertString(x); + return; + case 'undefined': + assertUndefined(x); + return; + } + assertNever(x); +} +function testExtendsUnion(x) { + switch (typeof x) { + case 'number': + assertNumber(x); + return; + case 'boolean': + assertBoolean(x); + return; + case 'function': + assertAll(x); + return; + case 'symbol': + assertSymbol(x); + return; + case 'object': + assertAll(x); + return; + case 'string': + assertString(x); + return; + case 'undefined': + assertUndefined(x); + return; + } + assertAll(x); +} +function testAny(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': + assertObject(x); + return; + case 'string': + assertString(x); + return; + case 'undefined': + assertUndefined(x); + return; + } + assertAll(x); // is any +} +function a1(x) { + return x; +} +function testUnionExplicitDefault(x) { + switch (typeof x) { + case 'number': + assertNumber(x); + return; + case 'boolean': + assertBoolean(x); + return; + case 'function': + assertFunction(x); + return; + case 'symbol': + assertSymbol(x); + return; + default: + a1(x); + return; + } +} +function testUnionImplicitDefault(x) { + switch (typeof x) { + case 'number': + assertNumber(x); + return; + case 'boolean': + assertBoolean(x); + return; + case 'function': + assertFunction(x); + return; + case 'symbol': + assertSymbol(x); + return; + } + return a1(x); +} +function testExtendsExplicitDefault(x) { + switch (typeof x) { + case 'number': + assertNumber(x); + return; + case 'boolean': + assertBoolean(x); + return; + case 'function': + assertAll(x); + return; + case 'symbol': + assertSymbol(x); + return; + default: + assertAll(x); + return; + } +} +function testExtendsImplicitDefault(x) { + switch (typeof x) { + case 'number': + assertNumber(x); + return; + case 'boolean': + assertBoolean(x); + return; + case 'function': + assertAll(x); + return; + case 'symbol': + assertSymbol(x); + return; + } + return assertAll(x); +} +function exhaustiveChecks(x) { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return x(42); + case 'object': return x.x; + } +} +function exhaustiveChecksGenerics(x) { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return x(42); // Can't narrow generic + case 'object': return x.x; // Can't narrow generic + } +} +function multipleGeneric(xy) { + switch (typeof xy) { + case 'function': return [xy, xy(42)]; + case 'object': return [xy, xy.y]; + default: return assertNever(xy); + } +} +function multipleGenericFuse(xy) { + switch (typeof xy) { + case 'function': return [xy, 1]; + case 'object': return [xy, 'two']; + case 'number': return [xy]; + } +} +function multipleGenericExhaustive(xy) { + switch (typeof xy) { + case 'object': return [xy, xy.y]; + case 'function': return [xy, xy(42)]; + } +} +function switchOrdering(x) { + switch (typeof x) { + case 'string': return assertString(x); + case 'number': return assertNumber(x); + case 'boolean': return assertBoolean(x); + case 'number': return assertNever(x); + } +} +function switchOrderingWithDefault(x) { + function local(y) { + return x; + } + switch (typeof x) { + case 'string': + case 'number': + default: return local(x); + case 'string': return assertNever(x); + case 'number': return assertNever(x); + } +} diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.symbols b/tests/baselines/reference/narrowingByTypeofInSwitch.symbols new file mode 100644 index 0000000000000..2d1bd06baba98 --- /dev/null +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.symbols @@ -0,0 +1,542 @@ +=== tests/cases/compiler/narrowingByTypeofInSwitch.ts === +function assertNever(x: never) { +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 0, 21)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 0, 21)) +} + +function assertNumber(x: number) { +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 4, 22)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 4, 22)) +} + +function assertBoolean(x: boolean) { +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 8, 23)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 8, 23)) +} + +function assertString(x: string) { +>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 12, 22)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 12, 22)) +} + +function assertSymbol(x: symbol) { +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 16, 22)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 16, 22)) +} + +function assertFunction(x: Function) { +>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 20, 24)) +>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 20, 24)) +} + +function assertObject(x: object) { +>assertObject : Symbol(assertObject, Decl(narrowingByTypeofInSwitch.ts, 22, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 24, 22)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 24, 22)) +} + +function assertUndefined(x: undefined) { +>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 26, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 28, 25)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 28, 25)) +} + +function assertAll(x: Basic) { +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 32, 19)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 32, 19)) +} + +type Basic = number | boolean | string | symbol | object | Function | undefined; +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) +>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +function testUnion(x: Basic) { +>testUnion : Symbol(testUnion, Decl(narrowingByTypeofInSwitch.ts, 36, 80)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'function': assertFunction(x); return; +>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'object': assertObject(x); return; +>assertObject : Symbol(assertObject, Decl(narrowingByTypeofInSwitch.ts, 22, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'string': assertString(x); return; +>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + + case 'undefined': assertUndefined(x); return; +>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 26, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) + } + assertNever(x); +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 38, 19)) +} + +function testExtendsUnion(x: T) { +>testExtendsUnion : Symbol(testExtendsUnion, Decl(narrowingByTypeofInSwitch.ts, 49, 1)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 51, 26)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 51, 26)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'function': assertAll(x); return; +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'object': assertAll(x); return; +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'string': assertString(x); return; +>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + + case 'undefined': assertUndefined(x); return; +>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 26, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) + } + assertAll(x); +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 51, 43)) +} + +function testAny(x: any) { +>testAny : Symbol(testAny, Decl(narrowingByTypeofInSwitch.ts, 62, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'function': assertFunction(x); return; +>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'object': assertObject(x); return; +>assertObject : Symbol(assertObject, Decl(narrowingByTypeofInSwitch.ts, 22, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'string': assertString(x); return; +>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + + case 'undefined': assertUndefined(x); return; +>assertUndefined : Symbol(assertUndefined, Decl(narrowingByTypeofInSwitch.ts, 26, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) + } + assertAll(x); // is any +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 64, 17)) +} + +function a1(x: string | object | undefined) { +>a1 : Symbol(a1, Decl(narrowingByTypeofInSwitch.ts, 75, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 77, 12)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 77, 12)) +} + +function testUnionExplicitDefault(x: Basic) { +>testUnionExplicitDefault : Symbol(testUnionExplicitDefault, Decl(narrowingByTypeofInSwitch.ts, 79, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + + case 'function': assertFunction(x); return; +>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + + default: a1(x); return; +>a1 : Symbol(a1, Decl(narrowingByTypeofInSwitch.ts, 75, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 81, 34)) + } +} + +function testUnionImplicitDefault(x: Basic) { +>testUnionImplicitDefault : Symbol(testUnionImplicitDefault, Decl(narrowingByTypeofInSwitch.ts, 89, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) + + case 'function': assertFunction(x); return; +>assertFunction : Symbol(assertFunction, Decl(narrowingByTypeofInSwitch.ts, 18, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) + } + return a1(x); +>a1 : Symbol(a1, Decl(narrowingByTypeofInSwitch.ts, 75, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 91, 34)) +} + +function testExtendsExplicitDefault(x: T) { +>testExtendsExplicitDefault : Symbol(testExtendsExplicitDefault, Decl(narrowingByTypeofInSwitch.ts, 99, 1)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 101, 36)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 101, 36)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + case 'function': assertAll(x); return; +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + default: assertAll(x); return; +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 101, 53)) + + } +} + +function testExtendsImplicitDefault(x: T) { +>testExtendsImplicitDefault : Symbol(testExtendsImplicitDefault, Decl(narrowingByTypeofInSwitch.ts, 110, 1)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 112, 36)) +>Basic : Symbol(Basic, Decl(narrowingByTypeofInSwitch.ts, 34, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 112, 36)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) + + case 'number': assertNumber(x); return; +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) + + case 'boolean': assertBoolean(x); return; +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) + + case 'function': assertAll(x); return; +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) + + case 'symbol': assertSymbol(x); return; +>assertSymbol : Symbol(assertSymbol, Decl(narrowingByTypeofInSwitch.ts, 14, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) + } + return assertAll(x); +>assertAll : Symbol(assertAll, Decl(narrowingByTypeofInSwitch.ts, 30, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 112, 53)) +} + +type L = (x: number) => string; +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 122, 10)) + +type R = { x: string, y: number } +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 123, 10)) +>y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 123, 21)) + +function exhaustiveChecks(x: number | string | L | R): string { +>exhaustiveChecks : Symbol(exhaustiveChecks, Decl(narrowingByTypeofInSwitch.ts, 123, 33)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) + + case 'number': return x.toString(2); +>x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) +>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) + + case 'string': return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) + + case 'function': return x(42); +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) + + case 'object': return x.x; +>x.x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 123, 10)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 125, 26)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 123, 10)) + } +} + +function exhaustiveChecksGenerics(x: T): string { +>exhaustiveChecksGenerics : Symbol(exhaustiveChecksGenerics, Decl(narrowingByTypeofInSwitch.ts, 132, 1)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 134, 34)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) +>T : Symbol(T, Decl(narrowingByTypeofInSwitch.ts, 134, 34)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) + + case 'number': return x.toString(2); +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --) ... and 2 more) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --) ... and 2 more) + + case 'string': return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) + + case 'function': return (x as L)(42); // Can't narrow generic +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) + + case 'object': return (x as R).x; // Can't narrow generic +>(x as R).x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 123, 10)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 134, 69)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 123, 10)) + } +} + +function multipleGeneric(xy: X | Y): [X, string] | [Y, number] { +>multipleGeneric : Symbol(multipleGeneric, Decl(narrowingByTypeofInSwitch.ts, 141, 1)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 143, 25)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 143, 37)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 143, 25)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 143, 37)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 143, 25)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 143, 37)) + + switch (typeof xy) { +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) + + case 'function': return [xy, xy(42)]; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) + + case 'object': return [xy, xy.y]; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) +>xy.y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 123, 21)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) +>y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 123, 21)) + + default: return assertNever(xy); +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 143, 51)) + } +} + +function multipleGenericFuse(xy: X | Y): [X, number] | [Y, string] | [(X | Y)] { +>multipleGenericFuse : Symbol(multipleGenericFuse, Decl(narrowingByTypeofInSwitch.ts, 149, 1)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 151, 29)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 151, 50)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 151, 73)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 151, 29)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 151, 50)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 151, 29)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 151, 50)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 151, 29)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 151, 50)) + + switch (typeof xy) { +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 151, 73)) + + case 'function': return [xy, 1]; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 151, 73)) + + case 'object': return [xy, 'two']; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 151, 73)) + + case 'number': return [xy] +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 151, 73)) + } +} + +function multipleGenericExhaustive(xy: X | Y): [X, string] | [Y, number] { +>multipleGenericExhaustive : Symbol(multipleGenericExhaustive, Decl(narrowingByTypeofInSwitch.ts, 157, 1)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 159, 35)) +>L : Symbol(L, Decl(narrowingByTypeofInSwitch.ts, 120, 1)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 159, 47)) +>R : Symbol(R, Decl(narrowingByTypeofInSwitch.ts, 122, 31)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 159, 35)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 159, 47)) +>X : Symbol(X, Decl(narrowingByTypeofInSwitch.ts, 159, 35)) +>Y : Symbol(Y, Decl(narrowingByTypeofInSwitch.ts, 159, 47)) + + switch (typeof xy) { +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) + + case 'object': return [xy, xy.y]; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) +>xy.y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 123, 21)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) +>y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 123, 21)) + + case 'function': return [xy, xy(42)]; +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) +>xy : Symbol(xy, Decl(narrowingByTypeofInSwitch.ts, 159, 61)) + } +} + +function switchOrdering(x: string | number | boolean) { +>switchOrdering : Symbol(switchOrdering, Decl(narrowingByTypeofInSwitch.ts, 164, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + + case 'string': return assertString(x); +>assertString : Symbol(assertString, Decl(narrowingByTypeofInSwitch.ts, 10, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + + case 'number': return assertNumber(x); +>assertNumber : Symbol(assertNumber, Decl(narrowingByTypeofInSwitch.ts, 2, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + + case 'boolean': return assertBoolean(x); +>assertBoolean : Symbol(assertBoolean, Decl(narrowingByTypeofInSwitch.ts, 6, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + + case 'number': return assertNever(x); +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 166, 24)) + } +} + +function switchOrderingWithDefault(x: string | number | boolean) { +>switchOrderingWithDefault : Symbol(switchOrderingWithDefault, Decl(narrowingByTypeofInSwitch.ts, 173, 1)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + + function local(y: string | number | boolean) { +>local : Symbol(local, Decl(narrowingByTypeofInSwitch.ts, 175, 66)) +>y : Symbol(y, Decl(narrowingByTypeofInSwitch.ts, 176, 19)) + + return x; +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + } + switch (typeof x) { +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + + case 'string': + case 'number': + default: return local(x) +>local : Symbol(local, Decl(narrowingByTypeofInSwitch.ts, 175, 66)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + + case 'string': return assertNever(x); +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + + case 'number': return assertNever(x); +>assertNever : Symbol(assertNever, Decl(narrowingByTypeofInSwitch.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 175, 35)) + } +} + diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.types b/tests/baselines/reference/narrowingByTypeofInSwitch.types new file mode 100644 index 0000000000000..785bdd23609d5 --- /dev/null +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.types @@ -0,0 +1,695 @@ +=== tests/cases/compiler/narrowingByTypeofInSwitch.ts === +function assertNever(x: never) { +>assertNever : (x: never) => never +>x : never + + return x; +>x : never +} + +function assertNumber(x: number) { +>assertNumber : (x: number) => number +>x : number + + return x; +>x : number +} + +function assertBoolean(x: boolean) { +>assertBoolean : (x: boolean) => boolean +>x : boolean + + return x; +>x : boolean +} + +function assertString(x: string) { +>assertString : (x: string) => string +>x : string + + return x; +>x : string +} + +function assertSymbol(x: symbol) { +>assertSymbol : (x: symbol) => symbol +>x : symbol + + return x; +>x : symbol +} + +function assertFunction(x: Function) { +>assertFunction : (x: Function) => Function +>x : Function +>Function : Function + + return x; +>x : Function +} + +function assertObject(x: object) { +>assertObject : (x: object) => object +>x : object + + return x; +>x : object +} + +function assertUndefined(x: undefined) { +>assertUndefined : (x: undefined) => undefined +>x : undefined + + return x; +>x : undefined +} + +function assertAll(x: Basic) { +>assertAll : (x: Basic) => Basic +>x : Basic +>Basic : Basic + + return x; +>x : Basic +} + +type Basic = number | boolean | string | symbol | object | Function | undefined; +>Basic : Basic +>Function : Function + +function testUnion(x: Basic) { +>testUnion : (x: Basic) => void +>x : Basic +>Basic : Basic + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : Basic + + 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': assertObject(x); return; +>'object' : "object" +>assertObject(x) : object +>assertObject : (x: object) => object +>x : object + + 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 + } + assertNever(x); +>assertNever(x) : never +>assertNever : (x: never) => never +>x : never +} + +function testExtendsUnion(x: T) { +>testExtendsUnion : (x: T) => void +>T : T +>Basic : Basic +>x : T +>T : T + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T + + case 'number': assertNumber(x); return; +>'number' : "number" +>assertNumber(x) : number +>assertNumber : (x: number) => number +>x : T & number + + case 'boolean': assertBoolean(x); return; +>'boolean' : "boolean" +>assertBoolean(x) : boolean +>assertBoolean : (x: boolean) => boolean +>x : (T & true) | (T & false) + + case 'function': assertAll(x); return; +>'function' : "function" +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T + + case 'symbol': assertSymbol(x); return; +>'symbol' : "symbol" +>assertSymbol(x) : symbol +>assertSymbol : (x: symbol) => symbol +>x : T & symbol + + case 'object': assertAll(x); return; +>'object' : "object" +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T + + case 'string': assertString(x); return; +>'string' : "string" +>assertString(x) : string +>assertString : (x: string) => string +>x : T & string + + case 'undefined': assertUndefined(x); return; +>'undefined' : "undefined" +>assertUndefined(x) : undefined +>assertUndefined : (x: undefined) => undefined +>x : T & undefined + } + assertAll(x); +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T +} + +function testAny(x: any) { +>testAny : (x: any) => void +>x : any + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : any + + 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 : any + + case 'symbol': assertSymbol(x); return; +>'symbol' : "symbol" +>assertSymbol(x) : symbol +>assertSymbol : (x: symbol) => symbol +>x : symbol + + case 'object': assertObject(x); return; +>'object' : "object" +>assertObject(x) : object +>assertObject : (x: object) => object +>x : any + + 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 + } + assertAll(x); // is any +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : any +} + +function a1(x: string | object | undefined) { +>a1 : (x: string | object | undefined) => string | object | undefined +>x : string | object | undefined + + return x; +>x : string | object | undefined +} + +function testUnionExplicitDefault(x: Basic) { +>testUnionExplicitDefault : (x: Basic) => void +>x : Basic +>Basic : Basic + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : Basic + + 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 + + default: a1(x); return; +>a1(x) : string | object | undefined +>a1 : (x: string | object | undefined) => string | object | undefined +>x : string | object | undefined + } +} + +function testUnionImplicitDefault(x: Basic) { +>testUnionImplicitDefault : (x: Basic) => string | object | undefined +>x : Basic +>Basic : Basic + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : Basic + + 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 + } + return a1(x); +>a1(x) : string | object | undefined +>a1 : (x: string | object | undefined) => string | object | undefined +>x : string | object | undefined +} + +function testExtendsExplicitDefault(x: T) { +>testExtendsExplicitDefault : (x: T) => void +>T : T +>Basic : Basic +>x : T +>T : T + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T + + case 'number': assertNumber(x); return; +>'number' : "number" +>assertNumber(x) : number +>assertNumber : (x: number) => number +>x : T & number + + case 'boolean': assertBoolean(x); return; +>'boolean' : "boolean" +>assertBoolean(x) : boolean +>assertBoolean : (x: boolean) => boolean +>x : (T & true) | (T & false) + + case 'function': assertAll(x); return; +>'function' : "function" +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T + + case 'symbol': assertSymbol(x); return; +>'symbol' : "symbol" +>assertSymbol(x) : symbol +>assertSymbol : (x: symbol) => symbol +>x : T & symbol + + default: assertAll(x); return; +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T + + } +} + +function testExtendsImplicitDefault(x: T) { +>testExtendsImplicitDefault : (x: T) => string | number | boolean | symbol | object | undefined +>T : T +>Basic : Basic +>x : T +>T : T + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T + + case 'number': assertNumber(x); return; +>'number' : "number" +>assertNumber(x) : number +>assertNumber : (x: number) => number +>x : T & number + + case 'boolean': assertBoolean(x); return; +>'boolean' : "boolean" +>assertBoolean(x) : boolean +>assertBoolean : (x: boolean) => boolean +>x : (T & true) | (T & false) + + case 'function': assertAll(x); return; +>'function' : "function" +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T + + case 'symbol': assertSymbol(x); return; +>'symbol' : "symbol" +>assertSymbol(x) : symbol +>assertSymbol : (x: symbol) => symbol +>x : T & symbol + } + return assertAll(x); +>assertAll(x) : Basic +>assertAll : (x: Basic) => Basic +>x : T +} + +type L = (x: number) => string; +>L : L +>x : number + +type R = { x: string, y: number } +>R : R +>x : string +>y : number + +function exhaustiveChecks(x: number | string | L | R): string { +>exhaustiveChecks : (x: string | number | R | L) => string +>x : string | number | R | L +>L : L +>R : R + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | R | L + + case 'number': return x.toString(2); +>'number' : "number" +>x.toString(2) : string +>x.toString : (radix?: number | undefined) => string +>x : number +>toString : (radix?: number | undefined) => string +>2 : 2 + + case 'string': return x; +>'string' : "string" +>x : string + + case 'function': return x(42); +>'function' : "function" +>x(42) : string +>x : L +>42 : 42 + + case 'object': return x.x; +>'object' : "object" +>x.x : string +>x : R +>x : string + } +} + +function exhaustiveChecksGenerics(x: T): string { +>exhaustiveChecksGenerics : (x: T) => string +>T : T +>L : L +>R : R +>x : T +>T : T + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T + + case 'number': return x.toString(2); +>'number' : "number" +>x.toString(2) : string +>x.toString : ((radix?: number | undefined) => string) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) +>x : T & number +>toString : ((radix?: number | undefined) => string) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) +>2 : 2 + + case 'string': return x; +>'string' : "string" +>x : T & string + + case 'function': return (x as L)(42); // Can't narrow generic +>'function' : "function" +>(x as L)(42) : string +>(x as L) : L +>x as L : L +>x : T +>L : L +>42 : 42 + + case 'object': return (x as R).x; // Can't narrow generic +>'object' : "object" +>(x as R).x : string +>(x as R) : R +>x as R : R +>x : T +>R : R +>x : string + } +} + +function multipleGeneric(xy: X | Y): [X, string] | [Y, number] { +>multipleGeneric : (xy: X | Y) => [X, string] | [Y, number] +>X : X +>L : L +>Y : Y +>R : R +>xy : X | Y +>X : X +>Y : Y +>X : X +>Y : Y + + switch (typeof xy) { +>typeof xy : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>xy : X | Y + + case 'function': return [xy, xy(42)]; +>'function' : "function" +>[xy, xy(42)] : [X, string] +>xy : X +>xy(42) : string +>xy : X +>42 : 42 + + case 'object': return [xy, xy.y]; +>'object' : "object" +>[xy, xy.y] : [Y, number] +>xy : Y +>xy.y : number +>xy : Y +>y : number + + default: return assertNever(xy); +>assertNever(xy) : never +>assertNever : (x: never) => never +>xy : never + } +} + +function multipleGenericFuse(xy: X | Y): [X, number] | [Y, string] | [(X | Y)] { +>multipleGenericFuse : (xy: X | Y) => [X, number] | [Y, string] | [X | Y] +>X : X +>L : L +>Y : Y +>R : R +>xy : X | Y +>X : X +>Y : Y +>X : X +>Y : Y +>X : X +>Y : Y + + switch (typeof xy) { +>typeof xy : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>xy : X | Y + + case 'function': return [xy, 1]; +>'function' : "function" +>[xy, 1] : [X, number] +>xy : X +>1 : 1 + + case 'object': return [xy, 'two']; +>'object' : "object" +>[xy, 'two'] : [Y, string] +>xy : Y +>'two' : "two" + + case 'number': return [xy] +>'number' : "number" +>[xy] : [X | Y] +>xy : X | Y + } +} + +function multipleGenericExhaustive(xy: X | Y): [X, string] | [Y, number] { +>multipleGenericExhaustive : (xy: X | Y) => [X, string] | [Y, number] +>X : X +>L : L +>Y : Y +>R : R +>xy : X | Y +>X : X +>Y : Y +>X : X +>Y : Y + + switch (typeof xy) { +>typeof xy : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>xy : X | Y + + case 'object': return [xy, xy.y]; +>'object' : "object" +>[xy, xy.y] : [Y, number] +>xy : Y +>xy.y : number +>xy : Y +>y : number + + case 'function': return [xy, xy(42)]; +>'function' : "function" +>[xy, xy(42)] : [X, string] +>xy : X +>xy(42) : string +>xy : X +>42 : 42 + } +} + +function switchOrdering(x: string | number | boolean) { +>switchOrdering : (x: string | number | boolean) => string | number | boolean +>x : string | number | boolean + + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean + + case 'string': return assertString(x); +>'string' : "string" +>assertString(x) : string +>assertString : (x: string) => string +>x : string + + case 'number': return assertNumber(x); +>'number' : "number" +>assertNumber(x) : number +>assertNumber : (x: number) => number +>x : number + + case 'boolean': return assertBoolean(x); +>'boolean' : "boolean" +>assertBoolean(x) : boolean +>assertBoolean : (x: boolean) => boolean +>x : boolean + + case 'number': return assertNever(x); +>'number' : "number" +>assertNever(x) : never +>assertNever : (x: never) => never +>x : never + } +} + +function switchOrderingWithDefault(x: string | number | boolean) { +>switchOrderingWithDefault : (x: string | number | boolean) => string | number | boolean +>x : string | number | boolean + + function local(y: string | number | boolean) { +>local : (y: string | number | boolean) => string | number | boolean +>y : string | number | boolean + + return x; +>x : string | number | boolean + } + switch (typeof x) { +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean + + case 'string': +>'string' : "string" + + case 'number': +>'number' : "number" + + default: return local(x) +>local(x) : string | number | boolean +>local : (y: string | number | boolean) => string | number | boolean +>x : string | number | boolean + + case 'string': return assertNever(x); +>'string' : "string" +>assertNever(x) : never +>assertNever : (x: never) => never +>x : never + + case 'number': return assertNever(x); +>'number' : "number" +>assertNever(x) : never +>assertNever : (x: never) => never +>x : never + } +} + diff --git a/tests/cases/compiler/narrowingByTypeofInSwitch.ts b/tests/cases/compiler/narrowingByTypeofInSwitch.ts new file mode 100644 index 0000000000000..aadd351b87e00 --- /dev/null +++ b/tests/cases/compiler/narrowingByTypeofInSwitch.ts @@ -0,0 +1,190 @@ +// @strictNullChecks: true +// @strictFunctionTypes: true + +function assertNever(x: never) { + return x; +} + +function assertNumber(x: number) { + return x; +} + +function assertBoolean(x: boolean) { + return x; +} + +function assertString(x: string) { + return x; +} + +function assertSymbol(x: symbol) { + return x; +} + +function assertFunction(x: Function) { + return x; +} + +function assertObject(x: object) { + return x; +} + +function assertUndefined(x: undefined) { + return x; +} + +function assertAll(x: Basic) { + return x; +} + +type Basic = number | boolean | string | symbol | object | Function | undefined; + +function testUnion(x: Basic) { + 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': assertObject(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertNever(x); +} + +function testExtendsUnion(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + case 'object': assertAll(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertAll(x); +} + +function testAny(x: any) { + 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': assertObject(x); return; + case 'string': assertString(x); return; + case 'undefined': assertUndefined(x); return; + } + assertAll(x); // is any +} + +function a1(x: string | object | undefined) { + return x; +} + +function testUnionExplicitDefault(x: Basic) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertFunction(x); return; + case 'symbol': assertSymbol(x); return; + default: a1(x); return; + } +} + +function testUnionImplicitDefault(x: Basic) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertFunction(x); return; + case 'symbol': assertSymbol(x); return; + } + return a1(x); +} + +function testExtendsExplicitDefault(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + default: assertAll(x); return; + + } +} + +function testExtendsImplicitDefault(x: T) { + switch (typeof x) { + case 'number': assertNumber(x); return; + case 'boolean': assertBoolean(x); return; + case 'function': assertAll(x); return; + case 'symbol': assertSymbol(x); return; + } + return assertAll(x); +} + +type L = (x: number) => string; +type R = { x: string, y: number } + +function exhaustiveChecks(x: number | string | L | R): string { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return x(42); + case 'object': return x.x; + } +} + +function exhaustiveChecksGenerics(x: T): string { + switch (typeof x) { + case 'number': return x.toString(2); + case 'string': return x; + case 'function': return (x as L)(42); // Can't narrow generic + case 'object': return (x as R).x; // Can't narrow generic + } +} + +function multipleGeneric(xy: X | Y): [X, string] | [Y, number] { + switch (typeof xy) { + case 'function': return [xy, xy(42)]; + case 'object': return [xy, xy.y]; + default: return assertNever(xy); + } +} + +function multipleGenericFuse(xy: X | Y): [X, number] | [Y, string] | [(X | Y)] { + switch (typeof xy) { + case 'function': return [xy, 1]; + case 'object': return [xy, 'two']; + case 'number': return [xy] + } +} + +function multipleGenericExhaustive(xy: X | Y): [X, string] | [Y, number] { + switch (typeof xy) { + case 'object': return [xy, xy.y]; + case 'function': return [xy, xy(42)]; + } +} + +function switchOrdering(x: string | number | boolean) { + switch (typeof x) { + case 'string': return assertString(x); + case 'number': return assertNumber(x); + case 'boolean': return assertBoolean(x); + case 'number': return assertNever(x); + } +} + +function switchOrderingWithDefault(x: string | number | boolean) { + function local(y: string | number | boolean) { + return x; + } + switch (typeof x) { + case 'string': + case 'number': + default: return local(x) + case 'string': return assertNever(x); + case 'number': return assertNever(x); + } +}