Skip to content

Commit d3df927

Browse files
committed
Optional chain control flow analysis fixes
1 parent 1bfc472 commit d3df927

File tree

1 file changed

+37
-20
lines changed

1 file changed

+37
-20
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)