@@ -18770,6 +18770,14 @@ namespace ts {
18770
18770
signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
18771
18771
}
18772
18772
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
+
18773
18781
function reportFlowControlError(node: Node) {
18774
18782
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
18775
18783
const sourceFile = getSourceFileOfNode(node);
@@ -19158,20 +19166,30 @@ namespace ts {
19158
19166
if (isMatchingReference(reference, expr)) {
19159
19167
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
19160
19168
}
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
- }
19167
19169
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
19168
19170
type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
19169
19171
}
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
+ }
19175
19193
}
19176
19194
return createFlowType(type, isIncomplete(flowType));
19177
19195
}
@@ -19316,6 +19334,9 @@ namespace ts {
19316
19334
if (isMatchingReference(reference, expr)) {
19317
19335
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
19318
19336
}
19337
+ if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
19338
+ type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
19339
+ }
19319
19340
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
19320
19341
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
19321
19342
}
@@ -19367,12 +19388,12 @@ namespace ts {
19367
19388
if (isMatchingReference(reference, right)) {
19368
19389
return narrowTypeByEquality(type, operator, left, assumeTrue);
19369
19390
}
19370
- if (assumeTrue && strictNullChecks) {
19391
+ if (strictNullChecks) {
19371
19392
if (optionalChainContainsReference(left, reference)) {
19372
- type = narrowTypeByOptionalChainContainment(type, operator, right);
19393
+ type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue );
19373
19394
}
19374
19395
else if (optionalChainContainsReference(right, reference)) {
19375
- type = narrowTypeByOptionalChainContainment(type, operator, left);
19396
+ type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue );
19376
19397
}
19377
19398
}
19378
19399
if (isMatchingReferenceDiscriminant(left, declaredType)) {
@@ -19399,16 +19420,14 @@ namespace ts {
19399
19420
return type;
19400
19421
}
19401
19422
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
19404
19425
// the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the
19405
19426
// 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;
19412
19431
}
19413
19432
19414
19433
function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
@@ -19459,10 +19478,12 @@ namespace ts {
19459
19478
19460
19479
function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
19461
19480
// We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
19481
+ if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19482
+ assumeTrue = !assumeTrue;
19483
+ }
19462
19484
const target = getReferenceCandidate(typeOfExpr.expression);
19463
19485
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")) {
19466
19487
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
19467
19488
}
19468
19489
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
@@ -19472,9 +19493,6 @@ namespace ts {
19472
19493
}
19473
19494
return type;
19474
19495
}
19475
- if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
19476
- assumeTrue = !assumeTrue;
19477
- }
19478
19496
if (type.flags & TypeFlags.Any && literal.text === "function") {
19479
19497
return type;
19480
19498
}
@@ -19509,6 +19527,11 @@ namespace ts {
19509
19527
}
19510
19528
}
19511
19529
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
+
19512
19535
function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
19513
19536
// We only narrow if all case expressions specify
19514
19537
// values with unit types, except for the case where
@@ -19729,28 +19752,17 @@ namespace ts {
19729
19752
19730
19753
function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
19731
19754
// 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) {
19738
19758
if (isMatchingReference(reference, predicateArgument)) {
19739
19759
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
19740
19760
}
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);
19752
19764
}
19753
- if (containsMatchingReference(reference, possibleReference )) {
19765
+ if (containsMatchingReference(reference, predicateArgument )) {
19754
19766
return declaredType;
19755
19767
}
19756
19768
}
0 commit comments