Skip to content

Commit

Permalink
fix(45901): Error 1064 should apply to @callback definitions in the s…
Browse files Browse the repository at this point in the history
…ame way as @param, but doesn't (microsoft#54625)
  • Loading branch information
a-tarasyuk committed Jul 25, 2023
1 parent b1bec34 commit 9b4aa36
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 15 deletions.
53 changes: 39 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38309,7 +38309,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 @@ -38321,12 +38335,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 @@ -38339,11 +38353,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 @@ -39850,7 +39864,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 @@ -39876,7 +39890,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// }
//
const returnType = getTypeFromTypeNode(returnTypeNode);

if (languageVersion >= ScriptTarget.ES2015) {
if (isErrorType(returnType)) {
return;
Expand All @@ -39885,7 +39898,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 @@ -39899,18 +39912,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 @@ -39919,12 +39932,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 @@ -39938,7 +39952,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
}

0 comments on commit 9b4aa36

Please sign in to comment.