Skip to content

Commit 907ce8f

Browse files
committed
unwrap promised typed in async function before doing 'noImplicitReturns' check
1 parent ac147b1 commit 907ce8f

File tree

8 files changed

+257
-7
lines changed

8 files changed

+257
-7
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10813,11 +10813,11 @@ namespace ts {
1081310813
// If return type annotation is omitted check if function has any explicit return statements.
1081410814
// If it does not have any - its inferred return type is void - don't do any checks.
1081510815
// Otherwise get inferred return type from function body and report error only if it is not void / anytype
10816-
const inferredReturnType = hasExplicitReturn
10817-
? getReturnTypeOfSignature(getSignatureFromDeclaration(func))
10818-
: voidType;
10819-
10820-
if (inferredReturnType === voidType || isTypeAny(inferredReturnType)) {
10816+
if (!hasExplicitReturn) {
10817+
return;
10818+
}
10819+
const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
10820+
if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) {
1082110821
return;
1082210822
}
1082310823
}
@@ -12696,7 +12696,7 @@ namespace ts {
1269612696
return checkNonThenableType(type, location, message);
1269712697
}
1269812698
else {
12699-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
12699+
if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) {
1270012700
// We have a bad actor in the form of a promise whose promised type is
1270112701
// the same promise type, or a mutually recursive promise. Return the
1270212702
// unknown type as we cannot guess the shape. If this were the actual
@@ -13849,6 +13849,11 @@ namespace ts {
1384913849
return !!(node.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(<AccessorDeclaration>getDeclarationOfKind(node.symbol, SyntaxKind.SetAccessor)));
1385013850
}
1385113851

13852+
function isUnwrappedReturnTypeVoidOrAny(func: FunctionLikeDeclaration, returnType: Type): boolean {
13853+
const unwrappedReturnType = isAsyncFunctionLike(func) ? getPromisedType(returnType) : returnType;
13854+
return maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.Any);
13855+
}
13856+
1385213857
function checkReturnStatement(node: ReturnStatement) {
1385313858
// Grammar checking
1385413859
if (!checkGrammarStatementInAmbientContext(node)) {
@@ -13897,7 +13902,7 @@ namespace ts {
1389713902
}
1389813903
}
1389913904
}
13900-
else if (compilerOptions.noImplicitReturns && !maybeTypeOfKind(returnType, TypeFlags.Void | TypeFlags.Any)) {
13905+
else if (compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) {
1390113906
// The function has a return type, but the return statement doesn't have an expression.
1390213907
error(node, Diagnostics.Not_all_code_paths_return_a_value);
1390313908
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [noImplicitReturnsInAsync1.ts]
2+
3+
async function test(isError: boolean = false) {
4+
if (isError === true) {
5+
return;
6+
}
7+
let x = await Promise.resolve("The test is passed without an error.");
8+
}
9+
10+
//// [noImplicitReturnsInAsync1.js]
11+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12+
return new (P || (P = Promise))(function (resolve, reject) {
13+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14+
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
15+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
16+
step((generator = generator.apply(thisArg, _arguments)).next());
17+
});
18+
};
19+
function test(isError = false) {
20+
return __awaiter(this, void 0, void 0, function* () {
21+
if (isError === true) {
22+
return;
23+
}
24+
let x = yield Promise.resolve("The test is passed without an error.");
25+
});
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/compiler/noImplicitReturnsInAsync1.ts ===
2+
3+
async function test(isError: boolean = false) {
4+
>test : Symbol(test, Decl(noImplicitReturnsInAsync1.ts, 0, 0))
5+
>isError : Symbol(isError, Decl(noImplicitReturnsInAsync1.ts, 1, 20))
6+
7+
if (isError === true) {
8+
>isError : Symbol(isError, Decl(noImplicitReturnsInAsync1.ts, 1, 20))
9+
10+
return;
11+
}
12+
let x = await Promise.resolve("The test is passed without an error.");
13+
>x : Symbol(x, Decl(noImplicitReturnsInAsync1.ts, 5, 7))
14+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
15+
>Promise : Symbol(Promise, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
16+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/compiler/noImplicitReturnsInAsync1.ts ===
2+
3+
async function test(isError: boolean = false) {
4+
>test : (isError?: boolean) => Promise<void>
5+
>isError : boolean
6+
>false : boolean
7+
8+
if (isError === true) {
9+
>isError === true : boolean
10+
>isError : boolean
11+
>true : boolean
12+
13+
return;
14+
}
15+
let x = await Promise.resolve("The test is passed without an error.");
16+
>x : string
17+
>await Promise.resolve("The test is passed without an error.") : string
18+
>Promise.resolve("The test is passed without an error.") : Promise<string>
19+
>Promise.resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
20+
>Promise : PromiseConstructor
21+
>resolve : { <T>(value: T | PromiseLike<T>): Promise<T>; (): Promise<void>; }
22+
>"The test is passed without an error." : string
23+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
tests/cases/compiler/noImplicitReturnsInAsync2.ts(3,16): error TS7030: Not all code paths return a value.
2+
tests/cases/compiler/noImplicitReturnsInAsync2.ts(25,48): error TS7030: Not all code paths return a value.
3+
4+
5+
==== tests/cases/compiler/noImplicitReturnsInAsync2.ts (2 errors) ====
6+
7+
// Should be an error, Promise<number>, currently retorted correctly
8+
async function test3(isError: boolean = true) {
9+
~~~~~
10+
!!! error TS7030: Not all code paths return a value.
11+
if (isError === true) {
12+
return 6;
13+
}
14+
}
15+
16+
// Should not be an error, Promise<any>, currently **not** working
17+
async function test4(isError: boolean = true) {
18+
if (isError === true) {
19+
return undefined;
20+
}
21+
}
22+
23+
// should not be error, Promise<any> currently working correctly
24+
async function test5(isError: boolean = true): Promise<any> { //should not be error
25+
if (isError === true) {
26+
return undefined;
27+
}
28+
}
29+
30+
31+
// should be error, currently reported correctly
32+
async function test6(isError: boolean = true): Promise<number> {
33+
~~~~~~~~~~~~~~~
34+
!!! error TS7030: Not all code paths return a value.
35+
if (isError === true) {
36+
return undefined;
37+
}
38+
}
39+
40+
// infered to be Promise<void>, should not be an error, currently reported correctly
41+
async function test7(isError: boolean = true) {
42+
if (isError === true) {
43+
return;
44+
}
45+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//// [noImplicitReturnsInAsync2.ts]
2+
3+
// Should be an error, Promise<number>, currently retorted correctly
4+
async function test3(isError: boolean = true) {
5+
if (isError === true) {
6+
return 6;
7+
}
8+
}
9+
10+
// Should not be an error, Promise<any>, currently **not** working
11+
async function test4(isError: boolean = true) {
12+
if (isError === true) {
13+
return undefined;
14+
}
15+
}
16+
17+
// should not be error, Promise<any> currently working correctly
18+
async function test5(isError: boolean = true): Promise<any> { //should not be error
19+
if (isError === true) {
20+
return undefined;
21+
}
22+
}
23+
24+
25+
// should be error, currently reported correctly
26+
async function test6(isError: boolean = true): Promise<number> {
27+
if (isError === true) {
28+
return undefined;
29+
}
30+
}
31+
32+
// infered to be Promise<void>, should not be an error, currently reported correctly
33+
async function test7(isError: boolean = true) {
34+
if (isError === true) {
35+
return;
36+
}
37+
}
38+
39+
//// [noImplicitReturnsInAsync2.js]
40+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
41+
return new (P || (P = Promise))(function (resolve, reject) {
42+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
43+
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
44+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
45+
step((generator = generator.apply(thisArg, _arguments)).next());
46+
});
47+
};
48+
// Should be an error, Promise<number>, currently retorted correctly
49+
function test3(isError = true) {
50+
return __awaiter(this, void 0, void 0, function* () {
51+
if (isError === true) {
52+
return 6;
53+
}
54+
});
55+
}
56+
// Should not be an error, Promise<any>, currently **not** working
57+
function test4(isError = true) {
58+
return __awaiter(this, void 0, void 0, function* () {
59+
if (isError === true) {
60+
return undefined;
61+
}
62+
});
63+
}
64+
// should not be error, Promise<any> currently working correctly
65+
function test5(isError = true) {
66+
return __awaiter(this, void 0, void 0, function* () {
67+
if (isError === true) {
68+
return undefined;
69+
}
70+
});
71+
}
72+
// should be error, currently reported correctly
73+
function test6(isError = true) {
74+
return __awaiter(this, void 0, void 0, function* () {
75+
if (isError === true) {
76+
return undefined;
77+
}
78+
});
79+
}
80+
// infered to be Promise<void>, should not be an error, currently reported correctly
81+
function test7(isError = true) {
82+
return __awaiter(this, void 0, void 0, function* () {
83+
if (isError === true) {
84+
return;
85+
}
86+
});
87+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @target: es6
2+
// @noImplicitReturns: true
3+
4+
async function test(isError: boolean = false) {
5+
if (isError === true) {
6+
return;
7+
}
8+
let x = await Promise.resolve("The test is passed without an error.");
9+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// @target: es6
2+
// @noImplicitReturns: true
3+
4+
// Should be an error, Promise<number>, currently retorted correctly
5+
async function test3(isError: boolean = true) {
6+
if (isError === true) {
7+
return 6;
8+
}
9+
}
10+
11+
// Should not be an error, Promise<any>, currently **not** working
12+
async function test4(isError: boolean = true) {
13+
if (isError === true) {
14+
return undefined;
15+
}
16+
}
17+
18+
// should not be error, Promise<any> currently working correctly
19+
async function test5(isError: boolean = true): Promise<any> { //should not be error
20+
if (isError === true) {
21+
return undefined;
22+
}
23+
}
24+
25+
26+
// should be error, currently reported correctly
27+
async function test6(isError: boolean = true): Promise<number> {
28+
if (isError === true) {
29+
return undefined;
30+
}
31+
}
32+
33+
// infered to be Promise<void>, should not be an error, currently reported correctly
34+
async function test7(isError: boolean = true) {
35+
if (isError === true) {
36+
return;
37+
}
38+
}

0 commit comments

Comments
 (0)