Skip to content

fix(45901): Error 1064 should apply to @callback definitions in the same way as @param, but doesn't #54625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38304,7 +38304,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function checkSignatureDeclarationDiagnostics() {
checkCollisionWithArgumentsInGeneratedCode(node);
const returnTypeNode = getEffectiveReturnTypeNode(node);

let returnTypeNode = getEffectiveReturnTypeNode(node);
let returnTypeErrorLocation = returnTypeNode;

if (isInJSFile(node)) {
const typeTag = getJSDocTypeTag(node);
if (typeTag && typeTag.typeExpression && isTypeReferenceNode(typeTag.typeExpression.type)) {
const signature = getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression));
if (signature && signature.declaration) {
returnTypeNode = getEffectiveReturnTypeNode(signature.declaration);
returnTypeErrorLocation = typeTag.typeExpression.type;
}
}
}

if (noImplicitAny && !returnTypeNode) {
switch (node.kind) {
case SyntaxKind.ConstructSignature:
Expand All @@ -38316,12 +38330,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

if (returnTypeNode) {
if (returnTypeNode && returnTypeErrorLocation) {
const functionFlags = getFunctionFlags(node as FunctionDeclaration);
if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) {
const returnType = getTypeFromTypeNode(returnTypeNode);
if (returnType === voidType) {
error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
}
else {
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
Expand All @@ -38334,11 +38348,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode);
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeErrorLocation);
}
}
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode);
checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode, returnTypeErrorLocation);
}
}
if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) {
Expand Down Expand Up @@ -39845,7 +39859,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
*
* @param node The signature to check
*/
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) {
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode) {
// As part of our emit for an async function, we will need to emit the entity name of
// the return type annotation as an expression. To meet the necessary runtime semantics
// for __awaiter, we must also check that the type of the declaration (e.g. the static
Expand All @@ -39871,7 +39885,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// }
//
const returnType = getTypeFromTypeNode(returnTypeNode);

if (languageVersion >= ScriptTarget.ES2015) {
if (isErrorType(returnType)) {
return;
Expand All @@ -39880,7 +39893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType));
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));
return;
}
}
Expand All @@ -39894,18 +39907,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode);
if (promiseConstructorName === undefined) {
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));
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));
return;
}

const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true);
const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType;
if (isErrorType(promiseConstructorType)) {
if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) {
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);
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);
}
else {
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));
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));
}
return;
}
Expand All @@ -39914,12 +39927,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (globalPromiseConstructorLikeType === emptyObjectType) {
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
// compatibility with __awaiter.
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));
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));
return;
}

if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, 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)) {
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;
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);
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeErrorLocation, headMessage, errorInfo)) {
return;
}

Expand All @@ -39933,7 +39947,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return;
}
}

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);

function reportErrorForInvalidReturnType(message: DiagnosticMessage, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode, typeName: string) {
if (returnTypeNode === returnTypeErrorLocation) {
error(returnTypeErrorLocation, message, typeName);
}
else {
const diag = error(returnTypeErrorLocation, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
addRelatedInfo(diag, createDiagnosticForNode(returnTypeNode, message, typeName));
}
}
}

