From 794277727b7ad246aea849cfcd2e326daea8979b Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Fri, 13 Sep 2024 16:28:27 +0100 Subject: [PATCH 01/14] Support checking that condition will always return `true` when used with `!` --- src/compiler/checker.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4d4f1bf69a808..ae7236fabeffa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44408,26 +44408,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { if (!strictNullChecks) return; - bothHelper(condExpr, body); - function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { - condExpr = skipParentheses(condExpr); - - helper(condExpr, body); + bothHelper(condExpr, condType, body); + function bothHelper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { + condExpr = skipParentheses(condExpr); + while (isPrefixUnaryExpression(condExpr)) { + condExpr = skipParentheses(condExpr.operand) + condType = checkTruthinessExpression(condExpr) + } + helper(condExpr, condType, body); while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { condExpr = skipParentheses(condExpr.left); - helper(condExpr, body); + helper(condExpr, condType, body); } } - function helper(condExpr: Expression, body: Expression | Statement | undefined) { + function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; if (isModuleExportsAccessExpression(location)) { return; } if (isLogicalOrCoalescingBinaryExpression(location)) { - bothHelper(location, body); + bothHelper(location, condType, body); return; } const type = location === condExpr ? condType : checkExpression(location); From 41eb788b4477efb53372f67463fb6bdae3735401 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Fri, 13 Sep 2024 16:52:30 +0100 Subject: [PATCH 02/14] Update baselines --- ...xtualOverloadListFromArrayUnion.errors.txt | 69 +++++++++++++++++++ ...ruthinessCallExpressionCoercion.errors.txt | 5 +- ...uthinessCallExpressionCoercion1.errors.txt | 5 +- ...uthinessCallExpressionCoercion2.errors.txt | 5 +- .../truthinessPromiseCoercion.errors.txt | 10 ++- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt diff --git a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt new file mode 100644 index 0000000000000..7def0f968e165 --- /dev/null +++ b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt @@ -0,0 +1,69 @@ +three.ts(28,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + + +==== one.ts (0 errors) ==== + declare const y: never[] | string[]; + export const yThen = y.map(item => item.length); +==== two.ts (0 errors) ==== + declare const y: number[][] | string[]; + export const yThen = y.map(item => item.length); +==== three.ts (1 errors) ==== + // #42504 + interface ResizeObserverCallback { + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + interface ResizeObserverCallback { // duplicate for effect + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + + const resizeObserver = new ResizeObserver(([entry]) => { + entry + }); + // comment in #35501 + interface Callback { + (error: null, result: T): unknown + (error: Error, result: null): unknown + } + + interface Task { + (callback: Callback): unknown + } + + export function series(tasks: Task[], callback: Callback): void { + let index = 0 + let results: T[] = [] + + function next() { + let task = tasks[index] + if (!task) { + ~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + callback(null, results) + } else { + task((error, result) => { + if (error) { + callback(error, null) + } else { + // must use postfix-!, since `error` and `result` don't have a + // causal relationship when the overloads are combined + results.push(result!) + next() + } + }) + } + } + next() + } + + series([ + cb => setTimeout(() => cb(null, 1), 300), + cb => setTimeout(() => cb(null, 2), 200), + cb => setTimeout(() => cb(null, 3), 100), + ], (error, results) => { + if (error) { + console.error(error) + } else { + console.log(results) + } + }) + \ No newline at end of file diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt index 492cbdfcbffc1..239cea1d41f40 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt @@ -1,4 +1,5 @@ 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? +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? 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? 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? 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? @@ -7,7 +8,7 @@ truthinessCallExpressionCoercion.ts(76,9): error TS2774: This condition will alw 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? -==== truthinessCallExpressionCoercion.ts (7 errors) ==== +==== truthinessCallExpressionCoercion.ts (8 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { if (required) { // error ~~~~~~~~ @@ -18,6 +19,8 @@ truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will alw } if (!!required) { // ok + ~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } if (required()) { // ok diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt index 978a1565af7f9..9c972898bb280 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt @@ -1,11 +1,12 @@ 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? +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? 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? 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? 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? 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? -==== truthinessCallExpressionCoercion1.ts (5 errors) ==== +==== truthinessCallExpressionCoercion1.ts (6 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { // error required ? console.log('required') : undefined; @@ -17,6 +18,8 @@ truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will al // ok !!required ? console.log('not required') : undefined; + ~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required() ? console.log('required call') : undefined; diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt index d0cb3fd84095e..977ff398c8daf 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt @@ -1,5 +1,6 @@ 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? 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? +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? 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? 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? 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? @@ -35,7 +36,7 @@ truthinessCallExpressionCoercion2.ts(180,9): error TS2774: This condition will a 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? -==== truthinessCallExpressionCoercion2.ts (35 errors) ==== +==== truthinessCallExpressionCoercion2.ts (36 errors) ==== declare class A { static from(): string; } @@ -69,6 +70,8 @@ truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will // ok !!required1 && console.log('not required'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required1() && console.log('required call'); diff --git a/tests/baselines/reference/truthinessPromiseCoercion.errors.txt b/tests/baselines/reference/truthinessPromiseCoercion.errors.txt index 9a7a1e17a158c..8907fcb574c2b 100644 --- a/tests/baselines/reference/truthinessPromiseCoercion.errors.txt +++ b/tests/baselines/reference/truthinessPromiseCoercion.errors.txt @@ -1,11 +1,13 @@ truthinessPromiseCoercion.ts(7,9): error TS2801: This condition will always return true since this 'Promise' is always defined. +truthinessPromiseCoercion.ts(8,11): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(11,5): error TS2801: This condition will always return true since this 'Promise' is always defined. +truthinessPromiseCoercion.ts(12,7): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(32,9): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(40,9): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always return true since this 'Promise' is always defined. -==== truthinessPromiseCoercion.ts (5 errors) ==== +==== truthinessPromiseCoercion.ts (7 errors) ==== declare const p: Promise declare const p2: null | Promise declare const obj: { p: Promise } @@ -17,6 +19,9 @@ truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always ret !!! error TS2801: This condition will always return true since this 'Promise' is always defined. !!! related TS2773 truthinessPromiseCoercion.ts:7:9: Did you forget to use 'await'? if (!!p) {} // no err + ~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 truthinessPromiseCoercion.ts:8:11: Did you forget to use 'await'? if (p2) {} // no err p ? f.arguments : f.arguments; @@ -24,6 +29,9 @@ truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always ret !!! error TS2801: This condition will always return true since this 'Promise' is always defined. !!! related TS2773 truthinessPromiseCoercion.ts:11:5: Did you forget to use 'await'? !!p ? f.arguments : f.arguments; + ~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 truthinessPromiseCoercion.ts:12:7: Did you forget to use 'await'? p2 ? f.arguments : f.arguments; } From 72c3c8b9564314c8da16b2470194bc20b9d901c8 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Wed, 25 Sep 2024 22:55:06 +0100 Subject: [PATCH 03/14] Update to only check for missing `await` for `!` case --- src/compiler/checker.ts | 14 ++-- ...xtualOverloadListFromArrayUnion.errors.txt | 69 ------------------- ...ruthinessCallExpressionCoercion.errors.txt | 5 +- ...uthinessCallExpressionCoercion1.errors.txt | 5 +- ...uthinessCallExpressionCoercion2.errors.txt | 5 +- 5 files changed, 11 insertions(+), 87 deletions(-) delete mode 100644 tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae7236fabeffa..d69520edef593 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44413,10 +44413,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function bothHelper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { condExpr = skipParentheses(condExpr); - while (isPrefixUnaryExpression(condExpr)) { - condExpr = skipParentheses(condExpr.operand) - condType = checkTruthinessExpression(condExpr) - } helper(condExpr, condType, body); while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { condExpr = skipParentheses(condExpr.left); @@ -44425,7 +44421,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { - const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; + let location = condExpr + let checkAwaitOnly = false; + while (isPrefixUnaryExpression(location)) { + location = skipParentheses(location.operand) + checkAwaitOnly = true; + } + location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; if (isModuleExportsAccessExpression(location)) { return; } @@ -44472,7 +44474,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getTypeNameForErrorDisplay(type), ); } - else { + else if (!checkAwaitOnly) { error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); } } diff --git a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt deleted file mode 100644 index 7def0f968e165..0000000000000 --- a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt +++ /dev/null @@ -1,69 +0,0 @@ -three.ts(28,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? - - -==== one.ts (0 errors) ==== - declare const y: never[] | string[]; - export const yThen = y.map(item => item.length); -==== two.ts (0 errors) ==== - declare const y: number[][] | string[]; - export const yThen = y.map(item => item.length); -==== three.ts (1 errors) ==== - // #42504 - interface ResizeObserverCallback { - (entries: ResizeObserverEntry[], observer: ResizeObserver): void; - } - interface ResizeObserverCallback { // duplicate for effect - (entries: ResizeObserverEntry[], observer: ResizeObserver): void; - } - - const resizeObserver = new ResizeObserver(([entry]) => { - entry - }); - // comment in #35501 - interface Callback { - (error: null, result: T): unknown - (error: Error, result: null): unknown - } - - interface Task { - (callback: Callback): unknown - } - - export function series(tasks: Task[], callback: Callback): void { - let index = 0 - let results: T[] = [] - - function next() { - let task = tasks[index] - if (!task) { - ~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? - callback(null, results) - } else { - task((error, result) => { - if (error) { - callback(error, null) - } else { - // must use postfix-!, since `error` and `result` don't have a - // causal relationship when the overloads are combined - results.push(result!) - next() - } - }) - } - } - next() - } - - series([ - cb => setTimeout(() => cb(null, 1), 300), - cb => setTimeout(() => cb(null, 2), 200), - cb => setTimeout(() => cb(null, 3), 100), - ], (error, results) => { - if (error) { - console.error(error) - } else { - console.log(results) - } - }) - \ No newline at end of file diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt index 239cea1d41f40..492cbdfcbffc1 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt @@ -1,5 +1,4 @@ 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? -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? 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? 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? 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 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? -==== truthinessCallExpressionCoercion.ts (8 errors) ==== +==== truthinessCallExpressionCoercion.ts (7 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { if (required) { // error ~~~~~~~~ @@ -19,8 +18,6 @@ truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will alw } if (!!required) { // ok - ~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } if (required()) { // ok diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt index 9c972898bb280..978a1565af7f9 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt @@ -1,12 +1,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? -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? 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? 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? 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? 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? -==== truthinessCallExpressionCoercion1.ts (6 errors) ==== +==== truthinessCallExpressionCoercion1.ts (5 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { // error required ? console.log('required') : undefined; @@ -18,8 +17,6 @@ truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will al // ok !!required ? console.log('not required') : undefined; - ~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required() ? console.log('required call') : undefined; diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt index 977ff398c8daf..d0cb3fd84095e 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt @@ -1,6 +1,5 @@ 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? 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? -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? 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? 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? 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 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? -==== truthinessCallExpressionCoercion2.ts (36 errors) ==== +==== truthinessCallExpressionCoercion2.ts (35 errors) ==== declare class A { static from(): string; } @@ -70,8 +69,6 @@ truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will // ok !!required1 && console.log('not required'); - ~~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required1() && console.log('required call'); From 5eb8478d689f5ff03dcf3e38590665e61c7025b7 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 26 Sep 2024 09:08:57 +0100 Subject: [PATCH 04/14] Go back to checking everything for `!` case --- src/compiler/checker.ts | 7 +- ...xtualOverloadListFromArrayUnion.errors.txt | 69 +++++++++++++++++++ ...ruthinessCallExpressionCoercion.errors.txt | 5 +- ...uthinessCallExpressionCoercion1.errors.txt | 5 +- ...uthinessCallExpressionCoercion2.errors.txt | 5 +- 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d69520edef593..55a9ed65e9fb9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44422,10 +44422,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { let location = condExpr - let checkAwaitOnly = false; + // let checkAwaitOnly = false; while (isPrefixUnaryExpression(location)) { location = skipParentheses(location.operand) - checkAwaitOnly = true; + // checkAwaitOnly = true; } location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; if (isModuleExportsAccessExpression(location)) { @@ -44474,7 +44474,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getTypeNameForErrorDisplay(type), ); } - else if (!checkAwaitOnly) { + // else if (!checkAwaitOnly) { + else { error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); } } diff --git a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt new file mode 100644 index 0000000000000..7def0f968e165 --- /dev/null +++ b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt @@ -0,0 +1,69 @@ +three.ts(28,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + + +==== one.ts (0 errors) ==== + declare const y: never[] | string[]; + export const yThen = y.map(item => item.length); +==== two.ts (0 errors) ==== + declare const y: number[][] | string[]; + export const yThen = y.map(item => item.length); +==== three.ts (1 errors) ==== + // #42504 + interface ResizeObserverCallback { + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + interface ResizeObserverCallback { // duplicate for effect + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; + } + + const resizeObserver = new ResizeObserver(([entry]) => { + entry + }); + // comment in #35501 + interface Callback { + (error: null, result: T): unknown + (error: Error, result: null): unknown + } + + interface Task { + (callback: Callback): unknown + } + + export function series(tasks: Task[], callback: Callback): void { + let index = 0 + let results: T[] = [] + + function next() { + let task = tasks[index] + if (!task) { + ~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + callback(null, results) + } else { + task((error, result) => { + if (error) { + callback(error, null) + } else { + // must use postfix-!, since `error` and `result` don't have a + // causal relationship when the overloads are combined + results.push(result!) + next() + } + }) + } + } + next() + } + + series([ + cb => setTimeout(() => cb(null, 1), 300), + cb => setTimeout(() => cb(null, 2), 200), + cb => setTimeout(() => cb(null, 3), 100), + ], (error, results) => { + if (error) { + console.error(error) + } else { + console.log(results) + } + }) + \ No newline at end of file diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt index 492cbdfcbffc1..239cea1d41f40 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt @@ -1,4 +1,5 @@ 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? +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? 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? 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? 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? @@ -7,7 +8,7 @@ truthinessCallExpressionCoercion.ts(76,9): error TS2774: This condition will alw 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? -==== truthinessCallExpressionCoercion.ts (7 errors) ==== +==== truthinessCallExpressionCoercion.ts (8 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { if (required) { // error ~~~~~~~~ @@ -18,6 +19,8 @@ truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will alw } if (!!required) { // ok + ~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } if (required()) { // ok diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt index 978a1565af7f9..9c972898bb280 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt @@ -1,11 +1,12 @@ 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? +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? 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? 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? 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? 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? -==== truthinessCallExpressionCoercion1.ts (5 errors) ==== +==== truthinessCallExpressionCoercion1.ts (6 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { // error required ? console.log('required') : undefined; @@ -17,6 +18,8 @@ truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will al // ok !!required ? console.log('not required') : undefined; + ~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required() ? console.log('required call') : undefined; diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt index d0cb3fd84095e..977ff398c8daf 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt @@ -1,5 +1,6 @@ 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? 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? +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? 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? 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? 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? @@ -35,7 +36,7 @@ truthinessCallExpressionCoercion2.ts(180,9): error TS2774: This condition will a 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? -==== truthinessCallExpressionCoercion2.ts (35 errors) ==== +==== truthinessCallExpressionCoercion2.ts (36 errors) ==== declare class A { static from(): string; } @@ -69,6 +70,8 @@ truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will // ok !!required1 && console.log('not required'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required1() && console.log('required call'); From 96301476de5e03c14dd097ca252ef7a66594498b Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 26 Sep 2024 09:15:14 +0100 Subject: [PATCH 05/14] Run formatter --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 72aa338eb2652..1cb1eb828efee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44551,10 +44551,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { - let location = condExpr + let location = condExpr; // let checkAwaitOnly = false; while (isPrefixUnaryExpression(location)) { - location = skipParentheses(location.operand) + location = skipParentheses(location.operand); // checkAwaitOnly = true; } location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; From 16ae8dcf1145461a344b274da2927ce9fb684849 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 29 Dec 2024 21:07:31 +0000 Subject: [PATCH 06/14] Switch to checking for assignment when inverted --- src/compiler/checker.ts | 46 +++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1cb1eb828efee..520631a8ff841 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44539,30 +44539,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { if (!strictNullChecks) return; - bothHelper(condExpr, condType, body); + bothHelper(condExpr, body); - function bothHelper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { + function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { condExpr = skipParentheses(condExpr); - helper(condExpr, condType, body); + helper(condExpr, body); while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { condExpr = skipParentheses(condExpr.left); - helper(condExpr, condType, body); + helper(condExpr, body); } } - function helper(condExpr: Expression, condType: Type, body: Expression | Statement | undefined) { + function helper(condExpr: Expression, body: Expression | Statement | undefined) { let location = condExpr; - // let checkAwaitOnly = false; + let inverted = false; while (isPrefixUnaryExpression(location)) { location = skipParentheses(location.operand); - // checkAwaitOnly = true; + inverted = !inverted; } location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; if (isModuleExportsAccessExpression(location)) { return; } if (isLogicalOrCoalescingBinaryExpression(location)) { - bothHelper(location, condType, body); + bothHelper(location, body); return; } const type = location === condExpr ? condType : checkExpression(location); @@ -44593,8 +44593,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } + 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); const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) - || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); + || isUsedInBody; + if (!isUsed) { if (isPromise) { errorAndMaybeSuggestAwait( @@ -44604,7 +44606,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getTypeNameForErrorDisplay(type), ); } - // else if (!checkAwaitOnly) { else { error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); } @@ -44612,15 +44613,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { + function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression | Node, testedNode: Node, testedSymbol: Symbol, checkForAssignment: boolean): boolean { return !!forEachChild(body, function check(childNode): boolean | undefined { if (isIdentifier(childNode)) { const childSymbol = getSymbolAtLocation(childNode); if (childSymbol && childSymbol === testedSymbol) { - // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { - return true; + if (checkForAssignment) { + if (isIdentifier(expr) && isBinaryExpression(childNode.parent) && childNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return true; + } } + else { + // If the test was a simple identifier, the above check is sufficient + if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { + return true; + } + } + // Otherwise we need to ensure the symbol is called on the same target let testedExpression = testedNode.parent; let childExpression = childNode.parent; @@ -44629,7 +44638,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isIdentifier(testedExpression) && isIdentifier(childExpression) || testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword ) { - return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); + const sameSymbol = getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); + if (sameSymbol) { + if (checkForAssignment) { + return isPropertyAccessExpression(childNode.parent) && isBinaryExpression(childExpression.parent.parent) && childExpression.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken; + } + return true; + } + return false; } else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { From 8f8b45ccc92a3b77b7374e90c1867095a0a699f7 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Mon, 30 Dec 2024 21:59:48 +0000 Subject: [PATCH 07/14] Only check single inversion --- src/compiler/checker.ts | 4 ++-- .../truthinessCallExpressionCoercion.errors.txt | 5 +---- .../truthinessCallExpressionCoercion1.errors.txt | 5 +---- .../truthinessCallExpressionCoercion2.errors.txt | 5 +---- .../reference/truthinessPromiseCoercion.errors.txt | 10 +--------- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 520631a8ff841..74c11b9f51c04 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44553,9 +44553,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function helper(condExpr: Expression, body: Expression | Statement | undefined) { let location = condExpr; let inverted = false; - while (isPrefixUnaryExpression(location)) { + if (isPrefixUnaryExpression(location)) { location = skipParentheses(location.operand); - inverted = !inverted; + inverted = true; } location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; if (isModuleExportsAccessExpression(location)) { diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt index 239cea1d41f40..492cbdfcbffc1 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion.errors.txt @@ -1,5 +1,4 @@ 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? -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? 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? 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? 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 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? -==== truthinessCallExpressionCoercion.ts (8 errors) ==== +==== truthinessCallExpressionCoercion.ts (7 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { if (required) { // error ~~~~~~~~ @@ -19,8 +18,6 @@ truthinessCallExpressionCoercion.ts(82,9): error TS2774: This condition will alw } if (!!required) { // ok - ~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } if (required()) { // ok diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt index 9c972898bb280..978a1565af7f9 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt @@ -1,12 +1,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? -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? 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? 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? 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? 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? -==== truthinessCallExpressionCoercion1.ts (6 errors) ==== +==== truthinessCallExpressionCoercion1.ts (5 errors) ==== function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { // error required ? console.log('required') : undefined; @@ -18,8 +17,6 @@ truthinessCallExpressionCoercion1.ts(76,9): error TS2774: This condition will al // ok !!required ? console.log('not required') : undefined; - ~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required() ? console.log('required call') : undefined; diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt index 977ff398c8daf..d0cb3fd84095e 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt @@ -1,6 +1,5 @@ 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? 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? -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? 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? 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? 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 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? -==== truthinessCallExpressionCoercion2.ts (36 errors) ==== +==== truthinessCallExpressionCoercion2.ts (35 errors) ==== declare class A { static from(): string; } @@ -70,8 +69,6 @@ truthinessCallExpressionCoercion2.ts(183,14): error TS2774: This condition will // ok !!required1 && console.log('not required'); - ~~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? // ok required1() && console.log('required call'); diff --git a/tests/baselines/reference/truthinessPromiseCoercion.errors.txt b/tests/baselines/reference/truthinessPromiseCoercion.errors.txt index 8907fcb574c2b..9a7a1e17a158c 100644 --- a/tests/baselines/reference/truthinessPromiseCoercion.errors.txt +++ b/tests/baselines/reference/truthinessPromiseCoercion.errors.txt @@ -1,13 +1,11 @@ truthinessPromiseCoercion.ts(7,9): error TS2801: This condition will always return true since this 'Promise' is always defined. -truthinessPromiseCoercion.ts(8,11): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(11,5): error TS2801: This condition will always return true since this 'Promise' is always defined. -truthinessPromiseCoercion.ts(12,7): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(32,9): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(40,9): error TS2801: This condition will always return true since this 'Promise' is always defined. truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always return true since this 'Promise' is always defined. -==== truthinessPromiseCoercion.ts (7 errors) ==== +==== truthinessPromiseCoercion.ts (5 errors) ==== declare const p: Promise declare const p2: null | Promise declare const obj: { p: Promise } @@ -19,9 +17,6 @@ truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always ret !!! error TS2801: This condition will always return true since this 'Promise' is always defined. !!! related TS2773 truthinessPromiseCoercion.ts:7:9: Did you forget to use 'await'? if (!!p) {} // no err - ~ -!!! error TS2801: This condition will always return true since this 'Promise' is always defined. -!!! related TS2773 truthinessPromiseCoercion.ts:8:11: Did you forget to use 'await'? if (p2) {} // no err p ? f.arguments : f.arguments; @@ -29,9 +24,6 @@ truthinessPromiseCoercion.ts(43,9): error TS2801: This condition will always ret !!! error TS2801: This condition will always return true since this 'Promise' is always defined. !!! related TS2773 truthinessPromiseCoercion.ts:11:5: Did you forget to use 'await'? !!p ? f.arguments : f.arguments; - ~ -!!! error TS2801: This condition will always return true since this 'Promise' is always defined. -!!! related TS2773 truthinessPromiseCoercion.ts:12:7: Did you forget to use 'await'? p2 ? f.arguments : f.arguments; } From 70b70fb10c392fa99bf791cdd77c084b426b9ebe Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Mon, 30 Dec 2024 22:24:31 +0000 Subject: [PATCH 08/14] Make it work for the `if (!fnOrPromise)` case instead of just `if (!object.fnOrPromise)` --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 74c11b9f51c04..290f15529be1f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44593,7 +44593,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } - 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); + const isUsedInBody = inverted && isIfStatement(condExpr.parent) ? testedSymbol && isSymbolUsedInConditionBody(location, condExpr.parent.parent, testedNode, testedSymbol, /*checkForAssignment*/ true) : testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol, /*checkForAssignment*/ false); const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) || isUsedInBody; From fcb0c21fef4187ba03831463bbe47490e05aa3aa Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 18:24:00 +0000 Subject: [PATCH 09/14] Just check if it's referenced later, and if so ignore --- src/compiler/checker.ts | 61 ++-- ...falsinessCallExpressionCoercion.errors.txt | 112 +++++++ .../falsinessCallExpressionCoercion.js | 175 ++++++++++ .../falsinessCallExpressionCoercion.symbols | 179 +++++++++++ .../falsinessCallExpressionCoercion.types | 298 ++++++++++++++++++ .../falsinessPromiseCoercion.errors.txt | 106 +++++++ .../reference/falsinessPromiseCoercion.js | 158 ++++++++++ .../falsinessPromiseCoercion.symbols | 165 ++++++++++ .../reference/falsinessPromiseCoercion.types | 285 +++++++++++++++++ .../falsinessCallExpressionCoercion.ts | 95 ++++++ .../compiler/falsinessPromiseCoercion.ts | 89 ++++++ 11 files changed, 1694 insertions(+), 29 deletions(-) create mode 100644 tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt create mode 100644 tests/baselines/reference/falsinessCallExpressionCoercion.js create mode 100644 tests/baselines/reference/falsinessCallExpressionCoercion.symbols create mode 100644 tests/baselines/reference/falsinessCallExpressionCoercion.types create mode 100644 tests/baselines/reference/falsinessPromiseCoercion.errors.txt create mode 100644 tests/baselines/reference/falsinessPromiseCoercion.js create mode 100644 tests/baselines/reference/falsinessPromiseCoercion.symbols create mode 100644 tests/baselines/reference/falsinessPromiseCoercion.types create mode 100644 tests/cases/compiler/falsinessCallExpressionCoercion.ts create mode 100644 tests/cases/compiler/falsinessPromiseCoercion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 290f15529be1f..86315c5301f9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44536,25 +44536,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkSourceElement(node.elseStatement); } - function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(inputCondExpr: Expression, condType: Type, body?: Statement | Expression) { if (!strictNullChecks) return; - bothHelper(condExpr, body); + bothHelper(inputCondExpr); - function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { + function bothHelper(condExpr: Expression) { condExpr = skipParentheses(condExpr); - helper(condExpr, body); + helper(condExpr); while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { condExpr = skipParentheses(condExpr.left); - helper(condExpr, body); + helper(condExpr); } } - function helper(condExpr: Expression, body: Expression | Statement | undefined) { - let location = condExpr; + function helper(condExpr: Expression) { let inverted = false; - if (isPrefixUnaryExpression(location)) { - location = skipParentheses(location.operand); + let location = condExpr; + if (isPrefixUnaryExpression(condExpr) && condExpr.operator === SyntaxKind.ExclamationToken) { + if (!isIfStatement(condExpr.parent)) { + return; + } + location = skipParentheses(condExpr.operand); inverted = true; } location = isLogicalOrCoalescingBinaryExpression(location) ? skipParentheses(location.right) : location; @@ -44562,10 +44565,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } if (isLogicalOrCoalescingBinaryExpression(location)) { - bothHelper(location, body); + bothHelper(location); return; } - const type = location === condExpr ? condType : checkExpression(location); + const type = location === inputCondExpr ? condType : checkExpression(location); if (type.flags & TypeFlags.EnumLiteral && isPropertyAccessExpression(location) && (getNodeLinks(location.expression).resolvedSymbol ?? unknownSymbol).flags & SymbolFlags.Enum) { // EnumLiteral type at condition with known value is always truthy or always falsy, likely an error error(location, Diagnostics.This_condition_will_always_return_0, !!(type as LiteralType).value ? "true" : "false"); @@ -44580,8 +44583,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // are too many false positives for values sourced from type // definitions without strictNullChecks otherwise. const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + const isFnCall = callSignatures.length > 0; const isPromise = !!getAwaitedTypeOfPromise(type); - if (callSignatures.length === 0 && !isPromise) { + if (!isFnCall && !isPromise) { return; } @@ -44593,9 +44597,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } - const isUsedInBody = inverted && isIfStatement(condExpr.parent) ? testedSymbol && isSymbolUsedInConditionBody(location, condExpr.parent.parent, testedNode, testedSymbol, /*checkForAssignment*/ true) : testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol, /*checkForAssignment*/ false); - const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) - || isUsedInBody; + let isUsed: boolean; + if (inverted) { + const ifStatementContainerBody = condExpr.parent.parent; + const isUsedLater = !!(testedSymbol && isSymbolUsedInBody(location, ifStatementContainerBody, testedNode, testedSymbol, condExpr.end + 1)); + isUsed = isUsedLater; + } + else { + const isUsedInBody = !!(testedSymbol && body && isSymbolUsedInBody(location, body, testedNode, testedSymbol, 0)); + isUsed = isUsedInBody || !!(testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol)); + } if (!isUsed) { if (isPromise) { @@ -44613,21 +44624,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression | Node, testedNode: Node, testedSymbol: Symbol, checkForAssignment: boolean): boolean { + function isSymbolUsedInBody(expr: Expression, body: Statement | Expression | Node, testedNode: Node, testedSymbol: Symbol, startPos: number): boolean { return !!forEachChild(body, function check(childNode): boolean | undefined { + if (childNode.pos < startPos) return false; + if (isIdentifier(childNode)) { const childSymbol = getSymbolAtLocation(childNode); if (childSymbol && childSymbol === testedSymbol) { - if (checkForAssignment) { - if (isIdentifier(expr) && isBinaryExpression(childNode.parent) && childNode.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - return true; - } - } - else { - // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { - return true; - } + // If the test was a simple identifier, the above check is sufficient + if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { + return true; } // Otherwise we need to ensure the symbol is called on the same target @@ -44640,9 +44646,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) { const sameSymbol = getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); if (sameSymbol) { - if (checkForAssignment) { - return isPropertyAccessExpression(childNode.parent) && isBinaryExpression(childExpression.parent.parent) && childExpression.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken; - } return true; } return false; diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt new file mode 100644 index 0000000000000..41f95ad977b4e --- /dev/null +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt @@ -0,0 +1,112 @@ +falsinessCallExpressionCoercion.ts(4,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(25,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(41,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(66,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(91,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + + +==== falsinessCallExpressionCoercion.ts (5 errors) ==== + function test1() { + function canAccess() { return false; } + + if (!canAccess) { // error + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + } + + function test2() { + function canAccess() { return false; } + + if (!canAccess) { // ok + } + + canAccess(); + } + + function test3() { + function canAccess() { return false; } + + if (!!!canAccess) { // ok + } + } + + function test4(canAccess: () => boolean) { + if (!canAccess) { // error + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + } + + function test5(canAccess?: () => boolean) { + if (!canAccess) { // ok + } + } + + function test6() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // error + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + } + + function test7() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // ok + } + + x.foo.bar(); + } + + class Test8 { + maybeIsUser?: () => boolean; + + isUser() { + return true; + } + + test() { + if (!this.isUser) { // error + ~~~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + + if (!this.maybeIsUser) { // ok + } + } + } + + class Test9 { + isUser() { + return true; + } + + test() { + if (!this.isUser) { // ok + } + + this.isUser(); + } + } + + function test10() { + function canAccess() { return false; } + + const res = canAccess + if (!res) { // error + ~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.js b/tests/baselines/reference/falsinessCallExpressionCoercion.js new file mode 100644 index 0000000000000..73d4432377a24 --- /dev/null +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.js @@ -0,0 +1,175 @@ +//// [tests/cases/compiler/falsinessCallExpressionCoercion.ts] //// + +//// [falsinessCallExpressionCoercion.ts] +function test1() { + function canAccess() { return false; } + + if (!canAccess) { // error + } +} + +function test2() { + function canAccess() { return false; } + + if (!canAccess) { // ok + } + + canAccess(); +} + +function test3() { + function canAccess() { return false; } + + if (!!!canAccess) { // ok + } +} + +function test4(canAccess: () => boolean) { + if (!canAccess) { // error + } +} + +function test5(canAccess?: () => boolean) { + if (!canAccess) { // ok + } +} + +function test6() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // error + } +} + +function test7() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // ok + } + + x.foo.bar(); +} + +class Test8 { + maybeIsUser?: () => boolean; + + isUser() { + return true; + } + + test() { + if (!this.isUser) { // error + } + + if (!this.maybeIsUser) { // ok + } + } +} + +class Test9 { + isUser() { + return true; + } + + test() { + if (!this.isUser) { // ok + } + + this.isUser(); + } +} + +function test10() { + function canAccess() { return false; } + + const res = canAccess + if (!res) { // error + } +} + + +//// [falsinessCallExpressionCoercion.js] +function test1() { + function canAccess() { return false; } + if (!canAccess) { // error + } +} +function test2() { + function canAccess() { return false; } + if (!canAccess) { // ok + } + canAccess(); +} +function test3() { + function canAccess() { return false; } + if (!!!canAccess) { // ok + } +} +function test4(canAccess) { + if (!canAccess) { // error + } +} +function test5(canAccess) { + if (!canAccess) { // ok + } +} +function test6() { + var x = { + foo: { + bar: function () { return true; } + } + }; + if (!x.foo.bar) { // error + } +} +function test7() { + var x = { + foo: { + bar: function () { return true; } + } + }; + if (!x.foo.bar) { // ok + } + x.foo.bar(); +} +var Test8 = /** @class */ (function () { + function Test8() { + } + Test8.prototype.isUser = function () { + return true; + }; + Test8.prototype.test = function () { + if (!this.isUser) { // error + } + if (!this.maybeIsUser) { // ok + } + }; + return Test8; +}()); +var Test9 = /** @class */ (function () { + function Test9() { + } + Test9.prototype.isUser = function () { + return true; + }; + Test9.prototype.test = function () { + if (!this.isUser) { // ok + } + this.isUser(); + }; + return Test9; +}()); +function test10() { + function canAccess() { return false; } + var res = canAccess; + if (!res) { // error + } +} diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.symbols b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols new file mode 100644 index 0000000000000..745bead5aadf3 --- /dev/null +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols @@ -0,0 +1,179 @@ +//// [tests/cases/compiler/falsinessCallExpressionCoercion.ts] //// + +=== falsinessCallExpressionCoercion.ts === +function test1() { +>test1 : Symbol(test1, Decl(falsinessCallExpressionCoercion.ts, 0, 0)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 0, 18)) + + if (!canAccess) { // error +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 0, 18)) + } +} + +function test2() { +>test2 : Symbol(test2, Decl(falsinessCallExpressionCoercion.ts, 5, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 7, 18)) + + if (!canAccess) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 7, 18)) + } + + canAccess(); +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 7, 18)) +} + +function test3() { +>test3 : Symbol(test3, Decl(falsinessCallExpressionCoercion.ts, 14, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 16, 18)) + + if (!!!canAccess) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 16, 18)) + } +} + +function test4(canAccess: () => boolean) { +>test4 : Symbol(test4, Decl(falsinessCallExpressionCoercion.ts, 21, 1)) +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 23, 15)) + + if (!canAccess) { // error +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 23, 15)) + } +} + +function test5(canAccess?: () => boolean) { +>test5 : Symbol(test5, Decl(falsinessCallExpressionCoercion.ts, 26, 1)) +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 28, 15)) + + if (!canAccess) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 28, 15)) + } +} + +function test6() { +>test6 : Symbol(test6, Decl(falsinessCallExpressionCoercion.ts, 31, 1)) + + const x = { +>x : Symbol(x, Decl(falsinessCallExpressionCoercion.ts, 34, 9)) + + foo: { +>foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 34, 15)) + + bar() { return true; } +>bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 35, 14)) + } + }; + + if (!x.foo.bar) { // error +>x.foo.bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 35, 14)) +>x.foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 34, 15)) +>x : Symbol(x, Decl(falsinessCallExpressionCoercion.ts, 34, 9)) +>foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 34, 15)) +>bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 35, 14)) + } +} + +function test7() { +>test7 : Symbol(test7, Decl(falsinessCallExpressionCoercion.ts, 42, 1)) + + const x = { +>x : Symbol(x, Decl(falsinessCallExpressionCoercion.ts, 45, 9)) + + foo: { +>foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 45, 15)) + + bar() { return true; } +>bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 46, 14)) + } + }; + + if (!x.foo.bar) { // ok +>x.foo.bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 46, 14)) +>x.foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 45, 15)) +>x : Symbol(x, Decl(falsinessCallExpressionCoercion.ts, 45, 9)) +>foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 45, 15)) +>bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 46, 14)) + } + + x.foo.bar(); +>x.foo.bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 46, 14)) +>x.foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 45, 15)) +>x : Symbol(x, Decl(falsinessCallExpressionCoercion.ts, 45, 9)) +>foo : Symbol(foo, Decl(falsinessCallExpressionCoercion.ts, 45, 15)) +>bar : Symbol(bar, Decl(falsinessCallExpressionCoercion.ts, 46, 14)) +} + +class Test8 { +>Test8 : Symbol(Test8, Decl(falsinessCallExpressionCoercion.ts, 55, 1)) + + maybeIsUser?: () => boolean; +>maybeIsUser : Symbol(Test8.maybeIsUser, Decl(falsinessCallExpressionCoercion.ts, 57, 13)) + + isUser() { +>isUser : Symbol(Test8.isUser, Decl(falsinessCallExpressionCoercion.ts, 58, 32)) + + return true; + } + + test() { +>test : Symbol(Test8.test, Decl(falsinessCallExpressionCoercion.ts, 62, 5)) + + if (!this.isUser) { // error +>this.isUser : Symbol(Test8.isUser, Decl(falsinessCallExpressionCoercion.ts, 58, 32)) +>this : Symbol(Test8, Decl(falsinessCallExpressionCoercion.ts, 55, 1)) +>isUser : Symbol(Test8.isUser, Decl(falsinessCallExpressionCoercion.ts, 58, 32)) + } + + if (!this.maybeIsUser) { // ok +>this.maybeIsUser : Symbol(Test8.maybeIsUser, Decl(falsinessCallExpressionCoercion.ts, 57, 13)) +>this : Symbol(Test8, Decl(falsinessCallExpressionCoercion.ts, 55, 1)) +>maybeIsUser : Symbol(Test8.maybeIsUser, Decl(falsinessCallExpressionCoercion.ts, 57, 13)) + } + } +} + +class Test9 { +>Test9 : Symbol(Test9, Decl(falsinessCallExpressionCoercion.ts, 71, 1)) + + isUser() { +>isUser : Symbol(Test9.isUser, Decl(falsinessCallExpressionCoercion.ts, 73, 13)) + + return true; + } + + test() { +>test : Symbol(Test9.test, Decl(falsinessCallExpressionCoercion.ts, 76, 5)) + + if (!this.isUser) { // ok +>this.isUser : Symbol(Test9.isUser, Decl(falsinessCallExpressionCoercion.ts, 73, 13)) +>this : Symbol(Test9, Decl(falsinessCallExpressionCoercion.ts, 71, 1)) +>isUser : Symbol(Test9.isUser, Decl(falsinessCallExpressionCoercion.ts, 73, 13)) + } + + this.isUser(); +>this.isUser : Symbol(Test9.isUser, Decl(falsinessCallExpressionCoercion.ts, 73, 13)) +>this : Symbol(Test9, Decl(falsinessCallExpressionCoercion.ts, 71, 1)) +>isUser : Symbol(Test9.isUser, Decl(falsinessCallExpressionCoercion.ts, 73, 13)) + } +} + +function test10() { +>test10 : Symbol(test10, Decl(falsinessCallExpressionCoercion.ts, 84, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 86, 19)) + + const res = canAccess +>res : Symbol(res, Decl(falsinessCallExpressionCoercion.ts, 89, 9)) +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 86, 19)) + + if (!res) { // error +>res : Symbol(res, Decl(falsinessCallExpressionCoercion.ts, 89, 9)) + } +} + diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.types b/tests/baselines/reference/falsinessCallExpressionCoercion.types new file mode 100644 index 0000000000000..22cd40adc0037 --- /dev/null +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.types @@ -0,0 +1,298 @@ +//// [tests/cases/compiler/falsinessCallExpressionCoercion.ts] //// + +=== falsinessCallExpressionCoercion.ts === +function test1() { +>test1 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess) { // error +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + } +} + +function test2() { +>test2 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess) { // ok +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + } + + canAccess(); +>canAccess() : boolean +> : ^^^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +} + +function test3() { +>test3 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!!!canAccess) { // ok +>!!!canAccess : false +> : ^^^^^ +>!!canAccess : true +> : ^^^^ +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + } +} + +function test4(canAccess: () => boolean) { +>test4 : (canAccess: () => boolean) => void +> : ^ ^^ ^^^^^^^^^ +>canAccess : () => boolean +> : ^^^^^^ + + if (!canAccess) { // error +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^ + } +} + +function test5(canAccess?: () => boolean) { +>test5 : (canAccess?: () => boolean) => void +> : ^ ^^^ ^^^^^^^^^ +>canAccess : (() => boolean) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ + + if (!canAccess) { // ok +>!canAccess : boolean +> : ^^^^^^^ +>canAccess : (() => boolean) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ + } +} + +function test6() { +>test6 : () => void +> : ^^^^^^^^^^ + + const x = { +>x : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ foo: { bar() { return true; } } } : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + foo: { +>foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>{ bar() { return true; } } : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ + + bar() { return true; } +>bar : () => boolean +> : ^^^^^^^^^^^^^ +>true : true +> : ^^^^ + } + }; + + if (!x.foo.bar) { // error +>!x.foo.bar : false +> : ^^^^^ +>x.foo.bar : () => boolean +> : ^^^^^^^^^^^^^ +>x.foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>x : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>bar : () => boolean +> : ^^^^^^^^^^^^^ + } +} + +function test7() { +>test7 : () => void +> : ^^^^^^^^^^ + + const x = { +>x : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ foo: { bar() { return true; } } } : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + foo: { +>foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>{ bar() { return true; } } : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ + + bar() { return true; } +>bar : () => boolean +> : ^^^^^^^^^^^^^ +>true : true +> : ^^^^ + } + }; + + if (!x.foo.bar) { // ok +>!x.foo.bar : false +> : ^^^^^ +>x.foo.bar : () => boolean +> : ^^^^^^^^^^^^^ +>x.foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>x : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>bar : () => boolean +> : ^^^^^^^^^^^^^ + } + + x.foo.bar(); +>x.foo.bar() : boolean +> : ^^^^^^^ +>x.foo.bar : () => boolean +> : ^^^^^^^^^^^^^ +>x.foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>x : { foo: { bar(): boolean; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : { bar(): boolean; } +> : ^^^^^^^^^^^^^^^^^^^ +>bar : () => boolean +> : ^^^^^^^^^^^^^ +} + +class Test8 { +>Test8 : Test8 +> : ^^^^^ + + maybeIsUser?: () => boolean; +>maybeIsUser : (() => boolean) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ + + isUser() { +>isUser : () => boolean +> : ^^^^^^^^^^^^^ + + return true; +>true : true +> : ^^^^ + } + + test() { +>test : () => void +> : ^^^^^^^^^^ + + if (!this.isUser) { // error +>!this.isUser : false +> : ^^^^^ +>this.isUser : () => boolean +> : ^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>isUser : () => boolean +> : ^^^^^^^^^^^^^ + } + + if (!this.maybeIsUser) { // ok +>!this.maybeIsUser : boolean +> : ^^^^^^^ +>this.maybeIsUser : (() => boolean) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>maybeIsUser : (() => boolean) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ + } + } +} + +class Test9 { +>Test9 : Test9 +> : ^^^^^ + + isUser() { +>isUser : () => boolean +> : ^^^^^^^^^^^^^ + + return true; +>true : true +> : ^^^^ + } + + test() { +>test : () => void +> : ^^^^^^^^^^ + + if (!this.isUser) { // ok +>!this.isUser : false +> : ^^^^^ +>this.isUser : () => boolean +> : ^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>isUser : () => boolean +> : ^^^^^^^^^^^^^ + } + + this.isUser(); +>this.isUser() : boolean +> : ^^^^^^^ +>this.isUser : () => boolean +> : ^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>isUser : () => boolean +> : ^^^^^^^^^^^^^ + } +} + +function test10() { +>test10 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + const res = canAccess +>res : () => boolean +> : ^^^^^^^^^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + + if (!res) { // error +>!res : false +> : ^^^^^ +>res : () => boolean +> : ^^^^^^^^^^^^^ + } +} + diff --git a/tests/baselines/reference/falsinessPromiseCoercion.errors.txt b/tests/baselines/reference/falsinessPromiseCoercion.errors.txt new file mode 100644 index 0000000000000..8c489579198b4 --- /dev/null +++ b/tests/baselines/reference/falsinessPromiseCoercion.errors.txt @@ -0,0 +1,106 @@ +falsinessPromiseCoercion.ts(4,10): error TS2801: This condition will always return true since this 'Promise' is always defined. +falsinessPromiseCoercion.ts(28,10): error TS2801: This condition will always return true since this 'Promise' is always defined. +falsinessPromiseCoercion.ts(44,10): error TS2801: This condition will always return true since this 'Promise' is always defined. +falsinessPromiseCoercion.ts(69,14): error TS2801: This condition will always return true since this 'Promise' is always defined. + + +==== falsinessPromiseCoercion.ts (4 errors) ==== + function test1() { + async function canAccess() { return false; } + + if (!canAccess()) { // error + ~~~~~~~~~~~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 falsinessPromiseCoercion.ts:4:10: Did you forget to use 'await'? + } + } + + async function test2() { + async function canAccess() { return false; } + + const res = canAccess() + + if (!res) { // ok + return + } + + await res + } + + function test3() { + async function canAccess() { return false; } + + if (!!!canAccess()) { // ok + } + } + + function test4(canAccess: () => Promise) { + if (!canAccess()) { // error + ~~~~~~~~~~~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 falsinessPromiseCoercion.ts:28:10: Did you forget to use 'await'? + } + } + + function test5(canAccess: () => Promise | undefined) { + if (!canAccess()) { // ok + } + } + + function test6() { + const x = { + foo: { + async bar() { return true; } + } + }; + + if (!x.foo.bar()) { // error + ~~~~~~~~~~~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 falsinessPromiseCoercion.ts:44:10: Did you forget to use 'await'? + } + } + + async function test7() { + const x = { + foo: { + async bar() { return true; } + } + }; + + const res = x.foo.bar(); + + if (!res) { // ok + } + + await res; + } + + class Test8 { + async isUser() { + return true; + } + + test() { + if (!this.isUser()) { // error + ~~~~~~~~~~~~~ +!!! error TS2801: This condition will always return true since this 'Promise' is always defined. +!!! related TS2773 falsinessPromiseCoercion.ts:69:14: Did you forget to use 'await'? + } + } + } + + class Test9 { + async isUser() { + return true; + } + + async test() { + const res = this.isUser(); + if (!res) { // ok + } + + await res; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/falsinessPromiseCoercion.js b/tests/baselines/reference/falsinessPromiseCoercion.js new file mode 100644 index 0000000000000..93e67c9f7d496 --- /dev/null +++ b/tests/baselines/reference/falsinessPromiseCoercion.js @@ -0,0 +1,158 @@ +//// [tests/cases/compiler/falsinessPromiseCoercion.ts] //// + +//// [falsinessPromiseCoercion.ts] +function test1() { + async function canAccess() { return false; } + + if (!canAccess()) { // error + } +} + +async function test2() { + async function canAccess() { return false; } + + const res = canAccess() + + if (!res) { // ok + return + } + + await res +} + +function test3() { + async function canAccess() { return false; } + + if (!!!canAccess()) { // ok + } +} + +function test4(canAccess: () => Promise) { + if (!canAccess()) { // error + } +} + +function test5(canAccess: () => Promise | undefined) { + if (!canAccess()) { // ok + } +} + +function test6() { + const x = { + foo: { + async bar() { return true; } + } + }; + + if (!x.foo.bar()) { // error + } +} + +async function test7() { + const x = { + foo: { + async bar() { return true; } + } + }; + + const res = x.foo.bar(); + + if (!res) { // ok + } + + await res; +} + +class Test8 { + async isUser() { + return true; + } + + test() { + if (!this.isUser()) { // error + } + } +} + +class Test9 { + async isUser() { + return true; + } + + async test() { + const res = this.isUser(); + if (!res) { // ok + } + + await res; + } +} + + +//// [falsinessPromiseCoercion.js] +function test1() { + async function canAccess() { return false; } + if (!canAccess()) { // error + } +} +async function test2() { + async function canAccess() { return false; } + const res = canAccess(); + if (!res) { // ok + return; + } + await res; +} +function test3() { + async function canAccess() { return false; } + if (!!!canAccess()) { // ok + } +} +function test4(canAccess) { + if (!canAccess()) { // error + } +} +function test5(canAccess) { + if (!canAccess()) { // ok + } +} +function test6() { + const x = { + foo: { + async bar() { return true; } + } + }; + if (!x.foo.bar()) { // error + } +} +async function test7() { + const x = { + foo: { + async bar() { return true; } + } + }; + const res = x.foo.bar(); + if (!res) { // ok + } + await res; +} +class Test8 { + async isUser() { + return true; + } + test() { + if (!this.isUser()) { // error + } + } +} +class Test9 { + async isUser() { + return true; + } + async test() { + const res = this.isUser(); + if (!res) { // ok + } + await res; + } +} diff --git a/tests/baselines/reference/falsinessPromiseCoercion.symbols b/tests/baselines/reference/falsinessPromiseCoercion.symbols new file mode 100644 index 0000000000000..2ceac9f1d62b0 --- /dev/null +++ b/tests/baselines/reference/falsinessPromiseCoercion.symbols @@ -0,0 +1,165 @@ +//// [tests/cases/compiler/falsinessPromiseCoercion.ts] //// + +=== falsinessPromiseCoercion.ts === +function test1() { +>test1 : Symbol(test1, Decl(falsinessPromiseCoercion.ts, 0, 0)) + + async function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 0, 18)) + + if (!canAccess()) { // error +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 0, 18)) + } +} + +async function test2() { +>test2 : Symbol(test2, Decl(falsinessPromiseCoercion.ts, 5, 1)) + + async function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 7, 24)) + + const res = canAccess() +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 10, 9)) +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 7, 24)) + + if (!res) { // ok +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 10, 9)) + + return + } + + await res +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 10, 9)) +} + +function test3() { +>test3 : Symbol(test3, Decl(falsinessPromiseCoercion.ts, 17, 1)) + + async function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 19, 18)) + + if (!!!canAccess()) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 19, 18)) + } +} + +function test4(canAccess: () => Promise) { +>test4 : Symbol(test4, Decl(falsinessPromiseCoercion.ts, 24, 1)) +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 26, 15)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) + + if (!canAccess()) { // error +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 26, 15)) + } +} + +function test5(canAccess: () => Promise | undefined) { +>test5 : Symbol(test5, Decl(falsinessPromiseCoercion.ts, 29, 1)) +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 31, 15)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) + + if (!canAccess()) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessPromiseCoercion.ts, 31, 15)) + } +} + +function test6() { +>test6 : Symbol(test6, Decl(falsinessPromiseCoercion.ts, 34, 1)) + + const x = { +>x : Symbol(x, Decl(falsinessPromiseCoercion.ts, 37, 9)) + + foo: { +>foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 37, 15)) + + async bar() { return true; } +>bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 38, 14)) + } + }; + + if (!x.foo.bar()) { // error +>x.foo.bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 38, 14)) +>x.foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 37, 15)) +>x : Symbol(x, Decl(falsinessPromiseCoercion.ts, 37, 9)) +>foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 37, 15)) +>bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 38, 14)) + } +} + +async function test7() { +>test7 : Symbol(test7, Decl(falsinessPromiseCoercion.ts, 45, 1)) + + const x = { +>x : Symbol(x, Decl(falsinessPromiseCoercion.ts, 48, 9)) + + foo: { +>foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 48, 15)) + + async bar() { return true; } +>bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 49, 14)) + } + }; + + const res = x.foo.bar(); +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 54, 9)) +>x.foo.bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 49, 14)) +>x.foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 48, 15)) +>x : Symbol(x, Decl(falsinessPromiseCoercion.ts, 48, 9)) +>foo : Symbol(foo, Decl(falsinessPromiseCoercion.ts, 48, 15)) +>bar : Symbol(bar, Decl(falsinessPromiseCoercion.ts, 49, 14)) + + if (!res) { // ok +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 54, 9)) + } + + await res; +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 54, 9)) +} + +class Test8 { +>Test8 : Symbol(Test8, Decl(falsinessPromiseCoercion.ts, 60, 1)) + + async isUser() { +>isUser : Symbol(Test8.isUser, Decl(falsinessPromiseCoercion.ts, 62, 13)) + + return true; + } + + test() { +>test : Symbol(Test8.test, Decl(falsinessPromiseCoercion.ts, 65, 5)) + + if (!this.isUser()) { // error +>this.isUser : Symbol(Test8.isUser, Decl(falsinessPromiseCoercion.ts, 62, 13)) +>this : Symbol(Test8, Decl(falsinessPromiseCoercion.ts, 60, 1)) +>isUser : Symbol(Test8.isUser, Decl(falsinessPromiseCoercion.ts, 62, 13)) + } + } +} + +class Test9 { +>Test9 : Symbol(Test9, Decl(falsinessPromiseCoercion.ts, 71, 1)) + + async isUser() { +>isUser : Symbol(Test9.isUser, Decl(falsinessPromiseCoercion.ts, 73, 13)) + + return true; + } + + async test() { +>test : Symbol(Test9.test, Decl(falsinessPromiseCoercion.ts, 76, 5)) + + const res = this.isUser(); +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 79, 13)) +>this.isUser : Symbol(Test9.isUser, Decl(falsinessPromiseCoercion.ts, 73, 13)) +>this : Symbol(Test9, Decl(falsinessPromiseCoercion.ts, 71, 1)) +>isUser : Symbol(Test9.isUser, Decl(falsinessPromiseCoercion.ts, 73, 13)) + + if (!res) { // ok +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 79, 13)) + } + + await res; +>res : Symbol(res, Decl(falsinessPromiseCoercion.ts, 79, 13)) + } +} + diff --git a/tests/baselines/reference/falsinessPromiseCoercion.types b/tests/baselines/reference/falsinessPromiseCoercion.types new file mode 100644 index 0000000000000..35e1b522b9487 --- /dev/null +++ b/tests/baselines/reference/falsinessPromiseCoercion.types @@ -0,0 +1,285 @@ +//// [tests/cases/compiler/falsinessPromiseCoercion.ts] //// + +=== falsinessPromiseCoercion.ts === +function test1() { +>test1 : () => void +> : ^^^^^^^^^^ + + async function canAccess() { return false; } +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess()) { // error +>!canAccess() : false +> : ^^^^^ +>canAccess() : Promise +> : ^^^^^^^^^^^^^^^^ +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + } +} + +async function test2() { +>test2 : () => Promise +> : ^^^^^^^^^^^^^^^^^^^ + + async function canAccess() { return false; } +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + const res = canAccess() +>res : Promise +> : ^^^^^^^^^^^^^^^^ +>canAccess() : Promise +> : ^^^^^^^^^^^^^^^^ +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + + if (!res) { // ok +>!res : false +> : ^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ + + return + } + + await res +>await res : boolean +> : ^^^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ +} + +function test3() { +>test3 : () => void +> : ^^^^^^^^^^ + + async function canAccess() { return false; } +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!!!canAccess()) { // ok +>!!!canAccess() : false +> : ^^^^^ +>!!canAccess() : true +> : ^^^^ +>!canAccess() : false +> : ^^^^^ +>canAccess() : Promise +> : ^^^^^^^^^^^^^^^^ +>canAccess : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + } +} + +function test4(canAccess: () => Promise) { +>test4 : (canAccess: () => Promise) => void +> : ^ ^^ ^^^^^^^^^ +>canAccess : () => Promise +> : ^^^^^^ + + if (!canAccess()) { // error +>!canAccess() : false +> : ^^^^^ +>canAccess() : Promise +> : ^^^^^^^^^^^^^^^^ +>canAccess : () => Promise +> : ^^^^^^ + } +} + +function test5(canAccess: () => Promise | undefined) { +>test5 : (canAccess: () => Promise | undefined) => void +> : ^ ^^ ^^^^^^^^^ +>canAccess : () => Promise | undefined +> : ^^^^^^ + + if (!canAccess()) { // ok +>!canAccess() : boolean +> : ^^^^^^^ +>canAccess() : Promise | undefined +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>canAccess : () => Promise | undefined +> : ^^^^^^ + } +} + +function test6() { +>test6 : () => void +> : ^^^^^^^^^^ + + const x = { +>x : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ foo: { async bar() { return true; } } } : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + foo: { +>foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ async bar() { return true; } } : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + async bar() { return true; } +>bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ + } + }; + + if (!x.foo.bar()) { // error +>!x.foo.bar() : false +> : ^^^^^ +>x.foo.bar() : Promise +> : ^^^^^^^^^^^^^^^^ +>x.foo.bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>x.foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + } +} + +async function test7() { +>test7 : () => Promise +> : ^^^^^^^^^^^^^^^^^^^ + + const x = { +>x : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ foo: { async bar() { return true; } } } : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + foo: { +>foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ async bar() { return true; } } : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + async bar() { return true; } +>bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ + } + }; + + const res = x.foo.bar(); +>res : Promise +> : ^^^^^^^^^^^^^^^^ +>x.foo.bar() : Promise +> : ^^^^^^^^^^^^^^^^ +>x.foo.bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>x.foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : { foo: { bar(): Promise; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : { bar(): Promise; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>bar : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + + if (!res) { // ok +>!res : false +> : ^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ + } + + await res; +>await res : boolean +> : ^^^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ +} + +class Test8 { +>Test8 : Test8 +> : ^^^^^ + + async isUser() { +>isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + + return true; +>true : true +> : ^^^^ + } + + test() { +>test : () => void +> : ^^^^^^^^^^ + + if (!this.isUser()) { // error +>!this.isUser() : false +> : ^^^^^ +>this.isUser() : Promise +> : ^^^^^^^^^^^^^^^^ +>this.isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + } + } +} + +class Test9 { +>Test9 : Test9 +> : ^^^^^ + + async isUser() { +>isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + + return true; +>true : true +> : ^^^^ + } + + async test() { +>test : () => Promise +> : ^^^^^^^^^^^^^^^^^^^ + + const res = this.isUser(); +>res : Promise +> : ^^^^^^^^^^^^^^^^ +>this.isUser() : Promise +> : ^^^^^^^^^^^^^^^^ +>this.isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>isUser : () => Promise +> : ^^^^^^^^^^^^^^^^^^^^^^ + + if (!res) { // ok +>!res : false +> : ^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ + } + + await res; +>await res : boolean +> : ^^^^^^^ +>res : Promise +> : ^^^^^^^^^^^^^^^^ + } +} + diff --git a/tests/cases/compiler/falsinessCallExpressionCoercion.ts b/tests/cases/compiler/falsinessCallExpressionCoercion.ts new file mode 100644 index 0000000000000..99cdf13bf2f99 --- /dev/null +++ b/tests/cases/compiler/falsinessCallExpressionCoercion.ts @@ -0,0 +1,95 @@ +// @strictNullChecks:true + +function test1() { + function canAccess() { return false; } + + if (!canAccess) { // error + } +} + +function test2() { + function canAccess() { return false; } + + if (!canAccess) { // ok + } + + canAccess(); +} + +function test3() { + function canAccess() { return false; } + + if (!!!canAccess) { // ok + } +} + +function test4(canAccess: () => boolean) { + if (!canAccess) { // error + } +} + +function test5(canAccess?: () => boolean) { + if (!canAccess) { // ok + } +} + +function test6() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // error + } +} + +function test7() { + const x = { + foo: { + bar() { return true; } + } + }; + + if (!x.foo.bar) { // ok + } + + x.foo.bar(); +} + +class Test8 { + maybeIsUser?: () => boolean; + + isUser() { + return true; + } + + test() { + if (!this.isUser) { // error + } + + if (!this.maybeIsUser) { // ok + } + } +} + +class Test9 { + isUser() { + return true; + } + + test() { + if (!this.isUser) { // ok + } + + this.isUser(); + } +} + +function test10() { + function canAccess() { return false; } + + const res = canAccess + if (!res) { // error + } +} diff --git a/tests/cases/compiler/falsinessPromiseCoercion.ts b/tests/cases/compiler/falsinessPromiseCoercion.ts new file mode 100644 index 0000000000000..7f37c1a161a31 --- /dev/null +++ b/tests/cases/compiler/falsinessPromiseCoercion.ts @@ -0,0 +1,89 @@ +// @strictNullChecks:true +// @target:esnext + +function test1() { + async function canAccess() { return false; } + + if (!canAccess()) { // error + } +} + +async function test2() { + async function canAccess() { return false; } + + const res = canAccess() + + if (!res) { // ok + return + } + + await res +} + +function test3() { + async function canAccess() { return false; } + + if (!!!canAccess()) { // ok + } +} + +function test4(canAccess: () => Promise) { + if (!canAccess()) { // error + } +} + +function test5(canAccess: () => Promise | undefined) { + if (!canAccess()) { // ok + } +} + +function test6() { + const x = { + foo: { + async bar() { return true; } + } + }; + + if (!x.foo.bar()) { // error + } +} + +async function test7() { + const x = { + foo: { + async bar() { return true; } + } + }; + + const res = x.foo.bar(); + + if (!res) { // ok + } + + await res; +} + +class Test8 { + async isUser() { + return true; + } + + test() { + if (!this.isUser()) { // error + } + } +} + +class Test9 { + async isUser() { + return true; + } + + async test() { + const res = this.isUser(); + if (!res) { // ok + } + + await res; + } +} From 7a14b9dd405da87a77353a10f8b250572c92a13e Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 19:37:49 +0000 Subject: [PATCH 10/14] Add test that currently fails --- ...falsinessCallExpressionCoercion.errors.txt | 14 ++++++++++- .../falsinessCallExpressionCoercion.js | 17 +++++++++++++ .../falsinessCallExpressionCoercion.symbols | 15 +++++++++++ .../falsinessCallExpressionCoercion.types | 25 +++++++++++++++++++ .../falsinessCallExpressionCoercion.ts | 9 +++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt index 41f95ad977b4e..cff494bc1647a 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt @@ -3,9 +3,10 @@ falsinessCallExpressionCoercion.ts(25,10): error TS2774: This condition will alw falsinessCallExpressionCoercion.ts(41,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(66,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(91,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(98,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? -==== falsinessCallExpressionCoercion.ts (5 errors) ==== +==== falsinessCallExpressionCoercion.ts (6 errors) ==== function test1() { function canAccess() { return false; } @@ -109,4 +110,15 @@ falsinessCallExpressionCoercion.ts(91,10): error TS2774: This condition will alw !!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } } + + function test11() { + function canAccess() { return false; } + + if (!canAccess) { // ok + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } else { + canAccess() + } + } \ No newline at end of file diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.js b/tests/baselines/reference/falsinessCallExpressionCoercion.js index 73d4432377a24..1d7824835363c 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.js +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.js @@ -94,6 +94,15 @@ function test10() { if (!res) { // error } } + +function test11() { + function canAccess() { return false; } + + if (!canAccess) { // ok + } else { + canAccess() + } +} //// [falsinessCallExpressionCoercion.js] @@ -173,3 +182,11 @@ function test10() { if (!res) { // error } } +function test11() { + function canAccess() { return false; } + if (!canAccess) { // ok + } + else { + canAccess(); + } +} diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.symbols b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols index 745bead5aadf3..680be62506026 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.symbols +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols @@ -177,3 +177,18 @@ function test10() { } } +function test11() { +>test11 : Symbol(test11, Decl(falsinessCallExpressionCoercion.ts, 92, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 94, 19)) + + if (!canAccess) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 94, 19)) + + } else { + canAccess() +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 94, 19)) + } +} + diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.types b/tests/baselines/reference/falsinessCallExpressionCoercion.types index 22cd40adc0037..cb2ae2fe665ed 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.types +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.types @@ -296,3 +296,28 @@ function test10() { } } +function test11() { +>test11 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess) { // ok +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + + } else { + canAccess() +>canAccess() : boolean +> : ^^^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ + } +} + diff --git a/tests/cases/compiler/falsinessCallExpressionCoercion.ts b/tests/cases/compiler/falsinessCallExpressionCoercion.ts index 99cdf13bf2f99..db2de2d88ba8f 100644 --- a/tests/cases/compiler/falsinessCallExpressionCoercion.ts +++ b/tests/cases/compiler/falsinessCallExpressionCoercion.ts @@ -93,3 +93,12 @@ function test10() { if (!res) { // error } } + +function test11() { + function canAccess() { return false; } + + if (!canAccess) { // ok + } else { + canAccess() + } +} From 4e47c53465f89ed199a7a8e785194c8f519cff67 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 20:18:51 +0000 Subject: [PATCH 11/14] Find the nearest block --- src/compiler/checker.ts | 17 +++-- ...falsinessCallExpressionCoercion.errors.txt | 21 ++++++- .../falsinessCallExpressionCoercion.js | 27 ++++++++ .../falsinessCallExpressionCoercion.symbols | 31 ++++++++++ .../falsinessCallExpressionCoercion.types | 62 +++++++++++++++++++ .../falsinessCallExpressionCoercion.ts | 16 +++++ 6 files changed, 168 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 86315c5301f9a..c46262e40604a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44554,9 +44554,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let inverted = false; let location = condExpr; if (isPrefixUnaryExpression(condExpr) && condExpr.operator === SyntaxKind.ExclamationToken) { - if (!isIfStatement(condExpr.parent)) { - return; - } location = skipParentheses(condExpr.operand); inverted = true; } @@ -44599,8 +44596,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let isUsed: boolean; if (inverted) { - const ifStatementContainerBody = condExpr.parent.parent; - const isUsedLater = !!(testedSymbol && isSymbolUsedInBody(location, ifStatementContainerBody, testedNode, testedSymbol, condExpr.end + 1)); + let closestBlock: Block | undefined; + { + let current: Node = condExpr.parent; + while (current) { + if (isBlock(current)) { + closestBlock = current; + break; + } + current = current.parent; + } + } + const isUsedLater = !!(testedSymbol && closestBlock && isSymbolUsedInBody(location, closestBlock, testedNode, testedSymbol, condExpr.end + 1)); isUsed = isUsedLater; } else { diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt index cff494bc1647a..c3052b0d38afc 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt @@ -4,9 +4,10 @@ falsinessCallExpressionCoercion.ts(41,10): error TS2774: This condition will alw falsinessCallExpressionCoercion.ts(66,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(91,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(98,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +falsinessCallExpressionCoercion.ts(107,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? -==== falsinessCallExpressionCoercion.ts (6 errors) ==== +==== falsinessCallExpressionCoercion.ts (7 errors) ==== function test1() { function canAccess() { return false; } @@ -121,4 +122,22 @@ falsinessCallExpressionCoercion.ts(98,10): error TS2774: This condition will alw canAccess() } } + + function test12() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // error + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + } + } + + function test13() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // ok + } + + canAccess() + } \ No newline at end of file diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.js b/tests/baselines/reference/falsinessCallExpressionCoercion.js index 1d7824835363c..d72eb4d26a7cd 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.js +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.js @@ -103,6 +103,22 @@ function test11() { canAccess() } } + +function test12() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // error + } +} + +function test13() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // ok + } + + canAccess() +} //// [falsinessCallExpressionCoercion.js] @@ -190,3 +206,14 @@ function test11() { canAccess(); } } +function test12() { + function canAccess() { return false; } + if (!canAccess || Math.random()) { // error + } +} +function test13() { + function canAccess() { return false; } + if (!canAccess || Math.random()) { // ok + } + canAccess(); +} diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.symbols b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols index 680be62506026..c6430ed13cfee 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.symbols +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.symbols @@ -192,3 +192,34 @@ function test11() { } } +function test12() { +>test12 : Symbol(test12, Decl(falsinessCallExpressionCoercion.ts, 101, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 103, 19)) + + if (!canAccess || Math.random()) { // error +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 103, 19)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + } +} + +function test13() { +>test13 : Symbol(test13, Decl(falsinessCallExpressionCoercion.ts, 108, 1)) + + function canAccess() { return false; } +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 110, 19)) + + if (!canAccess || Math.random()) { // ok +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 110, 19)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + } + + canAccess() +>canAccess : Symbol(canAccess, Decl(falsinessCallExpressionCoercion.ts, 110, 19)) +} + diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.types b/tests/baselines/reference/falsinessCallExpressionCoercion.types index cb2ae2fe665ed..f9fd32c6c2239 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.types +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.types @@ -321,3 +321,65 @@ function test11() { } } +function test12() { +>test12 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess || Math.random()) { // error +>!canAccess || Math.random() : number +> : ^^^^^^ +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>Math.random() : number +> : ^^^^^^ +>Math.random : () => number +> : ^^^^^^ +>Math : Math +> : ^^^^ +>random : () => number +> : ^^^^^^ + } +} + +function test13() { +>test13 : () => void +> : ^^^^^^^^^^ + + function canAccess() { return false; } +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (!canAccess || Math.random()) { // ok +>!canAccess || Math.random() : number +> : ^^^^^^ +>!canAccess : false +> : ^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +>Math.random() : number +> : ^^^^^^ +>Math.random : () => number +> : ^^^^^^ +>Math : Math +> : ^^^^ +>random : () => number +> : ^^^^^^ + } + + canAccess() +>canAccess() : boolean +> : ^^^^^^^ +>canAccess : () => boolean +> : ^^^^^^^^^^^^^ +} + diff --git a/tests/cases/compiler/falsinessCallExpressionCoercion.ts b/tests/cases/compiler/falsinessCallExpressionCoercion.ts index db2de2d88ba8f..5922f66d8ae5f 100644 --- a/tests/cases/compiler/falsinessCallExpressionCoercion.ts +++ b/tests/cases/compiler/falsinessCallExpressionCoercion.ts @@ -102,3 +102,19 @@ function test11() { canAccess() } } + +function test12() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // error + } +} + +function test13() { + function canAccess() { return false; } + + if (!canAccess || Math.random()) { // ok + } + + canAccess() +} From 5099e8898cce376636447e7b8b0a97ff8266c2c7 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 20:56:39 +0000 Subject: [PATCH 12/14] Fix looping through all children --- src/compiler/checker.ts | 4 +- ...xtualOverloadListFromArrayUnion.errors.txt | 69 ------------------- ...falsinessCallExpressionCoercion.errors.txt | 5 +- 3 files changed, 2 insertions(+), 76 deletions(-) delete mode 100644 tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c46262e40604a..5ef53f991fa19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44633,9 +44633,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isSymbolUsedInBody(expr: Expression, body: Statement | Expression | Node, testedNode: Node, testedSymbol: Symbol, startPos: number): boolean { return !!forEachChild(body, function check(childNode): boolean | undefined { - if (childNode.pos < startPos) return false; - - if (isIdentifier(childNode)) { + if (childNode.pos >= startPos && isIdentifier(childNode)) { const childSymbol = getSymbolAtLocation(childNode); if (childSymbol && childSymbol === testedSymbol) { // If the test was a simple identifier, the above check is sufficient diff --git a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt b/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt deleted file mode 100644 index 7def0f968e165..0000000000000 --- a/tests/baselines/reference/contextualOverloadListFromArrayUnion.errors.txt +++ /dev/null @@ -1,69 +0,0 @@ -three.ts(28,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? - - -==== one.ts (0 errors) ==== - declare const y: never[] | string[]; - export const yThen = y.map(item => item.length); -==== two.ts (0 errors) ==== - declare const y: number[][] | string[]; - export const yThen = y.map(item => item.length); -==== three.ts (1 errors) ==== - // #42504 - interface ResizeObserverCallback { - (entries: ResizeObserverEntry[], observer: ResizeObserver): void; - } - interface ResizeObserverCallback { // duplicate for effect - (entries: ResizeObserverEntry[], observer: ResizeObserver): void; - } - - const resizeObserver = new ResizeObserver(([entry]) => { - entry - }); - // comment in #35501 - interface Callback { - (error: null, result: T): unknown - (error: Error, result: null): unknown - } - - interface Task { - (callback: Callback): unknown - } - - export function series(tasks: Task[], callback: Callback): void { - let index = 0 - let results: T[] = [] - - function next() { - let task = tasks[index] - if (!task) { - ~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? - callback(null, results) - } else { - task((error, result) => { - if (error) { - callback(error, null) - } else { - // must use postfix-!, since `error` and `result` don't have a - // causal relationship when the overloads are combined - results.push(result!) - next() - } - }) - } - } - next() - } - - series([ - cb => setTimeout(() => cb(null, 1), 300), - cb => setTimeout(() => cb(null, 2), 200), - cb => setTimeout(() => cb(null, 3), 100), - ], (error, results) => { - if (error) { - console.error(error) - } else { - console.log(results) - } - }) - \ No newline at end of file diff --git a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt index c3052b0d38afc..e1b08cedb9a7d 100644 --- a/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt +++ b/tests/baselines/reference/falsinessCallExpressionCoercion.errors.txt @@ -3,11 +3,10 @@ falsinessCallExpressionCoercion.ts(25,10): error TS2774: This condition will alw falsinessCallExpressionCoercion.ts(41,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(66,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(91,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? -falsinessCallExpressionCoercion.ts(98,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? falsinessCallExpressionCoercion.ts(107,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? -==== falsinessCallExpressionCoercion.ts (7 errors) ==== +==== falsinessCallExpressionCoercion.ts (6 errors) ==== function test1() { function canAccess() { return false; } @@ -116,8 +115,6 @@ falsinessCallExpressionCoercion.ts(107,10): error TS2774: This condition will al function canAccess() { return false; } if (!canAccess) { // ok - ~~~~~~~~~ -!!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? } else { canAccess() } From 75f941d59f95deb7b01b1cea6925eff40b73f7e5 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 20:56:53 +0000 Subject: [PATCH 13/14] Use `findAncestor` --- src/compiler/checker.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5ef53f991fa19..381c713f0dc5d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44596,17 +44596,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let isUsed: boolean; if (inverted) { - let closestBlock: Block | undefined; - { - let current: Node = condExpr.parent; - while (current) { - if (isBlock(current)) { - closestBlock = current; - break; - } - current = current.parent; - } - } + const closestBlock = findAncestor(condExpr.parent, isBlock); const isUsedLater = !!(testedSymbol && closestBlock && isSymbolUsedInBody(location, closestBlock, testedNode, testedSymbol, condExpr.end + 1)); isUsed = isUsedLater; } From de9972484c1075d158b0ca02bd66c02fc14e5fee Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Sun, 26 Jan 2025 20:59:21 +0000 Subject: [PATCH 14/14] Resimplify check --- src/compiler/checker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 381c713f0dc5d..97ac71d9d65b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44639,11 +44639,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isIdentifier(testedExpression) && isIdentifier(childExpression) || testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword ) { - const sameSymbol = getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); - if (sameSymbol) { - return true; - } - return false; + return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); } else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) {