Skip to content

Commit 650a176

Browse files
elibarzilaytypescript-bot
authored andcommitted
Cherry-pick PR microsoft#36654 into release-3.9
Component commits: 766a201 feat(36266): add a quick fix for incorrect return types in async functions 889e82b Make `getAwaitedType` private Also, fix an additional baseline change and break up huge line.
1 parent 90570df commit 650a176

27 files changed

+336
-20
lines changed

src/compiler/checker.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ namespace ts {
406406
},
407407
getParameterType: getTypeAtPosition,
408408
getPromisedTypeOfPromise,
409+
getAwaitedType: type => getAwaitedType(type),
409410
getReturnTypeOfSignature,
410411
isNullableType,
411412
getNullableType,
@@ -30695,7 +30696,7 @@ namespace ts {
3069530696
if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) {
3069630697
// The promise type was not a valid type reference to the global promise type, so we
3069730698
// report an error and return the unknown type.
30698-
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
30699+
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedType(returnType) || voidType));
3069930700
return;
3070030701
}
3070130702
}

src/compiler/diagnosticMessages.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@
215215
"category": "Error",
216216
"code": 1063
217217
},
218-
"The return type of an async function or method must be the global Promise<T> type.": {
218+
"The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{0}>'?": {
219219
"category": "Error",
220220
"code": 1064
221221
},
@@ -5189,6 +5189,14 @@
51895189
"category": "Message",
51905190
"code": 90035
51915191
},
5192+
"Replace '{0}' with 'Promise<{1}>'": {
5193+
"category": "Message",
5194+
"code": 90036
5195+
},
5196+
"Fix all incorrect return type of an async functions": {
5197+
"category": "Message",
5198+
"code": 90037
5199+
},
51925200
"Declare a private field named '{0}'.": {
51935201
"category": "Message",
51945202
"code": 90053

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3421,6 +3421,8 @@ namespace ts {
34213421
getWidenedType(type: Type): Type;
34223422
/* @internal */
34233423
getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined;
3424+
/* @internal */
3425+
getAwaitedType(type: Type): Type | undefined;
34243426
getReturnTypeOfSignature(signature: Signature): Type;
34253427
/**
34263428
* Gets the type of a parameter at a given position in a signature.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "fixReturnTypeInAsyncFunction";
4+
const errorCodes = [
5+
Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0.code,
6+
];
7+
8+
interface Info {
9+
readonly returnTypeNode: TypeNode;
10+
readonly returnType: Type;
11+
readonly promisedTypeNode: TypeNode;
12+
readonly promisedType: Type;
13+
}
14+
15+
registerCodeFix({
16+
errorCodes,
17+
fixIds: [fixId],
18+
getCodeActions: context => {
19+
const { sourceFile, program, span } = context;
20+
const checker = program.getTypeChecker();
21+
const info = getInfo(sourceFile, program.getTypeChecker(), span.start);
22+
if (!info) {
23+
return undefined;
24+
}
25+
const { returnTypeNode, returnType, promisedTypeNode, promisedType } = info;
26+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, returnTypeNode, promisedTypeNode));
27+
return [createCodeFixAction(
28+
fixId, changes,
29+
[Diagnostics.Replace_0_with_Promise_1,
30+
checker.typeToString(returnType), checker.typeToString(promisedType)],
31+
fixId, Diagnostics.Fix_all_incorrect_return_type_of_an_async_functions)];
32+
},
33+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
34+
const info = getInfo(diag.file, context.program.getTypeChecker(), diag.start);
35+
if (info) {
36+
doChange(changes, diag.file, info.returnTypeNode, info.promisedTypeNode);
37+
}
38+
})
39+
});
40+
41+
function getInfo(sourceFile: SourceFile, checker: TypeChecker, pos: number): Info | undefined {
42+
const returnTypeNode = getReturnTypeNode(sourceFile, pos);
43+
if (!returnTypeNode) {
44+
return undefined;
45+
}
46+
47+
const returnType = checker.getTypeFromTypeNode(returnTypeNode);
48+
const promisedType = checker.getAwaitedType(returnType) || checker.getVoidType();
49+
const promisedTypeNode = checker.typeToTypeNode(promisedType);
50+
if (promisedTypeNode) {
51+
return { returnTypeNode, returnType, promisedTypeNode, promisedType };
52+
}
53+
}
54+
55+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, returnTypeNode: TypeNode, promisedTypeNode: TypeNode): void {
56+
changes.replaceNode(sourceFile, returnTypeNode, createTypeReferenceNode("Promise", [promisedTypeNode]));
57+
}
58+
59+
function getReturnTypeNode(sourceFile: SourceFile, pos: number): TypeNode | undefined {
60+
if (isInJSFile(sourceFile)) {
61+
return undefined;
62+
}
63+
64+
const token = getTokenAtPosition(sourceFile, pos);
65+
const parent = findAncestor(token, isFunctionLikeDeclaration);
66+
if (parent?.type) {
67+
return parent.type;
68+
}
69+
}
70+
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"codefixes/fixMissingCallParentheses.ts",
8686
"codefixes/fixAwaitInSyncFunction.ts",
8787
"codefixes/inferFromUsage.ts",
88+
"codefixes/fixReturnTypeInAsyncFunction.ts",
8889
"codefixes/disableJsDiagnostics.ts",
8990
"codefixes/helpers.ts",
9091
"codefixes/fixInvalidImportSyntax.ts",

tests/baselines/reference/asyncArrowFunction_allowJs.errors.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(3,17): error TS2322: Type '0' is not assignable to type 'string'.
2-
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(6,24): error TS1064: The return type of an async function or method must be the global Promise<T> type.
2+
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(6,24): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
33
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(7,23): error TS2322: Type '0' is not assignable to type 'string'.
4-
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(10,24): error TS1064: The return type of an async function or method must be the global Promise<T> type.
4+
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(10,24): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
55
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(12,2): error TS2322: Type '0' is not assignable to type 'string'.
66
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2345: Argument of type '() => Promise<number>' is not assignable to parameter of type '() => string'.
77
Type 'Promise<number>' is not assignable to type 'string'.
@@ -17,15 +17,15 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2
1717
// Error (good)
1818
/** @type {function(): string} */
1919
~~~~~~
20-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
20+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
2121
const b = async () => 0
2222
~
2323
!!! error TS2322: Type '0' is not assignable to type 'string'.
2424

2525
// No error (bad)
2626
/** @type {function(): string} */
2727
~~~~~~
28-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
28+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
2929
const c = async () => {
3030
return 0
3131
~~~~~~~~

tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
1+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{}>'?
22
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
3-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(7,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
4-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
3+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(7,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<any>'?
4+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?
55
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
6-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
7-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise<T> type.
6+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
7+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
88
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(17,16): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
99
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(23,25): error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
1010

@@ -17,23 +17,23 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
1717
async function fn1() { } // valid: Promise<void>
1818
async function fn2(): { } { } // error
1919
~~~
20-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
20+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{}>'?
2121
~~~
2222
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
2323
async function fn3(): any { } // error
2424
~~~
25-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
25+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<any>'?
2626
async function fn4(): number { } // error
2727
~~~~~~
28-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
28+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?
2929
~~~~~~
3030
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
3131
async function fn5(): PromiseLike<void> { } // error
3232
~~~~~~~~~~~~~~~~~
33-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
33+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
3434
async function fn6(): Thenable { } // error
3535
~~~~~~~~
36-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
36+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
3737
async function fn7() { return; } // valid: Promise<void>
3838
async function fn8() { return 1; } // valid: Promise<number>
3939
async function fn9() { return null; } // valid: Promise<any>

tests/baselines/reference/asyncImportedPromise_es6.errors.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type of an async function or method must be the global Promise<T> type.
1+
tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<T>'?
22

33

44
==== tests/cases/conformance/async/es6/task.ts (0 errors) ====
@@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type o
99
class Test {
1010
async example<T>(): Task<T> { return; }
1111
~~~~~~~
12-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
12+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<T>'?
1313
}

tests/baselines/reference/asyncQualifiedReturnType_es6.errors.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error TS1064: The return type of an async function or method must be the global Promise<T> type.
1+
tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
22

33

44
==== tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts (1 errors) ====
@@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error T
99

1010
async function f(): X.MyPromise<void> {
1111
~~~~~~~~~~~~~~~~~
12-
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
12+
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): number {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
9+
newFileContent: `async function fn(): Promise<number> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////type Foo = "a" | "b";
5+
////async function fn(): Foo {}
6+
7+
verify.codeFix({
8+
index: 0,
9+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Foo", "Foo"],
10+
newFileContent:
11+
`type Foo = "a" | "b";
12+
async function fn(): Promise<Foo> {}`
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): PromiseLike<string> {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<string>", "string"],
9+
newFileContent: `async function fn(): Promise<string> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): PromiseLike<void> {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<void>", "void"],
9+
newFileContent: `async function fn(): Promise<void> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////declare class Thenable { then(): void; }
5+
////async function fn(): Thenable {}
6+
7+
verify.codeFix({
8+
index: 0,
9+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Thenable", "void"],
10+
newFileContent:
11+
`declare class Thenable { then(): void; }
12+
async function fn(): Promise<void> {}`
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): string | symbol {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string | symbol", "string | symbol"],
9+
newFileContent: `async function fn(): Promise<string | symbol> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////const foo = async function (): number {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
9+
newFileContent: `const foo = async function (): Promise<number> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////class A {
5+
//// async foo(): number {}
6+
////}
7+
8+
verify.codeFix({
9+
index: 0,
10+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
11+
newFileContent:
12+
`class A {
13+
async foo(): Promise<number> {}
14+
}`
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////const foo = async (): number => {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
9+
newFileContent: `const foo = async (): Promise<number> => {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): boolean {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "boolean", "boolean"],
9+
newFileContent: `async function fn(): Promise<boolean> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): string {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string", "string"],
9+
newFileContent: `async function fn(): Promise<string> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): void {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "void", "void"],
9+
newFileContent: `async function fn(): Promise<void> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): null {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "null", "null"],
9+
newFileContent: `async function fn(): Promise<null> {}`
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @target: es2015
4+
////async function fn(): undefined {}
5+
6+
verify.codeFix({
7+
index: 0,
8+
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "undefined", "undefined"],
9+
newFileContent: `async function fn(): Promise<undefined> {}`
10+
});

0 commit comments

Comments
 (0)