Skip to content

Commit 98fe342

Browse files
committed
Handle more cases
1 parent d3df927 commit 98fe342

File tree

1 file changed

+25
-35
lines changed

1 file changed

+25
-35
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18787,6 +18787,14 @@ namespace ts {
1878718787
signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
1878818788
}
1878918789

18790+
function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) {
18791+
if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
18792+
return callExpression.arguments[predicate.parameterIndex];
18793+
}
18794+
const invokedExpression = skipParentheses(callExpression.expression);
18795+
return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined;
18796+
}
18797+
1879018798
function reportFlowControlError(node: Node) {
1879118799
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
1879218800
const sourceFile = getSourceFileOfNode(node);
@@ -19338,6 +19346,9 @@ namespace ts {
1933819346
if (isMatchingReference(reference, expr)) {
1933919347
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
1934019348
}
19349+
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
19350+
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
19351+
}
1934119352
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
1934219353
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
1934319354
}
@@ -19422,21 +19433,13 @@ namespace ts {
1942219433
}
1942319434

1942419435
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;
1943119436
// We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
1943219437
// the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the
1943319438
// operator is !== and the type of value is undefined.
19434-
const valueType = getTypeOfExpression(value);
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 ?
19439-
getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
19439+
const effectiveTrue = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken ? assumeTrue : !assumeTrue;
19440+
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
19441+
const valueNonNullish = !(getTypeFacts(getTypeOfExpression(value)) & (doubleEquals ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQUndefined));
19442+
return effectiveTrue === valueNonNullish ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
1944019443
}
1944119444

1944219445
function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -19487,10 +19490,12 @@ namespace ts {
1948719490

1948819491
function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
1948919492
// We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
19493+
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19494+
assumeTrue = !assumeTrue;
19495+
}
1949019496
const target = getReferenceCandidate(typeOfExpr.expression);
1949119497
if (!isMatchingReference(reference, target)) {
19492-
if (assumeTrue && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken) &&
19493-
strictNullChecks && optionalChainContainsReference(target, reference)) {
19498+
if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
1949419499
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
1949519500
}
1949619501
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
@@ -19500,9 +19505,6 @@ namespace ts {
1950019505
}
1950119506
return type;
1950219507
}
19503-
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19504-
assumeTrue = !assumeTrue;
19505-
}
1950619508
if (type.flags & TypeFlags.Any && literal.text === "function") {
1950719509
return type;
1950819510
}
@@ -19763,28 +19765,16 @@ namespace ts {
1976319765

1976419766
function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
1976519767
// Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
19766-
if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) {
19767-
return type;
19768-
}
19769-
if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
19770-
const predicateArgument = callExpression.arguments[predicate.parameterIndex];
19771-
if (predicateArgument && predicate.type) {
19768+
if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
19769+
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
19770+
if (predicateArgument) {
1977219771
if (isMatchingReference(reference, predicateArgument)) {
1977319772
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
1977419773
}
19775-
if (containsMatchingReference(reference, predicateArgument)) {
19776-
return declaredType;
19777-
}
19778-
}
19779-
}
19780-
else {
19781-
const invokedExpression = skipParentheses(callExpression.expression);
19782-
if (isAccessExpression(invokedExpression) && predicate.type) {
19783-
const possibleReference = skipParentheses(invokedExpression.expression);
19784-
if (isMatchingReference(reference, possibleReference)) {
19785-
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
19774+
if (strictNullChecks && assumeTrue && !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
19775+
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
1978619776
}
19787-
if (containsMatchingReference(reference, possibleReference)) {
19777+
if (containsMatchingReference(reference, predicateArgument)) {
1978819778
return declaredType;
1978919779
}
1979019780
}

0 commit comments

Comments
 (0)