Skip to content

Commit 5d5fee2

Browse files
committed
Merge pull request microsoft#7450 from Microsoft/noImplicitReturnsInAsync
unwrap promised typed in async function before doing 'noImplicitRetur…
2 parents 614afb7 + 907ce8f commit 5d5fee2

8 files changed

Lines changed: 257 additions & 7 deletions

src/compiler/checker.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10860,11 +10860,11 @@ namespace ts {
1086010860
// If return type annotation is omitted check if function has any explicit return statements.
1086110861
// If it does not have any - its inferred return type is void - don't do any checks.
1086210862
// Otherwise get inferred return type from function body and report error only if it is not void / anytype
10863-
const inferredReturnType = hasExplicitReturn
10864-
? getReturnTypeOfSignature(getSignatureFromDeclaration(func))
10865-
: voidType;
10866-
10867-
if (inferredReturnType === voidType || isTypeAny(inferredReturnType)) {
10863+
if (!hasExplicitReturn) {
10864+
return;
10865+
}
10866+
const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
10867+
if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) {
1086810868
return;
1086910869
}
1087010870
}
@@ -12743,7 +12743,7 @@ namespace ts {
1274312743
return checkNonThenableType(type, location, message);
1274412744
}
1274512745
else {
12746-
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
12746+
if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) {
1274712747
// We have a bad actor in the form of a promise whose promised type is
1274812748
// the same promise type, or a mutually recursive promise. Return the
1274912749
// unknown type as we cannot guess the shape. If this were the actual
@@ -13896,6 +13896,11 @@ namespace ts {
1389613896
return !!(node.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(<AccessorDeclaration>getDeclarationOfKind(node.symbol, SyntaxKind.SetAccessor)));
1389713897
}
1389813898

13899+
function isUnwrappedReturnTypeVoidOrAny(func: FunctionLikeDeclaration, returnType: Type): boolean {
13900+
const unwrappedReturnType = isAsyncFunctionLike(func) ? getPromisedType(returnType) : returnType;
13901+
return maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.Any);
13902+
}
13903+
1389913904
function checkReturnStatement(node: ReturnStatement) {
1390013905
// Grammar checking
1390113906
if (!checkGrammarStatementInAmbientContext(node)) {
@@ -13944,7 +13949,7 @@ namespace ts {
1394413949
}
1394513950
}
1394613951
}
13947-
else if (compilerOptions.noImplicitReturns && !maybeTypeOfKind(returnType, TypeFlags.Void | TypeFlags.Any)) {
13952+
else if (compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) {
1394813953
// The function has a return type, but the return statement doesn't have an expression.
1394913954
error(node, Diagnostics.Not_all_code_paths_return_a_value);
1395013955
}
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)