diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index cf3142a3241..deca03a438f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2156,6 +2156,9 @@ namespace ts { if (isAsyncFunctionLike(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } + if (node.asteriskToken) { + emitFlags |= NodeFlags.HasGenerators; + } } checkStrictModeFunctionName(node); @@ -2173,6 +2176,9 @@ namespace ts { if (isAsyncFunctionLike(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } + if (node.asteriskToken) { + emitFlags |= NodeFlags.HasGenerators; + } } if (currentFlow) { node.flowNode = currentFlow; @@ -2187,6 +2193,9 @@ namespace ts { if (isAsyncFunctionLike(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } + if (isFunctionLike(node) && node.asteriskToken) { + emitFlags |= NodeFlags.HasGenerators; + } if (nodeIsDecorated(node)) { emitFlags |= NodeFlags.HasDecorators; } @@ -2565,8 +2574,9 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - // Currently, we only support generators that were originally async function bodies. - if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + // If a MethodDeclaration is generator method, then this node can be transformed to + // a down-level generator. + if (asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } @@ -2636,12 +2646,9 @@ namespace ts { transformFlags |= TransformFlags.AssertES6; } - // If a FunctionDeclaration is generator function and is the body of a - // transformed async function, then this node can be transformed to a - // down-level generator. - // Currently we do not support transforming any other generator fucntions - // down level. - if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + // If a FunctionDeclaration is generator function, then this node can be transformed to + // a down-level generator. + if (asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } } @@ -2667,12 +2674,9 @@ namespace ts { transformFlags |= TransformFlags.AssertES6; } - // If a FunctionExpression is generator function and is the body of a - // transformed async function, then this node can be transformed to a + // If a FunctionExpression is generator function then this node can be transformed to a // down-level generator. - // Currently we do not support transforming any other generator fucntions - // down level. - if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + if (asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f87f8bea2b7..2b26782a1ec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5168,6 +5168,11 @@ namespace ts { return getTypeOfGlobalSymbol(getGlobalTypeSymbol(name), arity); } + function tryGetGlobalType(name: string, arity = 0, fallbackType?: ObjectType): ObjectType { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol ? getTypeOfGlobalSymbol(symbol, arity) : fallbackType; + } + /** * Returns a type that is inside a namespace at the global scope, e.g. * getExportedTypeFromNamespace('JSX', 'Element') returns the JSX.Element type @@ -5199,10 +5204,20 @@ namespace ts { return createTypeFromGenericGlobalType(getGlobalIterableType(), [elementType]); } + function createGeneratorReturnType(elementType: Type): Type { + return languageVersion >= ScriptTarget.ES6 + ? createIterableIteratorType(elementType) + : createIteratorType(elementType); + } + function createIterableIteratorType(elementType: Type): Type { return createTypeFromGenericGlobalType(getGlobalIterableIteratorType(), [elementType]); } + function createIteratorType(elementType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIteratorType(), [elementType]); + } + function createArrayType(elementType: Type): Type { return createTypeFromGenericGlobalType(globalArrayType, [elementType]); } @@ -8630,6 +8645,9 @@ namespace ts { else if (hasModifier(container, ModifierFlags.Async)) { error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); } + else if (container.asteriskToken) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_a_generator_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); + } } if (node.flags & NodeFlags.AwaitContext) { @@ -12252,7 +12270,7 @@ namespace ts { if (funcIsGenerator) { types = checkAndAggregateYieldOperandTypes(func, contextualMapper); if (types.length === 0) { - const iterableIteratorAny = createIterableIteratorType(anyType); + const iterableIteratorAny = createGeneratorReturnType(anyType); if (compilerOptions.noImplicitAny) { error(func.asteriskToken, Diagnostics.Generator_implicitly_has_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type, typeToString(iterableIteratorAny)); @@ -12277,7 +12295,7 @@ namespace ts { if (!type) { if (funcIsGenerator) { error(func, Diagnostics.No_best_common_type_exists_among_yield_expressions); - return createIterableIteratorType(unknownType); + return createGeneratorReturnType(unknownType); } else { error(func, Diagnostics.No_best_common_type_exists_among_return_expressions); @@ -12287,7 +12305,7 @@ namespace ts { } if (funcIsGenerator) { - type = createIterableIteratorType(type); + type = createGeneratorReturnType(type); } } if (!contextualSignature) { @@ -12311,7 +12329,9 @@ namespace ts { if (yieldExpression.asteriskToken) { // A yield* expression effectively yields everything that its operand yields - type = checkElementTypeOfIterable(type, yieldExpression.expression); + type = languageVersion >= ScriptTarget.ES6 + ? checkElementTypeOfIterable(type, yieldExpression.expression) + : checkElementTypeOfIterator(type, yieldExpression.expression); } if (!contains(aggregatedTypes, type)) { @@ -13190,8 +13210,11 @@ namespace ts { let expressionElementType: Type; const nodeIsYieldStar = !!node.asteriskToken; if (nodeIsYieldStar) { - expressionElementType = checkElementTypeOfIterable(expressionType, node.expression); + expressionElementType = languageVersion >= ScriptTarget.ES6 + ? checkElementTypeOfIterable(expressionType, node.expression) + : checkElementTypeOfIterator(expressionType, node.expression); } + // There is no point in doing an assignability check if the function // has no explicit return type because the return type is directly computed // from the yield expressions. @@ -13668,14 +13691,14 @@ namespace ts { } if (node.type) { - if (languageVersion >= ScriptTarget.ES6 && isSyntacticallyValidGenerator(node)) { + if (isSyntacticallyValidGenerator(node)) { const returnType = getTypeFromTypeNode(node.type); if (returnType === voidType) { error(node.type, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { const generatorElementType = getElementTypeOfIterableIterator(returnType) || anyType; - const iterableIteratorInstantiation = createIterableIteratorType(generatorElementType); + const iterableIteratorInstantiation = createGeneratorReturnType(generatorElementType); // Naively, one could check that IterableIterator is assignable to the return type annotation. // However, that would not catch the error in the following case. @@ -15682,6 +15705,20 @@ namespace ts { return elementType || anyType; } + /** + * When errorNode is undefined, it means we should not report any errors. + */ + function checkElementTypeOfIterator(iterator: Type, errorNode: Node) { + const elementType = getElementTypeOfIterator(iterator, errorNode); + // Now even though we have extracted the elementType, we will have to validate that the type + // passed in is actually an Iterator. + if (errorNode && elementType) { + checkTypeAssignableTo(iterator, createIteratorType(elementType), errorNode); + } + + return elementType || anyType; + } + /** * We want to treat type as an iterable, and get the type it is an iterable of. The iterable * must have the following structure (annotated with the names of the variables below): @@ -18723,7 +18760,7 @@ namespace ts { else { getGlobalESSymbolType = memoize(() => emptyObjectType); getGlobalIterableType = memoize(() => emptyGenericType); - getGlobalIteratorType = memoize(() => emptyGenericType); + getGlobalIteratorType = memoize(() => tryGetGlobalType("Iterator", /*arity*/ 1, emptyGenericType)); getGlobalIterableIteratorType = memoize(() => emptyGenericType); } @@ -19341,9 +19378,6 @@ namespace ts { if (!node.body) { return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); } - if (languageVersion < ScriptTarget.ES6) { - return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_only_available_when_targeting_ECMAScript_2015_or_higher); - } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8ef0698dea7..4bf07aeba6f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -683,10 +683,6 @@ "category": "Error", "code": 1219 }, - "Generators are only available when targeting ECMAScript 2015 or higher.": { - "category": "Error", - "code": 1220 - }, "Generators are not allowed in an ambient context.": { "category": "Error", "code": 1221 @@ -1755,6 +1751,10 @@ "category": "Error", "code": 2535 }, + "The 'arguments' object cannot be referenced in a generator function or method in ES3 and ES5. Consider using a standard function or method.": { + "category": "Error", + "code": 2536 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8b91a7c19d9..cd7b963325d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -73,7 +73,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge const generatorHelper = ` var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (sent[0] === 1) throw sent[1]; return sent[1]; }, trys: [], stack: [] }, sent, f; + var _ = { label: 0, sent: function() { if (sent[0] === 1) throw sent[1]; return sent[1]; }, trys: [], stack: [] }, sent, star, f; function step(op) { if (f) throw new TypeError("Generator is already executing."); while (1) { @@ -83,9 +83,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) { case 2: return { value: op[1], done: true }; } try { - switch (f = 1, op[0]) { + f = 1; + if (star) { + var v = star[["next", "throw", "return"][op[0]]]; + if (v && !(v = v.call(star, op[1])).done) return v; + if (v) op = [0, v.value]; + star = void 0; continue; + } + switch (op[0]) { case 0: case 1: sent = op; break; case 4: return _.label++, { value: op[1], done: false }; + case 5: _.label++, star = op[1], op = [0]; continue; case 7: op = _.stack.pop(), _.trys.pop(); continue; default: var r = _.trys.length > 0 && _.trys[_.trys.length - 1]; @@ -99,8 +107,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) { } op = body.call(thisArg, _); } - catch (e) { op = [6, e]; } - finally { f = 0, sent = void 0; } + catch (e) { op = [6, e], star = void 0; } + finally { f = 0, sent = v = void 0; } } } return { @@ -179,6 +187,7 @@ const _super = (function (geti, seti) { let decorateEmitted: boolean; let paramEmitted: boolean; let awaiterEmitted: boolean; + let generatorEmitted: boolean; let isOwnFileEmit: boolean; let emitSkipped = false; @@ -295,6 +304,7 @@ const _super = (function (geti, seti) { decorateEmitted = false; paramEmitted = false; awaiterEmitted = false; + generatorEmitted = false; isOwnFileEmit = false; } @@ -2171,14 +2181,17 @@ const _super = (function (geti, seti) { if (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions) { writeLines(awaiterHelper); - if (languageVersion < ScriptTarget.ES6) { - writeLines(generatorHelper); - } - awaiterEmitted = true; helpersEmitted = true; } + if (!generatorEmitted && node.flags & (NodeFlags.HasAsyncFunctions | NodeFlags.HasGenerators) + && languageVersion < ScriptTarget.ES6) { + writeLines(generatorHelper); + generatorEmitted = true; + helpersEmitted = true; + } + if (helpersEmitted) { writeLine(); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 1930481115c..8fdb096a93e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -154,7 +154,7 @@ namespace ts { return name; } - export function createLoopVariable(location?: TextRange): Identifier { + export function createLoopVariable(recordTempVariable?: (node: Identifier) => void, location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); name.text = ""; name.originalKeywordKind = SyntaxKind.Unknown; @@ -1743,9 +1743,6 @@ namespace ts { body ); - // Mark this node as originally an async function - generatorFunc.emitFlags |= NodeEmitFlags.AsyncFunctionBody; - return createCall( createHelperName(externalHelpersModuleName, "__awaiter"), /*typeArguments*/ undefined, diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 1dcb2446e0b..e9662f47f43 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -442,8 +442,7 @@ namespace ts { * @param node The node to visit. */ function visitFunctionDeclaration(node: FunctionDeclaration): Statement { - // Currently, we only support generators that were originally async functions. - if (node.asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { node = setOriginalNode( createFunctionDeclaration( /*decorators*/ undefined, @@ -490,8 +489,7 @@ namespace ts { * @param node The node to visit. */ function visitFunctionExpression(node: FunctionExpression): Expression { - // Currently, we only support generators that were originally async functions. - if (node.asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { node = setOriginalNode( createFunctionExpression( /*asteriskToken*/ undefined, @@ -572,11 +570,11 @@ namespace ts { operationLocations = undefined; state = createTempVariable(/*recordTempVariable*/ undefined); - const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); - // Build the generator startLexicalEnvironment(); + const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); + transformAndEmitStatements(body.statements, statementOffset); const buildResult = build(); @@ -609,7 +607,10 @@ namespace ts { * @param node The node to visit. */ function visitVariableStatement(node: VariableStatement): Statement { - if (node.transformFlags & TransformFlags.ContainsYield) { + if (node.emitFlags & NodeEmitFlags.CustomPrologue) { + return visitEachChild(node, visitor, context); + } + else if (node.transformFlags & TransformFlags.ContainsYield) { transformAndEmitVariableDeclarationList(node.declarationList); return undefined; } @@ -906,7 +907,7 @@ namespace ts { * * @param node The node to visit. */ - function visitYieldExpression(node: YieldExpression) { + function visitYieldExpression(node: YieldExpression): LeftHandSideExpression { // [source] // x = yield a(); // @@ -915,9 +916,15 @@ namespace ts { // .mark resumeLabel // x = %sent%; - // NOTE: we are explicitly not handling YieldStar at this time. const resumeLabel = defineLabel(); - emitYield(visitNode(node.expression, visitor, isExpression), /*location*/ node); + const expression = visitNode(node.expression, visitor, isExpression); + if (node.asteriskToken) { + emitYieldStar(expression, /*location*/ node); + } + else { + emitYield(expression, /*location*/ node); + } + markLabel(resumeLabel); return createGeneratorResume(); } @@ -1421,6 +1428,10 @@ namespace ts { } function visitForStatement(node: ForStatement) { + if (node.emitFlags & NodeEmitFlags.CustomPrologue) { + return visitEachChild(node, visitor, context); + } + if (inStatementContainingYield) { beginScriptLoopBlock(); } @@ -2490,6 +2501,16 @@ namespace ts { emitWorker(OpCode.Yield, [expression], location); } + /** + * Emits a Yield operation for the provided expression. + * + * @param expression An optional value for the yield operation. + * @param location An optional source map location for the assignment. + */ + function emitYieldStar(expression?: Expression, location?: TextRange): void { + emitWorker(OpCode.YieldStar, [expression], location); + } + /** * Emits a Return operation for the provided expression. * diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0b8f04ed32a..82e27ab55a3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -404,15 +404,16 @@ namespace ts { HasDecorators = 1 << 11, // If the file has decorators (initialized by binding) HasParamDecorators = 1 << 12, // If the file has parameter decorators (initialized by binding) HasAsyncFunctions = 1 << 13, // If the file has async functions (initialized by binding) - HasJsxSpreadAttributes = 1 << 14, // If the file as JSX spread attributes (initialized by binding) - DisallowInContext = 1 << 15, // If node was parsed in a context where 'in-expressions' are not allowed - YieldContext = 1 << 16, // If node was parsed in the 'yield' context created when parsing a generator - DecoratorContext = 1 << 17, // If node was parsed as part of a decorator - AwaitContext = 1 << 18, // If node was parsed in the 'await' context created when parsing an async function - ThisNodeHasError = 1 << 19, // If the parser encountered an error when parsing the code that created this node - JavaScriptFile = 1 << 20, // If node was parsed in a JavaScript - ThisNodeOrAnySubNodesHasError = 1 << 21, // If this node or any of its children had an error - HasAggregatedChildData = 1 << 22, // If we've computed data from children and cached it in this node + HasGenerators = 1 << 14, // If the file has generators (initialized by binding) + HasJsxSpreadAttributes = 1 << 15, // If the file as JSX spread attributes (initialized by binding) + DisallowInContext = 1 << 16, // If node was parsed in a context where 'in-expressions' are not allowed + YieldContext = 1 << 17, // If node was parsed in the 'yield' context created when parsing a generator + DecoratorContext = 1 << 18, // If node was parsed as part of a decorator + AwaitContext = 1 << 19, // If node was parsed in the 'await' context created when parsing an async function + ThisNodeHasError = 1 << 20, // If the parser encountered an error when parsing the code that created this node + JavaScriptFile = 1 << 21, // If node was parsed in a JavaScript + ThisNodeOrAnySubNodesHasError = 1 << 22, // If this node or any of its children had an error + HasAggregatedChildData = 1 << 23, // If we've computed data from children and cached it in this node BlockScoped = Let | Const, @@ -3128,9 +3129,8 @@ namespace ts { LocalName = 1 << 18, // Ensure an export prefix is not added for an identifier that points to an exported declaration. Indented = 1 << 19, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). NoIndentation = 1 << 20, // Do not indent the node. - AsyncFunctionBody = 1 << 21, - ReuseTempVariableScope = 1 << 22, // Reuse the existing temp variable scope during emit. - CustomPrologue = 1 << 23, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). + ReuseTempVariableScope = 1 << 21, // Reuse the existing temp variable scope during emit. + CustomPrologue = 1 << 22, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). } /** Additional context provided to `visitEachChild` */ diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 496df578c19..cff24c79dd5 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1269,6 +1269,17 @@ interface PromiseLike { then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike; } +interface IteratorResult { + done: boolean; + value: T; +} + +interface Iterator { + next(value?: any): IteratorResult; + return?(value?: any): IteratorResult; + throw?(e?: any): IteratorResult; +} + interface ArrayLike { readonly length: number; readonly [n: number]: T;