Skip to content

Commit 9b4aa36

Browse files
authored
fix(45901): Error 1064 should apply to @callback definitions in the same way as @param, but doesn't (#54625)
1 parent b1bec34 commit 9b4aa36

7 files changed

+342
-15
lines changed

src/compiler/checker.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38309,7 +38309,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3830938309

3831038310
function checkSignatureDeclarationDiagnostics() {
3831138311
checkCollisionWithArgumentsInGeneratedCode(node);
38312-
const returnTypeNode = getEffectiveReturnTypeNode(node);
38312+
38313+
let returnTypeNode = getEffectiveReturnTypeNode(node);
38314+
let returnTypeErrorLocation = returnTypeNode;
38315+
38316+
if (isInJSFile(node)) {
38317+
const typeTag = getJSDocTypeTag(node);
38318+
if (typeTag && typeTag.typeExpression && isTypeReferenceNode(typeTag.typeExpression.type)) {
38319+
const signature = getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression));
38320+
if (signature && signature.declaration) {
38321+
returnTypeNode = getEffectiveReturnTypeNode(signature.declaration);
38322+
returnTypeErrorLocation = typeTag.typeExpression.type;
38323+
}
38324+
}
38325+
}
38326+
3831338327
if (noImplicitAny && !returnTypeNode) {
3831438328
switch (node.kind) {
3831538329
case SyntaxKind.ConstructSignature:
@@ -38321,12 +38335,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3832138335
}
3832238336
}
3832338337

38324-
if (returnTypeNode) {
38338+
if (returnTypeNode && returnTypeErrorLocation) {
3832538339
const functionFlags = getFunctionFlags(node as FunctionDeclaration);
3832638340
if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) {
3832738341
const returnType = getTypeFromTypeNode(returnTypeNode);
3832838342
if (returnType === voidType) {
38329-
error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
38343+
error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
3833038344
}
3833138345
else {
3833238346
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
@@ -38339,11 +38353,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3833938353
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
3834038354
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
3834138355
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
38342-
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode);
38356+
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeErrorLocation);
3834338357
}
3834438358
}
3834538359
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
38346-
checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode);
38360+
checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode, returnTypeErrorLocation);
3834738361
}
3834838362
}
3834938363
if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) {
@@ -39850,7 +39864,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3985039864
*
3985139865
* @param node The signature to check
3985239866
*/
39853-
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) {
39867+
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode) {
3985439868
// As part of our emit for an async function, we will need to emit the entity name of
3985539869
// the return type annotation as an expression. To meet the necessary runtime semantics
3985639870
// for __awaiter, we must also check that the type of the declaration (e.g. the static
@@ -39876,7 +39890,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3987639890
// }
3987739891
//
3987839892
const returnType = getTypeFromTypeNode(returnTypeNode);
39879-
3988039893
if (languageVersion >= ScriptTarget.ES2015) {
3988139894
if (isErrorType(returnType)) {
3988239895
return;
@@ -39885,7 +39898,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3988539898
if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) {
3988639899
// The promise type was not a valid type reference to the global promise type, so we
3988739900
// report an error and return the unknown type.
39888-
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(getAwaitedTypeNoAlias(returnType) || voidType));
39901+
reportErrorForInvalidReturnType(Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, returnTypeNode, returnTypeErrorLocation, typeToString(getAwaitedTypeNoAlias(returnType) || voidType));
3988939902
return;
3989039903
}
3989139904
}
@@ -39899,18 +39912,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3989939912

3990039913
const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode);
3990139914
if (promiseConstructorName === undefined) {
39902-
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType));
39915+
reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, typeToString(returnType));
3990339916
return;
3990439917
}
3990539918

3990639919
const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true);
3990739920
const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType;
3990839921
if (isErrorType(promiseConstructorType)) {
3990939922
if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) {
39910-
error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
39923+
error(returnTypeErrorLocation, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
3991139924
}
3991239925
else {
39913-
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
39926+
reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName));
3991439927
}
3991539928
return;
3991639929
}
@@ -39919,12 +39932,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3991939932
if (globalPromiseConstructorLikeType === emptyObjectType) {
3992039933
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
3992139934
// compatibility with __awaiter.
39922-
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
39935+
reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName));
3992339936
return;
3992439937
}
3992539938

