Skip to content

Commit d2604e2

Browse files
committed
Switch to checking for assignment when inverted
1 parent 9630147 commit d2604e2

6 files changed

+38
-108
lines changed

src/compiler/checker.ts

+34-18
Original file line numberDiff line numberDiff line change
@@ -44539,30 +44539,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4453944539
function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) {
4454044540
if (!strictNullChecks) return;
4454144541

44542-
bothHelper(condExpr, condType, body);
44542+
bothHelper(condExpr, body);
4454344543

44544-
function bothHelper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) {
44544+
function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) {
4454544545
condExpr = skipParentheses(condExpr);
44546-
helper(condExpr, condType, body);
44546+
helper(condExpr, body);
4454744547
while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) {
4454844548
condExpr = skipParentheses(condExpr.left);
44549-
helper(condExpr, condType, body);
44549+
helper(condExpr, body);
4455044550
}
4455144551
}
4455244552

44553-
function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) {
44554-
let location = condExpr;
44555-
// let checkAwaitOnly = false;
44556-
while (isPrefixUnaryExpression(location)) {
44557-
location = skipParentheses(location.operand);
44558-
// checkAwaitOnly = true;
44553+
function helper(condExpr: Expression, body: Expression | Statement | undefined) {
44554+
let inverted = false;
44555+
while (isPrefixUnaryExpression(condExpr)) {
44556+
condExpr = skipParentheses(condExpr.operand);
44557+
inverted = !inverted;
4455944558
}
44559+
let location = condExpr;
4456044560
location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location;
4456144561
if (isModuleExportsAccessExpression(location)) {
4456244562
return;
4456344563
}
4456444564
if (isLogicalOrCoalescingBinaryExpression(location)) {
44565-
bothHelper(location, condType, body);
44565+
bothHelper(location, body);
4456644566
return;
4456744567
}
4456844568
const type = location === condExpr ? condType : checkExpression(location);
@@ -44593,8 +44593,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4459344593
return;
4459444594
}
4459544595

44596+
const isUsedInBody = inverted && isIfStatement(condExpr.parent) ? testedSymbol && isSymbolUsedInConditionBody(condExpr, condExpr.parent.parent, testedNode, testedSymbol, /*checkForAssignment*/ true) : testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol, /*checkForAssignment*/ false);
4459644597
const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol)
44597-
|| testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol);
44598+
|| isUsedInBody;
44599+
4459844600
if (!isUsed) {
4459944601
if (isPromise) {
4460044602
errorAndMaybeSuggestAwait(
@@ -44604,23 +44606,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4460444606
getTypeNameForErrorDisplay(type),
4460544607
);
4460644608
}
44607-
// else if (!checkAwaitOnly) {
4460844609
else {
4460944610
error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead);
4461044611
}
4461144612
}
4461244613
}
4461344614
}
4461444615