/** Check a decorator */
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@
"category": "Error",
"code": 1064
},
"The return type of an async function or method must be the global Promise<T> type.": {
"category": "Error",
"code": 1065
},
"In ambient enum declarations member initializer must be constant expression.": {
"category": "Error",
"code": 1066
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/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.
/a.js(27,12): error TS1065: The return type of an async function or method must be the global Promise<T> type.
/a.js(45,12): error TS1065: The return type of an async function or method must be the global Promise<T> type.
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.
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
The types returned by 'then(...)' are incompatible between these types.
Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.


==== /types.d.ts (0 errors) ====
declare class Thenable { then(): void; }

==== /a.js (3 errors) ====
/**
* @callback T1
* @param {string} str
* @returns {string}
*/

/**
* @callback T2
* @param {string} str
* @returns {Promise<string>}
*/

/**
* @callback T3
* @param {string} str
* @returns {Thenable}
*/

/**
* @param {string} str
* @returns {string}
~~~~~~
!!! 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.
*/
const f1 = async str => {
return str;
}

/** @type {T1} */
~~
!!! error TS1065: The return type of an async function or method must be the global Promise<T> type.
!!! 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.
const f2 = async str => {
return str;
}

/**
* @param {string} str
* @returns {Promise<string>}
*/
const f3 = async str => {
return str;
}

/** @type {T2} */
const f4 = async str => {
return str;
}

/** @type {T3} */
~~
!!! error TS1065: The return type of an async function or method must be the global Promise<T> type.
!!! 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.
!!! error TS1065: Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
!!! error TS1065: The types returned by 'then(...)' are incompatible between these types.
!!! error TS1065: Type 'void' is not assignable to type 'PromiseLike<TResult1 | TResult2>'.
const f5 = async str => {
return str;
}

77 changes: 77 additions & 0 deletions tests/baselines/reference/asyncFunctionDeclaration16_es5.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//// [tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration16_es5.ts] ////

=== /types.d.ts ===
declare class Thenable { then(): void; }
>Thenable : Symbol(Thenable, Decl(types.d.ts, 0, 0))
>then : Symbol(Thenable.then, Decl(types.d.ts, 0, 24))

=== /a.js ===
/**
* @callback T1
* @param {string} str
* @returns {string}
*/

/**
* @callback T2
* @param {string} str
* @returns {Promise<string>}
*/

/**
* @callback T3
* @param {string} str
* @returns {Thenable}
*/

/**
* @param {string} str
* @returns {string}
*/
const f1 = async str => {
>f1 : Symbol(f1, Decl(a.js, 22, 5))
>str : Symbol(str, Decl(a.js, 22, 16))

return str;
>str : Symbol(str, Decl(a.js, 22, 16))
}

/** @type {T1} */
const f2 = async str => {
>f2 : Symbol(f2, Decl(a.js, 27, 5))
>str : Symbol(str, Decl(a.js, 27, 16))

return str;
>str : Symbol(str, Decl(a.js, 27, 16))
}

/**
* @param {string} str
* @returns {Promise<string>}
*/
const f3 = async str => {
>f3 : Symbol(f3, Decl(a.js, 35, 5))
>str : Symbol(str, Decl(a.js, 35, 16))

return str;
>str : Symbol(str, Decl(a.js, 35, 16))
}

/** @type {T2} */
const f4 = async str => {
>f4 : Symbol(f4, Decl(a.js, 40, 5))
>str : Symbol(str, Decl(a.js, 40, 16))

return str;
>str : Symbol(str, Decl(a.js, 40, 16))
}

/** @type {T3} */
const f5 = async str => {
>f5 : Symbol(f5, Decl(a.js, 45, 5))
>str : Symbol(str, Decl(a.js, 45, 16))

return str;
>str : Symbol(str, Decl(a.js, 45, 16))
}

82 changes: 82 additions & 0 deletions tests/baselines/reference/asyncFunctionDeclaration16_es5.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//// [tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration16_es5.ts] ////

=== /types.d.ts ===
declare class Thenable { then(): void; }
>Thenable : Thenable
>then : () => void

=== /a.js ===
/**
* @callback T1
* @param {string} str
* @returns {string}
*/

/**
* @callback T2
* @param {string} str
* @returns {Promise<string>}
*/

/**
* @callback T3
* @param {string} str
* @returns {Thenable}
*/

/**
* @param {string} str
* @returns {string}
*/
const f1 = async str => {
>f1 : (str: string) => string
>async str => { return str;} : (str: string) => string
>str : string

return str;
>str : string
}

/** @type {T1} */
const f2 = async str => {
>f2 : T1
>async str => { return str;} : (str: string) => string
>str : string

return str;
>str : string
}

/**
* @param {string} str
* @returns {Promise<string>}
*/
const f3 = async str => {
>f3 : (str: string) => Promise<string>
>async str => { return str;} : (str: string) => Promise<string>
>str : string

return str;
>str : string
}

/** @type {T2} */
const f4 = async str => {
>f4 : T2
>async str => { return str;} : (str: string) => Promise<string>
>str : string

return str;
>str : string
}

/** @type {T3} */
const f5 = async str => {
>f5 : T3
>async str => { return str;} : (str: string) => Thenable
>str : string

return str;
>str : string
}

Loading