@@ -14282,6 +14282,23 @@ namespace ts {
1428214282 return links.switchTypes;
1428314283 }
1428414284
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+
1428514302 function eachTypeContainedIn(source: Type, types: Type[]) {
1428614303 return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
1428714304 }
@@ -14704,6 +14721,9 @@ namespace ts {
1470414721 expr as PropertyAccessExpression | ElementAccessExpression,
1470514722 t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
1470614723 }
14724+ else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
14725+ type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
14726+ }
1470714727 return createFlowType(type, isIncomplete(flowType));
1470814728 }
1470914729
@@ -15019,6 +15039,83 @@ namespace ts {
1501915039 return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
1502015040 }
1502115041
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+
1502215119 function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
1502315120 const left = getReferenceCandidate(expr.left);
1502415121 if (!isMatchingReference(reference, left)) {
@@ -20572,10 +20669,62 @@ namespace ts {
2057220669 : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
2057320670 }
2057420671
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+
2057520715 function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
2057620716 if (!node.possiblyExhaustive) {
2057720717 return false;
2057820718 }
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+ }
2057920728 const type = getTypeOfExpression(node.expression);
2058020729 if (!isLiteralType(type)) {
2058120730 return false;
0 commit comments