Skip to content

Commit 719b779

Browse files
committed
Reflect CFA effects of optional chains in equality checks
1 parent 3c794e9 commit 719b779

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18124,6 +18124,16 @@ namespace ts {
1812418124
return false;
1812518125
}
1812618126

18127+
function optionalChainContainsReference(source: Node, target: Node) {
18128+
while (isOptionalChain(source)) {
18129+
source = source.expression;
18130+
if (isMatchingReference(source, target)) {
18131+
return true;
18132+
}
18133+
}
18134+
return false;
18135+
}
18136+
1812718137
// Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared
1812818138
// type of xxx is a union type, and yyy is a property that is possibly a discriminant. We consider a property
1812918139
// a possible discriminant if its type differs in the constituents of containing union type, and if every
@@ -19350,6 +19360,14 @@ namespace ts {
1935019360
if (isMatchingReference(reference, right)) {
1935119361
return narrowTypeByEquality(type, operator, left, assumeTrue);
1935219362
}
19363+
if (assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) {
19364+
if (optionalChainContainsReference(left, reference)) {
19365+
type = narrowTypeByOptionalChainContainment(type, operator, right);
19366+
}
19367+
else if (optionalChainContainsReference(right, reference)) {
19368+
type = narrowTypeByOptionalChainContainment(type, operator, left);
19369+
}
19370+
}
1935319371
if (isMatchingReferenceDiscriminant(left, declaredType)) {
1935419372
return narrowTypeByDiscriminant(type, <AccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
1935519373
}
@@ -19374,6 +19392,16 @@ namespace ts {
1937419392
return type;
1937519393
}
1937619394

19395+
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression): Type {
19396+
// We are in the true branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
19397+
// the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the
19398+
// operator is !== and the type of value is undefined.
19399+
const valueType = getTypeOfExpression(value);
19400+
return operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) ||
19401+
operator === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ?
19402+
getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
19403+
}
19404+
1937719405
function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
1937819406
if (type.flags & TypeFlags.Any) {
1937919407
return type;

0 commit comments

Comments
 (0)