@@ -12526,6 +12526,21 @@ namespace ts {
12526
12526
return links.switchTypes;
12527
12527
}
12528
12528
12529
+ function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] {
12530
+ const witnesses: (string | undefined)[] = [];
12531
+ for (const clause of switchStatement.caseBlock.clauses) {
12532
+ if (clause.kind === SyntaxKind.CaseClause) {
12533
+ if (clause.expression.kind === SyntaxKind.StringLiteral) {
12534
+ witnesses.push((clause.expression as StringLiteral).text);
12535
+ continue;
12536
+ }
12537
+ return emptyArray;
12538
+ }
12539
+ witnesses.push(/*explicitDefaultStatement*/ undefined);
12540
+ }
12541
+ return witnesses;
12542
+ }
12543
+
12529
12544
function eachTypeContainedIn(source: Type, types: Type[]) {
12530
12545
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
12531
12546
}
@@ -12939,6 +12954,9 @@ namespace ts {
12939
12954
else if (isMatchingReferenceDiscriminant(expr, type)) {
12940
12955
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
12941
12956
}
12957
+ else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
12958
+ type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
12959
+ }
12942
12960
return createFlowType(type, isIncomplete(flowType));
12943
12961
}
12944
12962
@@ -13235,6 +13253,57 @@ namespace ts {
13235
13253
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
13236
13254
}
13237
13255
13256
+ function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
13257
+ const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
13258
+ if (!switchWitnesses.length) {
13259
+ return type;
13260
+ }
13261
+ const clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd);
13262
+ // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
13263
+ const hasDefaultClause = clauseStart === clauseEnd || contains(clauseWitnesses, /*explicitDefaultStatement*/ undefined);
13264
+ const switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause);
13265
+ // The implied type is the raw type suggested by a
13266
+ // value being caught in this clause.
13267
+ // - If there is a default the implied type is not used.
13268
+ // - Otherwise, take the union of the types in the
13269
+ // clause. We narrow the union using facts to remove
13270
+ // types that appear multiple types and are
13271
+ // unreachable.
13272
+ // Example:
13273
+ //
13274
+ // switch (typeof x) {
13275
+ // case 'number':
13276
+ // case 'string': break;
13277
+ // default: break;
13278
+ // case 'number':
13279
+ // case 'boolean': break
13280
+ // }
13281
+ //
13282
+ // The implied type of the first clause number | string.
13283
+ // The implied type of the second clause is string (but this doesn't get used).
13284
+ // The implied type of the third clause is boolean (number has already be caught).
13285
+ if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
13286
+ let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
13287
+ if (impliedType.flags & TypeFlags.Union) {
13288
+ impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
13289
+ }
13290
+ if (!(impliedType.flags & TypeFlags.Never)) {
13291
+ if (isTypeSubtypeOf(impliedType, type)) {
13292
+ return impliedType;
13293
+ }
13294
+ if (type.flags & TypeFlags.Instantiable) {
13295
+ const constraint = getBaseConstraintOfType(type) || anyType;
13296
+ if (isTypeSubtypeOf(impliedType, constraint)) {
13297
+ return getIntersectionType([type, impliedType]);
13298
+ }
13299
+ }
13300
+ }
13301
+ }
13302
+ return hasDefaultClause ?
13303
+ filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts) :
13304
+ getTypeWithFacts(type, switchFacts);
13305
+ }
13306
+
13238
13307
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
13239
13308
const left = getReferenceCandidate(expr.left);
13240
13309
if (!isMatchingReference(reference, left)) {
@@ -18540,10 +18609,60 @@ namespace ts {
18540
18609
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
18541
18610
}
18542
18611
18612
+ /**
18613
+ * Collect the TypeFacts learned from a typeof switch with
18614
+ * total clauses `witnesses`, and the active clause ranging
18615
+ * from `start` to `end`. Parameter `hasDefault` denotes
18616
+ * whether the active clause contains a default clause.
18617
+ */
18618
+ function getFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[], hasDefault: boolean): TypeFacts {
18619
+ let facts: TypeFacts = TypeFacts.None;
18620
+ // When in the default we only collect inequality facts
18621
+ // because default is 'in theory' a set of infinite
18622
+ // equalities.
18623
+ if (hasDefault) {
18624
+ // Value is not equal to any types after the active clause.
18625
+ for (let i = end; i < witnesses.length; i++) {
18626
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
18627
+ }
18628
+ // Remove inequalities for types that appear in the
18629
+ // active clause because they appear before other
18630
+ // types collected so far.
18631
+ for (let i = start; i < end; i++) {
18632
+ facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
18633
+ }
18634
+ // Add inequalities for types before the active clause unconditionally.
18635
+ for (let i = 0; i < start; i++) {
18636
+ facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
18637
+ }
18638
+ }
18639
+ // When in an active clause without default the set of
18640
+ // equalities is finite.
18641
+ else {
18642
+ // Add equalities for all types in the active clause.
18643
+ for (let i = start; i < end; i++) {
18644
+ facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
18645
+ }
18646
+ // Remove equalities for types that appear before the
18647
+ // active clause.
18648
+ for (let i = 0; i < start; i++) {
18649
+ facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
18650
+ }
18651
+ }
18652
+ return facts;
18653
+ }
18654
+
18543
18655
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
18544
18656
if (!node.possiblyExhaustive) {
18545
18657
return false;
18546
18658
}
18659
+ if (node.expression.kind === SyntaxKind.TypeOfExpression) {
18660
+ const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
18661
+ // Type is not equal to every type in the switch.
18662
+ const notEqualFacts = getFactsFromTypeofSwitch(0, 0, getSwitchClauseTypeOfWitnesses(node), /*hasDefault*/ true);
18663
+ const type = getBaseConstraintOfType(operandType) || operandType;
18664
+ return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
18665
+ }
18547
18666
const type = getTypeOfExpression(node.expression);
18548
18667
if (!isLiteralType(type)) {
18549
18668
return false;
0 commit comments