@@ -14282,6 +14282,23 @@ namespace ts {
14282
14282
return links.switchTypes;
14283
14283
}
14284
14284
14285
+ // Get the types from all cases in a switch on `typeof`. An
14286
+ // `undefined` element denotes an explicit `default` clause.
14287
+ function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] {
14288
+ const witnesses: (string | undefined)[] = [];
14289
+ for (const clause of switchStatement.caseBlock.clauses) {
14290
+ if (clause.kind === SyntaxKind.CaseClause) {
14291
+ if (clause.expression.kind === SyntaxKind.StringLiteral) {
14292
+ witnesses.push((clause.expression as StringLiteral).text);
14293
+ continue;
14294
+ }
14295
+ return emptyArray;
14296
+ }
14297
+ witnesses.push(/*explicitDefaultStatement*/ undefined);
14298
+ }
14299
+ return witnesses;
14300
+ }
14301
+
14285
14302
function eachTypeContainedIn(source: Type, types: Type[]) {
14286
14303
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
14287
14304
}
@@ -14704,6 +14721,9 @@ namespace ts {
14704
14721
expr as PropertyAccessExpression | ElementAccessExpression,
14705
14722
t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
14706
14723
}
14724
+ else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
14725
+ type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
14726
+ }
14707
14727
return createFlowType(type, isIncomplete(flowType));
14708
14728
}
14709
14729
@@ -15019,6 +15039,83 @@ namespace ts {
15019
15039
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
15020
15040
}
15021
15041
15042
+ function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
15043
+ const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
15044
+ if (!switchWitnesses.length) {
15045
+ return type;
15046
+ }
15047
+ // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
15048
+ const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
15049
+ const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
15050
+ let clauseWitnesses: string[];
15051
+ let switchFacts: TypeFacts;
15052
+ if (defaultCaseLocation > -1) {
15053
+ // We no longer need the undefined denoting an
15054
+ // explicit default case. Remove the undefined and
15055
+ // fix-up clauseStart and clauseEnd. This means
15056
+ // that we don't have to worry about undefined
15057
+ // in the witness array.
15058
+ const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
15059
+ // The adjust clause start and end after removing the `default` statement.
15060
+ const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
15061
+ const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
15062
+ clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
15063
+ switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
15064
+ }
15065
+ else {
15066
+ clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
15067
+ switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
15068
+ }
15069
+ /*
15070
+ The implied type is the raw type suggested by a
15071
+ value being caught in this clause.
15072
+
15073
+ When the clause contains a default case we ignore
15074
+ the implied type and try to narrow using any facts
15075
+ we can learn: see `switchFacts`.
15076
+
15077
+ Example:
15078
+ switch (typeof x) {
15079
+ case 'number':
15080
+ case 'string': break;
15081
+ default: break;
15082
+ case 'number':
15083
+ case 'boolean': break
15084
+ }
15085
+
15086
+ In the first clause (case `number` and `string`) the
15087
+ implied type is number | string.
15088
+
15089
+ In the default clause we de not compute an implied type.
15090
+
15091
+ In the third clause (case `number` and `boolean`)
15092
+ the naive implied type is number | boolean, however
15093
+ we use the type facts to narrow the implied type to
15094
+ boolean. We know that number cannot be selected
15095
+ because it is caught in the first clause.
15096
+ */
15097
+ if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
15098
+ let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
15099
+ if (impliedType.flags & TypeFlags.Union) {
15100
+ impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
15101
+ }
15102
+ if (!(impliedType.flags & TypeFlags.Never)) {
15103
+ if (isTypeSubtypeOf(impliedType, type)) {
15104
+ return impliedType;
15105
+ }
15106
+ if (type.flags & TypeFlags.Instantiable) {
15107
+ const constraint = getBaseConstraintOfType(type) || anyType;
15108
+ if (isTypeSubtypeOf(impliedType, constraint)) {
15109
+ return getIntersectionType([type, impliedType]);
15110
+ }
15111
+ }
15112
+ }
15113
+ }
15114
+ return hasDefaultClause ?
15115
+ filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
15116
+ getTypeWithFacts(type, switchFacts);
15117
+ }
15118
+
15022
15119
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
15023
15120
const left = getReferenceCandidate(expr.left);
15024
15121
if (!isMatchingReference(reference, left)) {
@@ -20572,10 +20669,62 @@ namespace ts {
20572
20669
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
20573
20670
}
20574
20671
20672
+ /**
20673
+ * Collect the TypeFacts learned from a typeof switch with
20674
+ * total clauses `witnesses`, and the active clause ranging
20675
+ * from `start` to `end`. Parameter `hasDefault` denotes
20676
+ * whether the active clause contains a default clause.
20677
+ */
20678
+ function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts {
20679
+ let facts: TypeFacts = TypeFacts.None;
20680
+ // When in the default we only collect inequality facts
20681
+ // because default is 'in theory' a set of infinite
20682
+ // equalities.
20683
+ if (hasDefault) {
20684
+ // Value is not equal to any types after the active clause.
20685
+ for (let i = end; i < witnesses.length; i++) {
20686
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
20687
+ }
20688
+ // Remove inequalities for types that appear in the
20689
+ // active clause because they appear before other
20690
+ // types collected so far.
20691
+ for (let i = start; i < end; i++) {
20692
+ facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
20693
+ }
20694
+ // Add inequalities for types before the active clause unconditionally.
20695
+ for (let i = 0; i < start; i++) {
20696
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
20697
+ }
20698
+ }
20699
+ // When in an active clause without default the set of
20700
+ // equalities is finite.
20701
+ else {
20702
+ // Add equalities for all types in the active clause.
20703
+ for (let i = start; i < end; i++) {
20704
+ facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
20705
+ }
20706
+ // Remove equalities for types that appear before the
20707
+ // active clause.
20708
+ for (let i = 0; i < start; i++) {
20709
+ facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
20710
+ }
20711
+ }
20712
+ return facts;
20713
+ }
20714
+
20575
20715
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
20576
20716
if (!node.possiblyExhaustive) {
20577
20717
return false;
20578
20718
}
20719
+ if (node.expression.kind === SyntaxKind.TypeOfExpression) {
20720
+ const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
20721
+ // This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined.
20722
+ const witnesses = <string[]>getSwitchClauseTypeOfWitnesses(node);
20723
+ // notEqualFacts states that the type of the switched value is not equal to every type in the switch.
20724
+ const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true);
20725
+ const type = getBaseConstraintOfType(operandType) || operandType;
20726
+ return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
20727
+ }
20579
20728
const type = getTypeOfExpression(node.expression);
20580
20729
if (!isLiteralType(type)) {
20581
20730
return false;
0 commit comments