39926-
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode,
39927-
Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) {
39939+
const headMessage = Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value;
39940+
const errorInfo = () => returnTypeNode === returnTypeErrorLocation ? undefined : chainDiagnosticMessages(/*details*/ undefined, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
39941+
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeErrorLocation, headMessage, errorInfo)) {
3992839942
return;
3992939943
}
3993039944

@@ -39938,7 +39952,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3993839952
return;
3993939953
}
3994039954
}
39955+
3994139956
checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
39957+
39958+
function reportErrorForInvalidReturnType(message: DiagnosticMessage, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode, typeName: string) {
39959+
if (returnTypeNode === returnTypeErrorLocation) {
39960+
error(returnTypeErrorLocation, message, typeName);
39961+
}
39962+
else {
39963+
const diag = error(returnTypeErrorLocation, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
39964+
addRelatedInfo(diag, createDiagnosticForNode(returnTypeNode, message, typeName));
39965+
}
39966+
}
3994239967
}
3994339968

3994439969
/** Check a decorator */

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@
199199
"category": "Error",
200200
"code": 1064
201201
},
202+
"The return type of an async function or method must be the global Promise<T> type.": {
203+
"category": "Error",
204+
"code": 1065
205+
},
202206
"In ambient enum declarations member initializer must be constant expression.": {
203207
"category": "Error",
204208
"code": 1066
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/a.js(21,14): error TS1055: Type 'string' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
2+
/a.js(27,12): error TS1065: The return type of an async function or method must be the global Promise<T> type.
3+
/a.js(45,12): error TS1065: The return type of an async function or method must be the global Promise<T> type.
4+
Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
5+
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
6+
The types returned by 'then(...)' are incompatible between these types.
7+
Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
8+
9+
10+
==== /types.d.ts (0 errors) ====
11+
declare class Thenable { then(): void; }
12+
13+
==== /a.js (3 errors) ====
14+
/**
15+
* @callback T1
16+
* @param {string} str
17+
* @returns {string}
18+
*/
19+
20+
/**
21+
* @callback T2
22+
* @param {string} str
23+
* @returns {Promise<string>}
24+
*/
25+
26+
/**
27+
* @callback T3
28+
* @param {string} str
29+
* @returns {Thenable}
30+
*/
31+
32+
/**
33+
* @param {string} str
34+
* @returns {string}
35+
~~~~~~
36+
!!! error TS1055: Type 'string' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
37+
*/
38+
const f1 = async str => {
39+
return str;
40+
}
41+
42+
/** @type {T1} */
43+
~~
44+
!!! error TS1065: The return type of an async function or method must be the global Promise<T> type.
45+
!!! related TS1055 /a.js:4:14: Type 'string' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
46+
const f2 = async str => {
47+
return str;
48+
}
49+
50+
/**
51+
* @param {string} str
52+
* @returns {Promise<string>}
53+
*/
54+
const f3 = async str => {
55+
return str;
56+
}
57+
58+
/** @type {T2} */
59+
const f4 = async str => {
60+
return str;
61+
}
62+
63+
/** @type {T3} */
64+
~~
65+
!!! error TS1065: The return type of an async function or method must be the global Promise<T> type.
66+
!!! error TS1065: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
67+
!!! error TS1065: Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
68+
!!! error TS1065: The types returned by 'then(...)' are incompatible between these types.
69+
!!! error TS1065: Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
70+
const f5 = async str => {
71+
return str;
72+
}
73+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration16_es5.ts] ////
2+
3+
=== /types.d.ts ===
4+
declare class Thenable { then(): void; }
5+
>Thenable : Symbol(Thenable, Decl(types.d.ts, 0, 0))
6+
>then : Symbol(Thenable.then, Decl(types.d.ts, 0, 24))
7+
8+
=== /a.js ===
9+
/**
10+
* @callback T1
11+
* @param {string} str
12+
* @returns {string}
13+
*/
14+
15+
/**
16+
* @callback T2
17+
* @param {string} str
18+
* @returns {Promise<string>}
19+
*/
20+
21+
/**
22+
* @callback T3
23+
* @param {string} str
24+
* @returns {Thenable}
25+
*/
26+
27+
/**
28+
* @param {string} str
29+
* @returns {string}
30+
*/
31+
const f1 = async str => {
32+
>f1 : Symbol(f1, Decl(a.js, 22, 5))
33+
>str : Symbol(str, Decl(a.js, 22, 16))
34+
35+
return str;
36+
>str : Symbol(str, Decl(a.js, 22, 16))
37+
}
38+
39+
/** @type {T1} */
40+
const f2 = async str => {
41+
>f2 : Symbol(f2, Decl(a.js, 27, 5))
42+
>str : Symbol(str, Decl(a.js, 27, 16))
43+
44+
return str;
45+
>str : Symbol(str, Decl(a.js, 27, 16))
46+
}
47+
48+
/**
49+
* @param {string} str
50+
* @returns {Promise<string>}
51+
*/
52+
const f3 = async str => {
53+
>f3 : Symbol(f3, Decl(a.js, 35, 5))
54+
>str : Symbol(str, Decl(a.js, 35, 16))
55+
56+
return str;
57+
>str : Symbol(str, Decl(a.js, 35, 16))
58+
}
59+
60+
/** @type {T2} */
61+
const f4 = async str => {
62+
>f4 : Symbol(f4, Decl(a.js, 40, 5))
63+
>str : Symbol(str, Decl(a.js, 40, 16))
64+
65+
return str;
66+
>str : Symbol(str, Decl(a.js, 40, 16))
67+
}
68+
69+
/** @type {T3} */
70+
const f5 = async str => {
71+
>f5 : Symbol(f5, Decl(a.js, 45, 5))
72+
>str : Symbol(str, Decl(a.js, 45, 16))
73+
74+
return str;
75+
>str : Symbol(str, Decl(a.js, 45, 16))
76+
}
77+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//// [tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration16_es5.ts] ////
2+
3+
=== /types.d.ts ===
4+
declare class Thenable { then(): void; }
5+
>Thenable : Thenable
6+
>then : () => void
7+
8+
=== /a.js ===
9+
/**
10+
* @callback T1
11+
* @param {string} str
12+
* @returns {string}
13+
*/
14+
15+
/**
16+
* @callback T2
17+
* @param {string} str
18+
* @returns {Promise<string>}
19+
*/
20+
21+
/**
22+
* @callback T3
23+
* @param {string} str
24+
* @returns {Thenable}
25+
*/
26+
27+
/**
28+
* @param {string} str
29+
* @returns {string}
30+
*/
31+
const f1 = async str => {
32+
>f1 : (str: string) => string
33+
>async str => { return str;} : (str: string) => string
34+
>str : string
35+
36+
return str;
37+
>str : string
38+
}
39+
40+
/** @type {T1} */
41+
const f2 = async str => {
42+
>f2 : T1
43+
>async str => { return str;} : (str: string) => string
44+
>str : string
45+
46+
return str;
47+
>str : string
48+
}
49+
50+
/**
51+
* @param {string} str
52+
* @returns {Promise<string>}
53+
*/
54+
const f3 = async str => {
55+
>f3 : (str: string) => Promise<string>
56+
>async str => { return str;} : (str: string) => Promise<string>
57+
>str : string
58+
59+
return str;
60+
>str : string
61+
}
62+
63+
/** @type {T2} */
64+
const f4 = async str => {
65+
>f4 : T2
66+
>async str => { return str;} : (str: string) => Promise<string>
67+
>str : string
68+
69+
return str;
70+
>str : string
71+
}
72+
73+
/** @type {T3} */
74+
const f5 = async str => {
75+
>f5 : T3
76+
>async str => { return str;} : (str: string) => Thenable
77+
>str : string
78+
79+
return str;
80+
>str : string
81+
}
82+

0 commit comments

Comments
 (0)