Skip to content

Commit

Permalink
Make undefined for default case less pervasive by removing once done …
Browse files Browse the repository at this point in the history
…with it
  • Loading branch information
jack-williams committed May 23, 2018
1 parent 9e43183 commit 39d7d1e
Showing 1 changed file with 31 additions and 17 deletions.
48 changes: 31 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13572,10 +13572,27 @@ namespace ts {
if (!switchWitnesses.length) {
return type;
}
const clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd);
// Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseWitnesses, /*explicitDefaultStatement*/ undefined);
const switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause);
const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
let clauseWitnesses: string[];
let switchFacts: TypeFacts;
if (defaultCaseLocation > -1) {
// We no longer need the undefined denoting an
// explicit default case. Remove the undefined and
// fix-up clauseStart and clauseEnd. This means
// that we don't have to worry about undefined
// in the witness array.
const witnesses= <string[]>switchWitnesses.filter(witness => witness !== undefined);
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
}
else {
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
}
// The implied type is the raw type suggested by a
// value being caught in this clause.
// - If there is a default the implied type is not used.
Expand All @@ -13594,7 +13611,7 @@ namespace ts {
// }
//
// The implied type of the first clause number | string.
// The implied type of the second clause is never, but this does not get just because it includes a default case.
// The implied type of the second clause is never, but this does not get used because it includes a default case.
// The implied type of the third clause is boolean (number has already be caught).
if (!(hasDefaultClause || (type.flags & TypeFlags.Union))) {
let impliedType = getTypeWithFacts(getUnionType((<string[]>clauseWitnesses).map(text => typeofTypesByName.get(text) || neverType)), switchFacts);
Expand Down Expand Up @@ -19019,43 +19036,38 @@ namespace ts {
* from `start` to `end`. Parameter `hasDefault` denotes
* whether the active clause contains a default clause.
*/
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[], hasDefault: boolean): TypeFacts {
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts {
let facts: TypeFacts = TypeFacts.None;
// When in the default we only collect inequality facts
// because default is 'in theory' a set of infinite
// equalities.
if (hasDefault) {
// Value is not equal to any types after the active clause.
for (let i = end; i < witnesses.length; i++) {
const witness = witnesses[i];
facts |= (witness && typeofNEFacts.get(witness)) || TypeFacts.TypeofNEHostObject;
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
}
// Remove inequalities for types that appear in the
// active clause because they appear before other
// types collected so far.
for (let i = start; i < end; i++) {
const witness = witnesses[i];
facts &= ~((witness && typeofNEFacts.get(witness)) || 0);
facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
}
// Add inequalities for types before the active clause unconditionally.
for (let i = 0; i < start; i++) {
const witness = witnesses[i];
facts |= (witness && typeofNEFacts.get(witness)) || TypeFacts.TypeofNEHostObject;
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
}
}
// When in an active clause without default the set of
// equalities is finite.
else {
// Add equalities for all types in the active clause.
for (let i = start; i < end; i++) {
const witness = witnesses[i];
facts |= (witness && typeofEQFacts.get(witness)) || TypeFacts.TypeofEQHostObject;
facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
}
// Remove equalities for types that appear before the
// active clause.
for (let i = 0; i < start; i++) {
const witness = witnesses[i];
facts &= ~((witness && typeofEQFacts.get(witness)) || 0);
facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
}
}
return facts;
Expand All @@ -19067,8 +19079,10 @@ namespace ts {
}
if (node.expression.kind === SyntaxKind.TypeOfExpression) {
const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
// Type is not equal to every type in the switch.
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, getSwitchClauseTypeOfWitnesses(node), /*hasDefault*/ true);
// This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined.
const witnesses = <string[]>getSwitchClauseTypeOfWitnesses(node);
// notEqualFacts states that the type of the switched value is not equal to every type in the switch.
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true);
const type = getBaseConstraintOfType(operandType) || operandType;
return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
}
Expand Down

0 comments on commit 39d7d1e

Please sign in to comment.