Skip to content

Commit

Permalink
Cherry-pick PR microsoft#36654 into release-3.9
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
elibarzilay authored and typescript-bot committed May 5, 2020
1 parent 90570df commit 650a176
Show file tree
Hide file tree
Showing 27 changed files with 336 additions and 20 deletions.
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ namespace ts {
},
getParameterType: getTypeAtPosition,
getPromisedTypeOfPromise,
getAwaitedType: type => getAwaitedType(type),
getReturnTypeOfSignature,
isNullableType,
getNullableType,
Expand Down Expand Up @@ -30695,7 +30696,7 @@ namespace ts {
if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) {
// The promise type was not a valid type reference to the global promise type, so we
// report an error and return the unknown type.
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
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));
return;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
"category": "Error",
"code": 1063
},
"The return type of an async function or method must be the global Promise<T> type.": {
"The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{0}>'?": {
"category": "Error",
"code": 1064
},
Expand Down Expand Up @@ -5189,6 +5189,14 @@
"category": "Message",
"code": 90035
},
"Replace '{0}' with 'Promise<{1}>'": {
"category": "Message",
"code": 90036
},
"Fix all incorrect return type of an async functions": {
"category": "Message",
"code": 90037
},
"Declare a private field named '{0}'.": {
"category": "Message",
"code": 90053
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3421,6 +3421,8 @@ namespace ts {
getWidenedType(type: Type): Type;
/* @internal */
getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined;
/* @internal */
getAwaitedType(type: Type): Type | undefined;
getReturnTypeOfSignature(signature: Signature): Type;
/**
* Gets the type of a parameter at a given position in a signature.
Expand Down
70 changes: 70 additions & 0 deletions src/services/codefixes/fixReturnTypeInAsyncFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixReturnTypeInAsyncFunction";
const errorCodes = [
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,
];

interface Info {
readonly returnTypeNode: TypeNode;
readonly returnType: Type;
readonly promisedTypeNode: TypeNode;
readonly promisedType: Type;
}

registerCodeFix({
errorCodes,
fixIds: [fixId],
getCodeActions: context => {
const { sourceFile, program, span } = context;
const checker = program.getTypeChecker();
const info = getInfo(sourceFile, program.getTypeChecker(), span.start);
if (!info) {
return undefined;
}
const { returnTypeNode, returnType, promisedTypeNode, promisedType } = info;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, returnTypeNode, promisedTypeNode));
return [createCodeFixAction(
fixId, changes,
[Diagnostics.Replace_0_with_Promise_1,
checker.typeToString(returnType), checker.typeToString(promisedType)],
fixId, Diagnostics.Fix_all_incorrect_return_type_of_an_async_functions)];
},
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const info = getInfo(diag.file, context.program.getTypeChecker(), diag.start);
if (info) {
doChange(changes, diag.file, info.returnTypeNode, info.promisedTypeNode);
}
})
});

function getInfo(sourceFile: SourceFile, checker: TypeChecker, pos: number): Info | undefined {
const returnTypeNode = getReturnTypeNode(sourceFile, pos);
if (!returnTypeNode) {
return undefined;
}

const returnType = checker.getTypeFromTypeNode(returnTypeNode);
const promisedType = checker.getAwaitedType(returnType) || checker.getVoidType();
const promisedTypeNode = checker.typeToTypeNode(promisedType);
if (promisedTypeNode) {
return { returnTypeNode, returnType, promisedTypeNode, promisedType };
}
}

function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, returnTypeNode: TypeNode, promisedTypeNode: TypeNode): void {
changes.replaceNode(sourceFile, returnTypeNode, createTypeReferenceNode("Promise", [promisedTypeNode]));
}

function getReturnTypeNode(sourceFile: SourceFile, pos: number): TypeNode | undefined {
if (isInJSFile(sourceFile)) {
return undefined;
}

const token = getTokenAtPosition(sourceFile, pos);
const parent = findAncestor(token, isFunctionLikeDeclaration);
if (parent?.type) {
return parent.type;
}
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"codefixes/fixMissingCallParentheses.ts",
"codefixes/fixAwaitInSyncFunction.ts",
"codefixes/inferFromUsage.ts",
"codefixes/fixReturnTypeInAsyncFunction.ts",
"codefixes/disableJsDiagnostics.ts",
"codefixes/helpers.ts",
"codefixes/fixInvalidImportSyntax.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(3,17): error TS2322: Type '0' is not assignable to type 'string'.
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.
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>'?
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(7,23): error TS2322: Type '0' is not assignable to type 'string'.
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.
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>'?
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(12,2): error TS2322: Type '0' is not assignable to type 'string'.
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'.
Type 'Promise<number>' is not assignable to type 'string'.
Expand All @@ -17,15 +17,15 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2
// Error (good)
/** @type {function(): string} */
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
const b = async () => 0
~
!!! error TS2322: Type '0' is not assignable to type 'string'.

// No error (bad)
/** @type {function(): string} */
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
const c = async () => {
return 0
~~~~~~~~
Expand Down
20 changes: 10 additions & 10 deletions tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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.
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<{}>'?
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.
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.
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.
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>'?
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>'?
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.
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.
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.
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>'?
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>'?
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.
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.

Expand All @@ -17,23 +17,23 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
async function fn1() { } // valid: Promise<void>
async function fn2(): { } { } // error
~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{}>'?
~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
async function fn3(): any { } // error
~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
async function fn4(): number { } // error
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
~~~~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
async function fn5(): PromiseLike<void> { } // error
~~~~~~~~~~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
async function fn6(): Thenable { } // error
~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
async function fn7() { return; } // valid: Promise<void>
async function fn8() { return 1; } // valid: Promise<number>
async function fn9() { return null; } // valid: Promise<any>
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/asyncImportedPromise_es6.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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.
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>'?


==== tests/cases/conformance/async/es6/task.ts (0 errors) ====
Expand All @@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type o
class Test {
async example<T>(): Task<T> { return; }
~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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.
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>'?


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

async function f(): X.MyPromise<void> {
~~~~~~~~~~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! 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>'?
}
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): number {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
newFileContent: `async function fn(): Promise<number> {}`
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////type Foo = "a" | "b";
////async function fn(): Foo {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Foo", "Foo"],
newFileContent:
`type Foo = "a" | "b";
async function fn(): Promise<Foo> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): PromiseLike<string> {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<string>", "string"],
newFileContent: `async function fn(): Promise<string> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): PromiseLike<void> {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<void>", "void"],
newFileContent: `async function fn(): Promise<void> {}`
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////declare class Thenable { then(): void; }
////async function fn(): Thenable {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Thenable", "void"],
newFileContent:
`declare class Thenable { then(): void; }
async function fn(): Promise<void> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction14.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): string | symbol {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string | symbol", "string | symbol"],
newFileContent: `async function fn(): Promise<string | symbol> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction15.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////const foo = async function (): number {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
newFileContent: `const foo = async function (): Promise<number> {}`
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction16.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////class A {
//// async foo(): number {}
////}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
newFileContent:
`class A {
async foo(): Promise<number> {}
}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction17.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////const foo = async (): number => {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
newFileContent: `const foo = async (): Promise<number> => {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): boolean {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "boolean", "boolean"],
newFileContent: `async function fn(): Promise<boolean> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): string {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string", "string"],
newFileContent: `async function fn(): Promise<string> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): void {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "void", "void"],
newFileContent: `async function fn(): Promise<void> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): null {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "null", "null"],
newFileContent: `async function fn(): Promise<null> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): undefined {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "undefined", "undefined"],
newFileContent: `async function fn(): Promise<undefined> {}`
});
Loading

0 comments on commit 650a176

Please sign in to comment.