@@ -19175,20 +19175,25 @@ namespace ts {
1917519175 if (isMatchingReference(reference, expr)) {
1917619176 type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
1917719177 }
19178- else if (isMatchingReferenceDiscriminant(expr, type)) {
19179- type = narrowTypeByDiscriminant(
19180- type,
19181- expr as AccessExpression,
19182- t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
19183- }
1918419178 else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
1918519179 type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
1918619180 }
19187- else if (containsMatchingReferenceDiscriminant(reference, expr)) {
19188- type = declaredType;
19189- }
19190- else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
19191- return unreachableNeverType;
19181+ else {
19182+ if (strictNullChecks && optionalChainContainsReference(expr, reference)) {
19183+ type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
19184+ }
19185+ if (isMatchingReferenceDiscriminant(expr, type)) {
19186+ type = narrowTypeByDiscriminant(
19187+ type,
19188+ expr as AccessExpression,
19189+ t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
19190+ }
19191+ else if (containsMatchingReferenceDiscriminant(reference, expr)) {
19192+ type = declaredType;
19193+ }
19194+ else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
19195+ return unreachableNeverType;
19196+ }
1919219197 }
1919319198 return createFlowType(type, isIncomplete(flowType));
1919419199 }
@@ -19384,12 +19389,12 @@ namespace ts {
1938419389 if (isMatchingReference(reference, right)) {
1938519390 return narrowTypeByEquality(type, operator, left, assumeTrue);
1938619391 }
19387- if (assumeTrue && strictNullChecks) {
19392+ if (strictNullChecks) {
1938819393 if (optionalChainContainsReference(left, reference)) {
19389- type = narrowTypeByOptionalChainContainment(type, operator, right);
19394+ type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue );
1939019395 }
1939119396 else if (optionalChainContainsReference(right, reference)) {
19392- type = narrowTypeByOptionalChainContainment(type, operator, left);
19397+ type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue );
1939319398 }
1939419399 }
1939519400 if (isMatchingReferenceDiscriminant(left, declaredType)) {
@@ -19416,15 +19421,21 @@ namespace ts {
1941619421 return type;
1941719422 }
1941819423
19419- function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression): Type {
19420- // We are in the true branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
19424+ function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
19425+ const op = assumeTrue ? operator :
19426+ operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.ExclamationEqualsToken :
19427+ operator === SyntaxKind.EqualsEqualsEqualsToken ? SyntaxKind.ExclamationEqualsEqualsToken :
19428+ operator === SyntaxKind.ExclamationEqualsToken ? SyntaxKind.EqualsEqualsToken :
19429+ operator === SyntaxKind.ExclamationEqualsEqualsToken ? SyntaxKind.EqualsEqualsEqualsToken :
19430+ operator;
19431+ // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
1942119432 // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the
1942219433 // operator is !== and the type of value is undefined.
1942319434 const valueType = getTypeOfExpression(value);
19424- return operator === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) ||
19425- operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) ||
19426- operator === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable ||
19427- operator === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ?
19435+ return op === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) ||
19436+ op === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) ||
19437+ op === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable ||
19438+ op === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ?
1942819439 getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
1942919440 }
1943019441
@@ -19526,6 +19537,12 @@ namespace ts {
1952619537 }
1952719538 }
1952819539
19540+ function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
19541+ const noClauseIsDefaultOrUndefined = clauseStart !== clauseEnd &&
19542+ every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
19543+ return noClauseIsDefaultOrUndefined ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
19544+ }
19545+
1952919546 function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
1953019547 // We only narrow if all case expressions specify
1953119548 // values with unit types, except for the case where
0 commit comments