diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6bd6045a551..ee07acdac82 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -856,9 +856,8 @@ namespace ts { function isNarrowableReference(expr: Expression): boolean { return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || - isElementAccessExpression(expr) && - isStringOrNumericLiteralLike(expr.argumentExpression) && - isNarrowableReference(expr.expression); + isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || + isOptionalChain(expr); } function hasNarrowableArgument(expr: CallExpression) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5f0f8241a5e..a759f7179b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19360,7 +19360,7 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { + if (assumeTrue && strictNullChecks) { if (optionalChainContainsReference(left, reference)) { type = narrowTypeByOptionalChainContainment(type, operator, right); } @@ -19397,7 +19397,9 @@ namespace ts { // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the // operator is !== and the type of value is undefined. const valueType = getTypeOfExpression(value); - return operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) || + return operator === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) || + operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) || + operator === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable || operator === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } @@ -19452,6 +19454,10 @@ namespace ts { // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression); if (!isMatchingReference(reference, target)) { + if (assumeTrue && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken) && + strictNullChecks && optionalChainContainsReference(target, reference)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the // narrowed type of 'y' to its declared type. if (containsMatchingReference(reference, target)) { @@ -19633,6 +19639,9 @@ namespace ts { function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { const left = getReferenceCandidate(expr.left); if (!isMatchingReference(reference, left)) { + if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } // For a reference of the form 'x.y', an 'x instanceof T' type guard resets the // narrowed type of 'y' to its declared type. We do this because preceding 'x.y' // references might reference a different 'y' property. However, we make an exception