Skip to content

Commit d345d44

Browse files
committed
feat: error on forget to use await on Promise in "if" condition
1 parent 60a6240 commit d345d44

8 files changed

+213
-8
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31267,6 +31267,7 @@ namespace ts {
3126731267
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
3126831268
const type = checkTruthinessExpression(node.condition);
3126931269
checkTestingKnownTruthyCallableType(node.condition, type, node.whenTrue);
31270+
checkTestingKnownTruthyAwaitableType(node.condition, type);
3127031271
const type1 = checkExpression(node.whenTrue, checkMode);
3127131272
const type2 = checkExpression(node.whenFalse, checkMode);
3127231273
return getUnionType([type1, type2], UnionReduction.Subtype);
@@ -34518,6 +34519,7 @@ namespace ts {
3451834519
checkGrammarStatementInAmbientContext(node);
3451934520
const type = checkTruthinessExpression(node.expression);
3452034521
checkTestingKnownTruthyCallableType(node.expression, type, node.thenStatement);
34522+
checkTestingKnownTruthyAwaitableType(node.expression, type);
3452134523
checkSourceElement(node.thenStatement);
3452234524

3452334525
if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
@@ -34527,7 +34529,18 @@ namespace ts {
3452734529
checkSourceElement(node.elseStatement);
3452834530
}
3452934531

34530-
function checkTestingKnownTruthyCallableType(condExpr: Expression, type: Type, body: Statement | Expression | undefined) {
34532+
function checkTestingKnownTruthyAwaitableType(condExpr: Expression, type: Type) {
34533+
if (!strictNullChecks) return;
34534+
if (getFalsyFlags(type)) return;
34535+
if (!getAwaitedTypeOfPromise(type)) return;
34536+
errorAndMaybeSuggestAwait(
34537+
condExpr,
34538+
/*maybeMissingAwait*/ true,
34539+
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap,
34540+
"true", getTypeNameForErrorDisplay(type), "false");
34541+
}
34542+
34543+
function checkTestingKnownTruthyCallableType(condExpr: Expression, type: Type, body?: Statement | Expression) {
3453134544
if (!strictNullChecks) {
3453234545
return;
3453334546
}
@@ -34538,14 +34551,8 @@ namespace ts {
3453834551
: isBinaryExpression(location) && isIdentifier(location.right) ? location.right
3453934552
: undefined;
3454034553

34541-
if (!testedNode) {
34542-
return;
34543-
}
34554+
if (!testedNode) return;
3454434555

34545-
const possiblyFalsy = getFalsyFlags(type);
34546-
if (possiblyFalsy) {
34547-
return;
34548-
}
3454934556

3455034557
// While it technically should be invalid for any known-truthy value
3455134558
// to be tested, we de-scope to functions unrefenced in the block as a

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3256,6 +3256,10 @@
32563256
"category": "Error",
32573257
"code": 2800
32583258
},
3259+
"This condition will always return true since the Promise is always truthy.": {
3260+
"category": "Error",
3261+
"code": 2801
3262+
},
32593263

32603264
"Import declaration '{0}' is using private name '{1}'.": {
32613265
"category": "Error",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tests/cases/compiler/truthinessPromiseCoercion.ts(5,9): error TS2367: This condition will always return 'true' since the types 'Promise<number>' and 'false' have no overlap.
2+
tests/cases/compiler/truthinessPromiseCoercion.ts(9,5): error TS2367: This condition will always return 'true' since the types 'Promise<number>' and 'false' have no overlap.
3+
4+
5+
==== tests/cases/compiler/truthinessPromiseCoercion.ts (2 errors) ====
6+
declare const p: Promise<number>
7+
declare const p2: null | Promise<number>
8+
9+
async function f() {
10+
if (p) {} // err
11+
~
12+
!!! error TS2367: This condition will always return 'true' since the types 'Promise<number>' and 'false' have no overlap.
13+
!!! related TS2773 tests/cases/compiler/truthinessPromiseCoercion.ts:5:9: Did you forget to use 'await'?
14+
if (!!p) {} // no err
15+
if (p2) {} // no err
16+
17+
p ? f.arguments : f.arguments;
18+
~
19+
!!! error TS2367: This condition will always return 'true' since the types 'Promise<number>' and 'false' have no overlap.
20+
!!! related TS2773 tests/cases/compiler/truthinessPromiseCoercion.ts:9:5: Did you forget to use 'await'?
21+
!!p ? f.arguments : f.arguments;
22+
p2 ? f.arguments : f.arguments;
23+
}
24+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [truthinessPromiseCoercion.ts]
2+
declare const p: Promise<number>
3+
declare const p2: null | Promise<number>
4+
5+
async function f() {
6+
if (p) {} // err
7+
if (!!p) {} // no err
8+
if (p2) {} // no err
9+
10+
p ? f.arguments : f.arguments;
11+
!!p ? f.arguments : f.arguments;
12+
p2 ? f.arguments : f.arguments;
13+
}
14+
15+
16+
//// [truthinessPromiseCoercion.js]
17+
async function f() {
18+
if (p) { } // err
19+
if (!!p) { } // no err
20+
if (p2) { } // no err
21+
p ? f.arguments : f.arguments;
22+
!!p ? f.arguments : f.arguments;
23+
p2 ? f.arguments : f.arguments;
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/truthinessPromiseCoercion.ts ===
2+
declare const p: Promise<number>
3+
>p : Symbol(p, Decl(truthinessPromiseCoercion.ts, 0, 13))
4+
>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, --, --))
5+
6+
declare const p2: null | Promise<number>
7+
>p2 : Symbol(p2, Decl(truthinessPromiseCoercion.ts, 1, 13))
8+
>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, --, --))
9+
10+
async function f() {
11+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
12+
13+
if (p) {} // err
14+
>p : Symbol(p, Decl(truthinessPromiseCoercion.ts, 0, 13))
15+
16+
if (!!p) {} // no err
17+
>p : Symbol(p, Decl(truthinessPromiseCoercion.ts, 0, 13))
18+
19+
if (p2) {} // no err
20+
>p2 : Symbol(p2, Decl(truthinessPromiseCoercion.ts, 1, 13))
21+
22+
p ? f.arguments : f.arguments;
23+
>p : Symbol(p, Decl(truthinessPromiseCoercion.ts, 0, 13))
24+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
25+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
26+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
27+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
28+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
29+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
30+
31+
!!p ? f.arguments : f.arguments;
32+
>p : Symbol(p, Decl(truthinessPromiseCoercion.ts, 0, 13))
33+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
34+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
35+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
36+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
37+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
38+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
39+
40+
p2 ? f.arguments : f.arguments;
41+
>p2 : Symbol(p2, Decl(truthinessPromiseCoercion.ts, 1, 13))
42+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
43+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
44+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
45+
>f.arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
46+
>f : Symbol(f, Decl(truthinessPromiseCoercion.ts, 1, 40))
47+
>arguments : Symbol(Function.arguments, Decl(lib.es5.d.ts, --, --))
48+
}
49+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/compiler/truthinessPromiseCoercion.ts ===
2+
declare const p: Promise<number>
3+
>p : Promise<number>
4+
5+
declare const p2: null | Promise<number>
6+
>p2 : Promise<number> | null
7+
>null : null
8+
9+
async function f() {
10+
>f : () => Promise<void>
11+
12+
if (p) {} // err
13+
>p : Promise<number>
14+
15+
if (!!p) {} // no err
16+
>!!p : true
17+
>!p : false
18+
>p : Promise<number>
19+
20+
if (p2) {} // no err
21+
>p2 : Promise<number> | null
22+
23+
p ? f.arguments : f.arguments;
24+
>p ? f.arguments : f.arguments : any
25+
>p : Promise<number>
26+
>f.arguments : any
27+
>f : () => Promise<void>
28+
>arguments : any
29+
>f.arguments : any
30+
>f : () => Promise<void>
31+
>arguments : any
32+
33+
!!p ? f.arguments : f.arguments;
34+
>!!p ? f.arguments : f.arguments : any
35+
>!!p : true
36+
>!p : false
37+
>p : Promise<number>
38+
>f.arguments : any
39+
>f : () => Promise<void>
40+
>arguments : any
41+
>f.arguments : any
42+
>f : () => Promise<void>
43+
>arguments : any
44+
45+
p2 ? f.arguments : f.arguments;
46+
>p2 ? f.arguments : f.arguments : any
47+
>p2 : Promise<number> | null
48+
>f.arguments : any
49+
>f : () => Promise<void>
50+
>arguments : any
51+
>f.arguments : any
52+
>f : () => Promise<void>
53+
>arguments : any
54+
}
55+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @strictNullChecks:true
2+
// @target:esnext
3+
4+
declare const p: Promise<number>
5+
declare const p2: null | Promise<number>
6+
7+
async function f() {
8+
if (p) {} // err
9+
if (!!p) {} // no err
10+
if (p2) {} // no err
11+
12+
p ? f.arguments : f.arguments;
13+
!!p ? f.arguments : f.arguments;
14+
p2 ? f.arguments : f.arguments;
15+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @strictNullChecks: true
4+
////async function fn(a: Promise<string[]>) {
5+
//// if (a) {};
6+
//// a ? fn.call() : fn.call();
7+
////}
8+
9+
verify.codeFix({
10+
description: ts.Diagnostics.Add_await.message,
11+
index: 0,
12+
newFileContent:
13+
`async function fn(a: Promise<string[]>) {
14+
if (await a) {};
15+
a ? fn.call() : fn.call();
16+
}`
17+
});
18+
19+
verify.codeFix({
20+
description: ts.Diagnostics.Add_await.message,
21+
index: 1,
22+
newFileContent:
23+
`async function fn(a: Promise<string[]>) {
24+
if (a) {};
25+
await a ? fn.call() : fn.call();
26+
}`
27+
});

0 commit comments

Comments
 (0)