Skip to content

Commit 6391742

Browse files
committed
Make undefined for default case less pervasive by removing once done with it
1 parent 9e43183 commit 6391742

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13572,10 +13572,27 @@ namespace ts {
1357213572
if (!switchWitnesses.length) {
1357313573
return type;
1357413574
}
13575-
const clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd);
1357613575
// 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);
13576+
const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
13577+
const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
13578+
let clauseWitnesses: string[];
13579+
let switchFacts: TypeFacts;
13580+
if (defaultCaseLocation > -1) {
13581+
// We no longer need the undefined denoting an
13582+
// explicit default case. Remove the undefined and
13583+
// fix-up clauseStart and clauseEnd. This means
13584+
// that we don't have to worry about undefined
13585+
// in the witness array.
13586+
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
13587+
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
13588+
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
13589+
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
13590+
switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
13591+
}
13592+
else {
13593+
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
13594+
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
13595+
}
1357913596
// The implied type is the raw type suggested by a
1358013597
// value being caught in this clause.
1358113598
// - If there is a default the implied type is not used.
@@ -13594,10 +13611,10 @@ namespace ts {
1359413611
// }
1359513612
//
1359613613
// The implied type of the first clause number | string.
13597-
// The implied type of the second clause is never, but this does not get just because it includes a default case.
13614+
// The implied type of the second clause is never, but this does not get used because it includes a default case.
1359813615
// The implied type of the third clause is boolean (number has already be caught).
1359913616
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
13600-
let impliedType = getTypeWithFacts(getUnionType((<string[]>clauseWitnesses).map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
13617+
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
1360113618
if (impliedType.flags & TypeFlags.Union) {
1360213619
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOfType(type) || type);
1360313620
}
@@ -19019,43 +19036,38 @@ namespace ts {
1901919036
* from `start` to `end`. Parameter `hasDefault` denotes
1902019037
* whether the active clause contains a default clause.
1902119038
*/
19022-
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[], hasDefault: boolean): TypeFacts {
19039+
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts {
1902319040
let facts: TypeFacts = TypeFacts.None;
1902419041
// When in the default we only collect inequality facts
1902519042
// because default is 'in theory' a set of infinite
1902619043
// equalities.
1902719044
if (hasDefault) {
1902819045
// Value is not equal to any types after the active clause.
1902919046
for (let i = end; i < witnesses.length; i++) {
19030-
const witness = witnesses[i];
19031-
facts |= (witness && typeofNEFacts.get(witness)) || TypeFacts.TypeofNEHostObject;
19047+
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
1903219048
}
1903319049
// Remove inequalities for types that appear in the
1903419050
// active clause because they appear before other
1903519051
// types collected so far.
1903619052
for (let i = start; i < end; i++) {
19037-
const witness = witnesses[i];
19038-
facts &= ~((witness && typeofNEFacts.get(witness)) || 0);
19053+
facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
1903919054
}
1904019055
// Add inequalities for types before the active clause unconditionally.
1904119056
for (let i = 0; i < start; i++) {
19042-
const witness = witnesses[i];
19043-
facts |= (witness && typeofNEFacts.get(witness)) || TypeFacts.TypeofNEHostObject;
19057+
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
1904419058
}
1904519059
}
1904619060
// When in an active clause without default the set of
1904719061
// equalities is finite.
1904819062
else {
1904919063
// Add equalities for all types in the active clause.
1905019064
for (let i = start; i < end; i++) {
19051-
const witness = witnesses[i];
19052-
facts |= (witness && typeofEQFacts.get(witness)) || TypeFacts.TypeofEQHostObject;
19065+
facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
1905319066
}
1905419067
// Remove equalities for types that appear before the
1905519068
// active clause.
1905619069
for (let i = 0; i < start; i++) {
19057-
const witness = witnesses[i];
19058-
facts &= ~((witness && typeofEQFacts.get(witness)) || 0);
19070+
facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
1905919071
}
1906019072
}
1906119073
return facts;
@@ -19067,8 +19079,10 @@ namespace ts {
1906719079
}
1906819080
if (node.expression.kind === SyntaxKind.TypeOfExpression) {
1906919081
const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
19070-
// Type is not equal to every type in the switch.
19071-
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, getSwitchClauseTypeOfWitnesses(node), /*hasDefault*/ true);
19082+
// This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined.
19083+
const witnesses = <string[]>getSwitchClauseTypeOfWitnesses(node);
19084+
// notEqualFacts states that the type of the switched value is not equal to every type in the switch.
19085+
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true);
1907219086
const type = getBaseConstraintOfType(operandType) || operandType;
1907319087
return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
1907419088
}

0 commit comments

Comments
 (0)