From 39d7d1ead03d1c8e1696660ab91bd3dd5fb5c62d Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 23 May 2018 03:12:50 +0100 Subject: [PATCH] Make undefined for default case less pervasive by removing once done with it --- src/compiler/checker.ts | 48 ++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1629717ff2861..e9d7e4e5489d9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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= 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 = switchWitnesses.slice(clauseStart, clauseEnd); + switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, 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. @@ -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((clauseWitnesses).map(text => typeofTypesByName.get(text) || neverType)), switchFacts); @@ -19019,7 +19036,7 @@ 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 @@ -19027,20 +19044,17 @@ namespace ts { 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 @@ -19048,14 +19062,12 @@ namespace ts { 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; @@ -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 = 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); }