44615-
function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean {
44616+
function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression | Node, testedNode: Node, testedSymbol: Symbol, checkForAssignment: boolean): boolean {
4461644617
return !!forEachChild(body, function check(childNode): boolean | undefined {
4461744618
if (isIdentifier(childNode)) {
4461844619
const childSymbol = getSymbolAtLocation(childNode);
4461944620
if (childSymbol && childSymbol === testedSymbol) {
44620-
// If the test was a simple identifier, the above check is sufficient
44621-
if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) {
44622-
return true;
44621+
if (checkForAssignment) {
44622+
if (isIdentifier(expr) && isBinaryExpression(childNode.parent) && childNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
44623+
return true;
44624+
}
4462344625
}
44626+
else {
44627+
// If the test was a simple identifier, the above check is sufficient
44628+
if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) {
44629+
return true;
44630+
}
44631+
}
44632+
4462444633
// Otherwise we need to ensure the symbol is called on the same target
4462544634
let testedExpression = testedNode.parent;
4462644635
let childExpression = childNode.parent;
@@ -44629,7 +44638,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4462944638
isIdentifier(testedExpression) && isIdentifier(childExpression) ||
4463044639
testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword
4463144640
) {
44632-
return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression);
44641+
const sameSymbol = getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression);
44642+
if (sameSymbol) {
44643+
if (checkForAssignment) {
44644+
return isPropertyAccessExpression(childNode.parent) && isBinaryExpression(childExpression.parent.parent) && childExpression.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken;
44645+
}
44646+
return true;
44647+
}
44648+
return false;
4463344649
}
4463444650
else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) {
4463544651
if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) {

tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt

-69
This file was deleted.

tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
truthinessCallExpressionCoercion.ts(2,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
2-
truthinessCallExpressionCoercion.ts(8,11): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
32
truthinessCallExpressionCoercion.ts(18,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
43
truthinessCallExpressionCoercion.ts(36,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
54
truthinessCallExpressionCoercion.ts(50,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
@@ -8,7 +7,7 @@ truthinessCallExpressionCoercion.ts(76,9): error TS2774: This condition will alw
87
truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
98

109

11-
==== truthinessCallExpressionCoercion.ts (8 errors) ====
10+
==== truthinessCallExpressionCoercion.ts (7 errors) ====
1211
function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) {
1312
if (required) { // error
1413
~~~~~~~~
@@ -19,8 +18,6 @@ truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will alw
1918
}
2019

2120
if (!!required) { // ok
22-
~~~~~~~~
23-
!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
2421
}
2522

2623
if (required()) { // ok

tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
truthinessCallExpressionCoercion1.ts(3,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
2-
truthinessCallExpressionCoercion1.ts(9,7): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
32
truthinessCallExpressionCoercion1.ts(19,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
43
truthinessCallExpressionCoercion1.ts(33,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
54
truthinessCallExpressionCoercion1.ts(46,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
65
truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
76

87

9-
==== truthinessCallExpressionCoercion1.ts (6 errors) ====
8+
==== truthinessCallExpressionCoercion1.ts (5 errors) ====
109
function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) {
1110
// error
1211
required ? console.log('required') : undefined;
@@ -18,8 +17,6 @@ truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will al
1817

1918
// ok
2019
!!required ? console.log('not required') : undefined;
21-
~~~~~~~~
22-
!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
2320

2421
// ok
2522
required() ? console.log('required call') : undefined;

tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
truthinessCallExpressionCoercion2.ts(11,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
22
truthinessCallExpressionCoercion2.ts(14,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
3-
truthinessCallExpressionCoercion2.ts(29,7): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
43
truthinessCallExpressionCoercion2.ts(41,18): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
54
truthinessCallExpressionCoercion2.ts(44,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
65
truthinessCallExpressionCoercion2.ts(48,9): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
@@ -36,7 +35,7 @@ truthinessCallExpressionCoercion2.ts(180,9): error TS2774: This condition will a
3635
truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
3736

3837

39-
==== truthinessCallExpressionCoercion2.ts (36 errors) ====
38+
==== truthinessCallExpressionCoercion2.ts (35 errors) ====
4039
declare class A {
4140
static from(): string;
4241
}
@@ -70,8 +69,6 @@ truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will
7069

7170
// ok
7271
!!required1 && console.log('not required');
73-
~~~~~~~~~
74-
!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
7572

7673
// ok
7774
required1() && console.log('required call');

tests/baselines/reference/truthinessPromiseCoercion.errors.txt

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
truthinessPromiseCoercion.ts(7,9): error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
2-
truthinessPromiseCoercion.ts(8,11): error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
32
truthinessPromiseCoercion.ts(11,5): error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
4-
truthinessPromiseCoercion.ts(12,7): error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
53
truthinessPromiseCoercion.ts(32,9): error TS2801: This condition will always return true since this 'Promise<unknown>' is always defined.
64
truthinessPromiseCoercion.ts(40,9): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
75
truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
86

97

10-
==== truthinessPromiseCoercion.ts (7 errors) ====
8+
==== truthinessPromiseCoercion.ts (5 errors) ====
119
declare const p: Promise<number>
1210
declare const p2: null | Promise<number>
1311
declare const obj: { p: Promise<unknown> }
@@ -19,19 +17,13 @@ truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always ret
1917
!!! error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
2018
!!! related TS2773 truthinessPromiseCoercion.ts:7:9: Did you forget to use 'await'?
2119
if (!!p) {} // no err
22-
~
23-
!!! error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
24-
!!! related TS2773 truthinessPromiseCoercion.ts:8:11: Did you forget to use 'await'?
2520
if (p2) {} // no err
2621

2722
p ? f.arguments : f.arguments;
2823
~
2924
!!! error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
3025
!!! related TS2773 truthinessPromiseCoercion.ts:11:5: Did you forget to use 'await'?
3126
!!p ? f.arguments : f.arguments;
32-
~
33-
!!! error TS2801: This condition will always return true since this 'Promise<number>' is always defined.
34-
!!! related TS2773 truthinessPromiseCoercion.ts:12:7: Did you forget to use 'await'?
3527
p2 ? f.arguments : f.arguments;
3628
}
3729

0 commit comments

Comments
 (0)