@@ -12840,6 +12840,21 @@ namespace ts {
12840
12840
return links.switchTypes;
12841
12841
}
12842
12842
12843
+ function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] {
12844
+ const witnesses: (string | undefined)[] = [];
12845
+ for (const clause of switchStatement.caseBlock.clauses) {
12846
+ if (clause.kind === SyntaxKind.CaseClause) {
12847
+ if (clause.expression.kind === SyntaxKind.StringLiteral) {
12848
+ witnesses.push((clause.expression as StringLiteral).text);
12849
+ continue;
12850
+ }
12851
+ return emptyArray;
12852
+ }
12853
+ witnesses.push(/*explicitDefaultStatement*/ undefined);
12854
+ }
12855
+ return witnesses;
12856
+ }
12857
+
12843
12858
function eachTypeContainedIn(source: Type, types: Type[]) {
12844
12859
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
12845
12860
}
@@ -13253,6 +13268,9 @@ namespace ts {
13253
13268
else if (isMatchingReferenceDiscriminant(expr, type)) {
13254
13269
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
13255
13270
}
13271
+ else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
13272
+ type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
13273
+ }
13256
13274
return createFlowType(type, isIncomplete(flowType));
13257
13275
}
13258
13276
@@ -13549,6 +13567,57 @@ namespace ts {
13549
13567
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
13550
13568
}
13551
13569
13570
+ function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
13571
+ const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
13572
+ if (!switchWitnesses.length) {
13573
+ return type;
13574
+ }
13575
+ const clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd);
13576
+ // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
13577
+ const hasDefaultClause = clauseStart === clauseEnd || contains(clauseWitnesses, /*explicitDefaultStatement*/ undefined);
13578
+ const switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause);
13579
+ // The implied type is the raw type suggested by a
13580
+ // value being caught in this clause.
13581
+ // - If there is a default the implied type is not used.
13582
+ // - Otherwise, take the union of the types in the
13583
+ // clause. We narrow the union using facts to remove
13584
+ // types that appear multiple types and are
13585
+ // unreachable.
13586
+ // Example:
13587
+ //
13588
+ // switch (typeof x) {
13589
+ // case 'number':
13590
+ // case 'string': break;
13591
+ // default: break;
13592
+ // case 'number':
13593
+ // case 'boolean': break
13594
+ // }
13595
+ //
13596
+ // The implied type of the first clause number | string.
13597
+ // The implied type of the second clause is string (but this doesn't get used).
13598
+ // The implied type of the third clause is boolean (number has already be caught).
13599
+ if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
13600
+ let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
13601
+ if (impliedType.flags & TypeFlags.Union) {
13602
+ impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
13603
+ }
13604
+ if (!(impliedType.flags & TypeFlags.Never)) {
13605
+ if (isTypeSubtypeOf(impliedType, type)) {
13606
+ return impliedType;
13607
+ }
13608
+ if (type.flags & TypeFlags.Instantiable) {
13609
+ const constraint = getBaseConstraintOfType(type) || anyType;
13610
+ if (isTypeSubtypeOf(impliedType, constraint)) {
13611
+ return getIntersectionType([type, impliedType]);
13612
+ }
13613
+ }
13614
+ }
13615
+ }
13616
+ return hasDefaultClause ?
13617
+ filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
13618
+ getTypeWithFacts(type, switchFacts);
13619
+ }
13620
+
13552
13621
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
13553
13622
const left = getReferenceCandidate(expr.left);
13554
13623
if (!isMatchingReference(reference, left)) {
@@ -18944,10 +19013,60 @@ namespace ts {
18944
19013
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
18945
19014
}
18946
19015
19016
+ /**
19017
+ * Collect the TypeFacts learned from a typeof switch with
19018
+ * total clauses `witnesses`, and the active clause ranging
19019
+ * from `start` to `end`. Parameter `hasDefault` denotes
19020
+ * whether the active clause contains a default clause.
19021
+ */
19022
+ function getFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[], hasDefault: boolean): TypeFacts {
19023
+ let facts: TypeFacts = TypeFacts.None;
19024
+ // When in the default we only collect inequality facts
19025
+ // because default is 'in theory' a set of infinite
19026
+ // equalities.
19027
+ if (hasDefault) {
19028
+ // Value is not equal to any types after the active clause.
19029
+ for (let i = end; i < witnesses.length; i++) {
19030
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
19031
+ }
19032
+ // Remove inequalities for types that appear in the
19033
+ // active clause because they appear before other
19034
+ // types collected so far.
19035
+ for (let i = start; i < end; i++) {
19036
+ facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
19037
+ }
19038
+ // Add inequalities for types before the active clause unconditionally.
19039
+ for (let i = 0; i < start; i++) {
19040
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
19041
+ }
19042
+ }
19043
+ // When in an active clause without default the set of
19044
+ // equalities is finite.
19045
+ else {
19046
+ // Add equalities for all types in the active clause.
19047
+ for (let i = start; i < end; i++) {
19048
+ facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
19049
+ }
19050
+ // Remove equalities for types that appear before the
19051
+ // active clause.
19052
+ for (let i = 0; i < start; i++) {
19053
+ facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
19054
+ }
19055
+ }
19056
+ return facts;
19057
+ }
19058
+
18947
19059
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
18948
19060
if (!node.possiblyExhaustive) {
18949
19061
return false;
18950
19062
}
19063
+ if (node.expression.kind === SyntaxKind.TypeOfExpression) {
19064
+ const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
19065
+ // Type is not equal to every type in the switch.
19066
+ const notEqualFacts = getFactsFromTypeofSwitch(0, 0, getSwitchClauseTypeOfWitnesses(node), /*hasDefault*/ true);
19067
+ const type = getBaseConstraintOfType(operandType) || operandType;
19068
+ return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
19069
+ }
18951
19070
const type = getTypeOfExpression(node.expression);
18952
19071
if (!isLiteralType(type)) {
18953
19072
return false;
0 commit comments