@@ -18770,6 +18770,14 @@ namespace ts {
1877018770 signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
1877118771 }
1877218772
18773+ function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) {
18774+ if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
18775+ return callExpression.arguments[predicate.parameterIndex];
18776+ }
18777+ const invokedExpression = skipParentheses(callExpression.expression);
18778+ return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined;
18779+ }
18780+
1877318781 function reportFlowControlError(node: Node) {
1877418782 const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
1877518783 const sourceFile = getSourceFileOfNode(node);
@@ -19158,20 +19166,30 @@ namespace ts {
1915819166 if (isMatchingReference(reference, expr)) {
1915919167 type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
1916019168 }
19161- else if (isMatchingReferenceDiscriminant(expr, type)) {
19162- type = narrowTypeByDiscriminant(
19163- type,
19164- expr as AccessExpression,
19165- t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
19166- }
1916719169 else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
1916819170 type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
1916919171 }
19170- else if (containsMatchingReferenceDiscriminant(reference, expr)) {
19171- type = declaredType;
19172- }
19173- else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
19174- return unreachableNeverType;
19172+ else {
19173+ if (strictNullChecks) {
19174+ if (optionalChainContainsReference(expr, reference)) {
19175+ type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
19176+ t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
19177+ }
19178+ else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
19179+ type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
19180+ t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (<StringLiteralType>t).value === "undefined"));
19181+ }
19182+ }
19183+ if (isMatchingReferenceDiscriminant(expr, type)) {
19184+ type = narrowTypeByDiscriminant(type, expr as AccessExpression,
19185+ t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
19186+ }
19187+ else if (containsMatchingReferenceDiscriminant(reference, expr)) {
19188+ type = declaredType;
19189+ }
19190+ else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
19191+ return unreachableNeverType;
19192+ }
1917519193 }
1917619194 return createFlowType(type, isIncomplete(flowType));
1917719195 }
@@ -19316,6 +19334,9 @@ namespace ts {
1931619334 if (isMatchingReference(reference, expr)) {
1931719335 return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
1931819336 }
19337+ if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
19338+ type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
19339+ }
1931919340 if (isMatchingReferenceDiscriminant(expr, declaredType)) {
1932019341 return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
1932119342 }
@@ -19367,12 +19388,12 @@ namespace ts {
1936719388 if (isMatchingReference(reference, right)) {
1936819389 return narrowTypeByEquality(type, operator, left, assumeTrue);
1936919390 }
19370- if (assumeTrue && strictNullChecks) {
19391+ if (strictNullChecks) {
1937119392 if (optionalChainContainsReference(left, reference)) {
19372- type = narrowTypeByOptionalChainContainment(type, operator, right);
19393+ type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue );
1937319394 }
1937419395 else if (optionalChainContainsReference(right, reference)) {
19375- type = narrowTypeByOptionalChainContainment(type, operator, left);
19396+ type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue );
1937619397 }
1937719398 }
1937819399 if (isMatchingReferenceDiscriminant(left, declaredType)) {
@@ -19399,16 +19420,14 @@ namespace ts {
1939919420 return type;
1940019421 }
1940119422
19402- function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression): Type {
19403- // We are in the true branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
19423+ function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean ): Type {
19424+ // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from
1940419425 // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the
1940519426 // operator is !== and the type of value is undefined.
19406- const valueType = getTypeOfExpression(value);
19407- return operator === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) ||
19408- operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) ||
19409- operator === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable ||
19410- operator === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ?
19411- getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
19427+ const effectiveTrue = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken ? assumeTrue : !assumeTrue;
19428+ const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
19429+ const valueNonNullish = !(getTypeFacts(getTypeOfExpression(value)) & (doubleEquals ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQUndefined));
19430+ return effectiveTrue === valueNonNullish ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
1941219431 }
1941319432
1941419433 function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -19459,10 +19478,12 @@ namespace ts {
1945919478
1946019479 function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
1946119480 // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
19481+ if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19482+ assumeTrue = !assumeTrue;
19483+ }
1946219484 const target = getReferenceCandidate(typeOfExpr.expression);
1946319485 if (!isMatchingReference(reference, target)) {
19464- if (assumeTrue && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken) &&
19465- strictNullChecks && optionalChainContainsReference(target, reference)) {
19486+ if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
1946619487 return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
1946719488 }
1946819489 // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
@@ -19472,9 +19493,6 @@ namespace ts {
1947219493 }
1947319494 return type;
1947419495 }
19475- if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19476- assumeTrue = !assumeTrue;
19477- }
1947819496 if (type.flags & TypeFlags.Any && literal.text === "function") {
1947919497 return type;
1948019498 }
@@ -19509,6 +19527,11 @@ namespace ts {
1950919527 }
1951019528 }
1951119529
19530+ function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) {
19531+ const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck);
19532+ return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
19533+ }
19534+
1951219535 function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
1951319536 // We only narrow if all case expressions specify
1951419537 // values with unit types, except for the case where
@@ -19729,28 +19752,17 @@ namespace ts {
1972919752
1973019753 function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
1973119754 // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
19732- if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) {
19733- return type;
19734- }
19735- if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
19736- const predicateArgument = callExpression.arguments[predicate.parameterIndex];
19737- if (predicateArgument && predicate.type) {
19755+ if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
19756+ const predicateArgument = getTypePredicateArgument(predicate, callExpression);
19757+ if (predicateArgument) {
1973819758 if (isMatchingReference(reference, predicateArgument)) {
1973919759 return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
1974019760 }
19741- if (containsMatchingReference(reference, predicateArgument)) {
19742- return declaredType;
19743- }
19744- }
19745- }
19746- else {
19747- const invokedExpression = skipParentheses(callExpression.expression);
19748- if (isAccessExpression(invokedExpression) && predicate.type) {
19749- const possibleReference = skipParentheses(invokedExpression.expression);
19750- if (isMatchingReference(reference, possibleReference)) {
19751- return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
19761+ if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
19762+ !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
19763+ return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
1975219764 }
19753- if (containsMatchingReference(reference, possibleReference )) {
19765+ if (containsMatchingReference(reference, predicateArgument )) {
1975419766 return declaredType;
1975519767 }
1975619768 }
0 commit comments