diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6554c03a969..e737cd58bd5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9895,7 +9895,7 @@ namespace ts { return aggregatedTypes; } - /* + /* *TypeScript Specification 1.0 (6.3) - July 2014 * An explicitly typed function whose return type isn't the Void or the Any type * must have at least one return statement somewhere in its body. @@ -9921,15 +9921,15 @@ namespace ts { const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; if (returnType && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // minimal check: function has syntactic return type annotation and no explicit return statements in the body // this function does not conform to the specification. - // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present + // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } else if (compilerOptions.noImplicitReturns) { if (!returnType) { // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. + // If it does not have any - its inferred return type is void - don't do any checks. // Otherwise get inferred return type from function body and report error only if it is not void / anytype const inferredReturnType = hasExplicitReturn ? getReturnTypeOfSignature(getSignatureFromDeclaration(func)) @@ -11959,6 +11959,9 @@ namespace ts { return unknownType; } + // If the Promise constructor, resolved locally, is an alias symbol we should mark it as referenced. + checkReturnTypeAnnotationAsExpression(node); + // Validate the promise constructor type. const promiseConstructorType = getTypeOfSymbol(promiseConstructor); if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) { @@ -11967,11 +11970,11 @@ namespace ts { // Verify there is no local declaration that could collide with the promise constructor. const promiseName = getEntityNameFromTypeNode(node.type); - const root = getFirstIdentifier(promiseName); - const rootSymbol = getSymbol(node.locals, root.text, SymbolFlags.Value); + const promiseNameOrNamespaceRoot = getFirstIdentifier(promiseName); + const rootSymbol = getSymbol(node.locals, promiseNameOrNamespaceRoot.text, SymbolFlags.Value); if (rootSymbol) { error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, - root.text, + promiseNameOrNamespaceRoot.text, getFullyQualifiedName(promiseConstructor)); return unknownType; } @@ -12055,24 +12058,12 @@ namespace ts { * Checks the type annotation of an accessor declaration or property declaration as * an expression if it is a type reference to a type with a value declaration. */ - function checkTypeAnnotationAsExpression(node: AccessorDeclaration | PropertyDeclaration | ParameterDeclaration | MethodDeclaration) { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - checkTypeNodeAsExpression((node).type); - break; - case SyntaxKind.Parameter: - checkTypeNodeAsExpression((node).type); - break; - case SyntaxKind.MethodDeclaration: - checkTypeNodeAsExpression((node).type); - break; - case SyntaxKind.GetAccessor: - checkTypeNodeAsExpression((node).type); - break; - case SyntaxKind.SetAccessor: - checkTypeNodeAsExpression(getSetAccessorTypeAnnotationNode(node)); - break; - } + function checkTypeAnnotationAsExpression(node: VariableLikeDeclaration) { + checkTypeNodeAsExpression((node).type); + } + + function checkReturnTypeAnnotationAsExpression(node: FunctionLikeDeclaration) { + checkTypeNodeAsExpression(node.type); } /** Checks the type annotation of the parameters of a function/method or the constructor of a class as expressions */ @@ -12110,11 +12101,12 @@ namespace ts { break; case SyntaxKind.MethodDeclaration: - checkParameterTypeAnnotationsAsExpressions(node); - // fall-through - - case SyntaxKind.SetAccessor: case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + checkParameterTypeAnnotationsAsExpressions(node); + checkReturnTypeAnnotationAsExpression(node); + break; + case SyntaxKind.PropertyDeclaration: case SyntaxKind.Parameter: checkTypeAnnotationAsExpression(node); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 3474a2cea04..4e14564a1fc 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2190,7 +2190,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emit(node.right); } - function emitEntityNameAsExpression(node: EntityName, useFallback: boolean) { + function emitEntityNameAsExpression(node: EntityName | Expression, useFallback: boolean) { switch (node.kind) { case SyntaxKind.Identifier: if (useFallback) { @@ -2205,6 +2205,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi case SyntaxKind.QualifiedName: emitQualifiedNameAsExpression(node, useFallback); break; + + default: + emitNodeWithoutSourceMap(node); + break; } } @@ -2978,7 +2982,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } else { // this is top level converted loop so we need to create an alias for 'this' here - // NOTE: + // NOTE: // if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set. // If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'. write(`var ${convertedLoopState.thisName} = this;`); @@ -4455,18 +4459,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(" __awaiter(this"); if (hasLexicalArguments) { - write(", arguments"); + write(", arguments, "); } else { - write(", void 0"); + write(", void 0, "); } if (promiseConstructor) { - write(", "); - emitNodeWithoutSourceMap(promiseConstructor); + emitEntityNameAsExpression(promiseConstructor, /*useFallback*/ false); } else { - write(", Promise"); + write("Promise"); } // Emit the call to __awaiter. @@ -5729,7 +5732,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } /** Serializes the return type of function. Used by the __metadata decorator for a method. */ - function emitSerializedReturnTypeOfNode(node: Node): string | string[] { + function emitSerializedReturnTypeOfNode(node: Node) { if (node && isFunctionLike(node) && (node).type) { emitSerializedTypeNode((node).type); return; diff --git a/tests/baselines/reference/asyncAliasReturnType_es6.errors.txt b/tests/baselines/reference/asyncAliasReturnType_es6.errors.txt new file mode 100644 index 00000000000..ec532d1380d --- /dev/null +++ b/tests/baselines/reference/asyncAliasReturnType_es6.errors.txt @@ -0,0 +1,10 @@ +tests/cases/conformance/async/es6/asyncAliasReturnType_es6.ts(3,16): error TS1055: Type 'PromiseAlias' is not a valid async function return type. + + +==== tests/cases/conformance/async/es6/asyncAliasReturnType_es6.ts (1 errors) ==== + type PromiseAlias = Promise; + + async function f(): PromiseAlias { + ~ +!!! error TS1055: Type 'PromiseAlias' is not a valid async function return type. + } \ No newline at end of file diff --git a/tests/baselines/reference/asyncAliasReturnType_es6.js b/tests/baselines/reference/asyncAliasReturnType_es6.js new file mode 100644 index 00000000000..0af63b0bfa6 --- /dev/null +++ b/tests/baselines/reference/asyncAliasReturnType_es6.js @@ -0,0 +1,11 @@ +//// [asyncAliasReturnType_es6.ts] +type PromiseAlias = Promise; + +async function f(): PromiseAlias { +} + +//// [asyncAliasReturnType_es6.js] +function f() { + return __awaiter(this, void 0, PromiseAlias, function* () { + }); +} diff --git a/tests/baselines/reference/asyncImportedPromise_es6.js b/tests/baselines/reference/asyncImportedPromise_es6.js new file mode 100644 index 00000000000..a9c7540d88f --- /dev/null +++ b/tests/baselines/reference/asyncImportedPromise_es6.js @@ -0,0 +1,37 @@ +//// [tests/cases/conformance/async/es6/asyncImportedPromise_es6.ts] //// + +//// [task.ts] +export class Task extends Promise { } + +//// [test.ts] +import { Task } from "./task"; +class Test { + async example(): Task { return; } +} + +//// [task.js] +"use strict"; +class Task extends Promise { +} +exports.Task = Task; +//// [test.js] +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { + return new Promise(function (resolve, reject) { + generator = generator.call(thisArg, _arguments); + function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } + function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } + function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } + function step(verb, value) { + var result = generator[verb](value); + result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); + } + step("next", void 0); + }); +}; +var task_1 = require("./task"); +class Test { + example() { + return __awaiter(this, void 0, task_1.Task, function* () { return; }); + } +} diff --git a/tests/baselines/reference/asyncImportedPromise_es6.symbols b/tests/baselines/reference/asyncImportedPromise_es6.symbols new file mode 100644 index 00000000000..45cf47d2b45 --- /dev/null +++ b/tests/baselines/reference/asyncImportedPromise_es6.symbols @@ -0,0 +1,20 @@ +=== tests/cases/conformance/async/es6/task.ts === +export class Task extends Promise { } +>Task : Symbol(Task, Decl(task.ts, 0, 0)) +>T : Symbol(T, Decl(task.ts, 0, 18)) +>Promise : Symbol(Promise, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(task.ts, 0, 18)) + +=== tests/cases/conformance/async/es6/test.ts === +import { Task } from "./task"; +>Task : Symbol(Task, Decl(test.ts, 0, 8)) + +class Test { +>Test : Symbol(Test, Decl(test.ts, 0, 30)) + + async example(): Task { return; } +>example : Symbol(example, Decl(test.ts, 1, 12)) +>T : Symbol(T, Decl(test.ts, 2, 18)) +>Task : Symbol(Task, Decl(test.ts, 0, 8)) +>T : Symbol(T, Decl(test.ts, 2, 18)) +} diff --git a/tests/baselines/reference/asyncImportedPromise_es6.types b/tests/baselines/reference/asyncImportedPromise_es6.types new file mode 100644 index 00000000000..424f14b34d3 --- /dev/null +++ b/tests/baselines/reference/asyncImportedPromise_es6.types @@ -0,0 +1,20 @@ +=== tests/cases/conformance/async/es6/task.ts === +export class Task extends Promise { } +>Task : Task +>T : T +>Promise : Promise +>T : T + +=== tests/cases/conformance/async/es6/test.ts === +import { Task } from "./task"; +>Task : typeof Task + +class Test { +>Test : Test + + async example(): Task { return; } +>example : () => Task +>T : T +>Task : Task +>T : T +} diff --git a/tests/baselines/reference/asyncQualifiedReturnType_es6.js b/tests/baselines/reference/asyncQualifiedReturnType_es6.js new file mode 100644 index 00000000000..1da37946254 --- /dev/null +++ b/tests/baselines/reference/asyncQualifiedReturnType_es6.js @@ -0,0 +1,20 @@ +//// [asyncQualifiedReturnType_es6.ts] +namespace X { + export class MyPromise extends Promise { + } +} + +async function f(): X.MyPromise { +} + +//// [asyncQualifiedReturnType_es6.js] +var X; +(function (X) { + class MyPromise extends Promise { + } + X.MyPromise = MyPromise; +})(X || (X = {})); +function f() { + return __awaiter(this, void 0, X.MyPromise, function* () { + }); +} diff --git a/tests/baselines/reference/asyncQualifiedReturnType_es6.symbols b/tests/baselines/reference/asyncQualifiedReturnType_es6.symbols new file mode 100644 index 00000000000..5d27d04ea3b --- /dev/null +++ b/tests/baselines/reference/asyncQualifiedReturnType_es6.symbols @@ -0,0 +1,17 @@ +=== tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts === +namespace X { +>X : Symbol(X, Decl(asyncQualifiedReturnType_es6.ts, 0, 0)) + + export class MyPromise extends Promise { +>MyPromise : Symbol(MyPromise, Decl(asyncQualifiedReturnType_es6.ts, 0, 13)) +>T : Symbol(T, Decl(asyncQualifiedReturnType_es6.ts, 1, 27)) +>Promise : Symbol(Promise, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(asyncQualifiedReturnType_es6.ts, 1, 27)) + } +} + +async function f(): X.MyPromise { +>f : Symbol(f, Decl(asyncQualifiedReturnType_es6.ts, 3, 1)) +>X : Symbol(X, Decl(asyncQualifiedReturnType_es6.ts, 0, 0)) +>MyPromise : Symbol(X.MyPromise, Decl(asyncQualifiedReturnType_es6.ts, 0, 13)) +} diff --git a/tests/baselines/reference/asyncQualifiedReturnType_es6.types b/tests/baselines/reference/asyncQualifiedReturnType_es6.types new file mode 100644 index 00000000000..3b438eb93b6 --- /dev/null +++ b/tests/baselines/reference/asyncQualifiedReturnType_es6.types @@ -0,0 +1,17 @@ +=== tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts === +namespace X { +>X : typeof X + + export class MyPromise extends Promise { +>MyPromise : MyPromise +>T : T +>Promise : Promise +>T : T + } +} + +async function f(): X.MyPromise { +>f : () => X.MyPromise +>X : any +>MyPromise : X.MyPromise +} diff --git a/tests/cases/conformance/async/es6/asyncAliasReturnType_es6.ts b/tests/cases/conformance/async/es6/asyncAliasReturnType_es6.ts new file mode 100644 index 00000000000..26d87429269 --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncAliasReturnType_es6.ts @@ -0,0 +1,6 @@ +// @target: ES6 +// @noEmitHelpers: true +type PromiseAlias = Promise; + +async function f(): PromiseAlias { +} \ No newline at end of file diff --git a/tests/cases/conformance/async/es6/asyncImportedPromise_es6.ts b/tests/cases/conformance/async/es6/asyncImportedPromise_es6.ts new file mode 100644 index 00000000000..baf7a5f652d --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncImportedPromise_es6.ts @@ -0,0 +1,10 @@ +// @target: es6 +// @module: commonjs +// @filename: task.ts +export class Task extends Promise { } + +// @filename: test.ts +import { Task } from "./task"; +class Test { + async example(): Task { return; } +} \ No newline at end of file diff --git a/tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts b/tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts new file mode 100644 index 00000000000..eb88f8b1174 --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts @@ -0,0 +1,9 @@ +// @target: ES6 +// @noEmitHelpers: true +namespace X { + export class MyPromise extends Promise { + } +} + +async function f(): X.MyPromise { +} \ No newline at end of file