diff --git a/Jakefile.js b/Jakefile.js index ed3333ff730a6..cd9f9bea0b724 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -50,6 +50,7 @@ var compilerSources = [ "transformers/module/module.ts", "transformers/jsx.ts", "transformers/es7.ts", + "transformers/generators.ts", "transformers/es6.ts", "transformer.ts", "sourcemap.ts", @@ -82,6 +83,7 @@ var servicesSources = [ "transformers/module/module.ts", "transformers/jsx.ts", "transformers/es7.ts", + "transformers/generators.ts", "transformers/es6.ts", "transformer.ts", "sourcemap.ts", @@ -747,7 +749,6 @@ function writeTestConfigFile(tests, light, taskConfigsFolder, workerCount, stack taskConfigsFolder: taskConfigsFolder, stackTraceLimit: stackTraceLimit }); - console.log('Running tests with config: ' + testConfigContents); fs.writeFileSync('test.config', testConfigContents); } diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index dec498b12eea0..baa585fb9ba37 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1638,7 +1638,7 @@ namespace ts { } } - function checkStrictModeNumericLiteral(node: LiteralExpression) { + function checkStrictModeNumericLiteral(node: NumericLiteral) { if (inStrictMode && node.isOctalLiteral) { file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); } @@ -1786,7 +1786,7 @@ namespace ts { case SyntaxKind.DeleteExpression: return checkStrictModeDeleteExpression(node); case SyntaxKind.NumericLiteral: - return checkStrictModeNumericLiteral(node); + return checkStrictModeNumericLiteral(node); case SyntaxKind.PostfixUnaryExpression: return checkStrictModePostfixUnaryExpression(node); case SyntaxKind.PrefixUnaryExpression: @@ -2568,6 +2568,7 @@ namespace ts { const modifierFlags = getModifierFlags(node); const body = node.body; const typeParameters = node.typeParameters; + const asteriskToken = node.asteriskToken; // A MethodDeclaration is TypeScript syntax if it is either async, abstract, overloaded, // generic, or has a decorator. @@ -2578,6 +2579,11 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } + // Currently, we only support generators that were originally async function bodies. + if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + transformFlags |= TransformFlags.AssertGenerator; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.MethodOrAccessorExcludes; } @@ -2625,7 +2631,7 @@ namespace ts { transformFlags = TransformFlags.AssertTypeScript; } else { - transformFlags = subtreeFlags; + transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; // If a FunctionDeclaration is exported, then it is either ES6 or TypeScript syntax. if (modifierFlags & ModifierFlags.Export) { @@ -2637,12 +2643,21 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - // If a FunctionDeclaration has an asterisk token, is exported, or its - // subtree has marked the container as needing to capture the lexical `this`, - // then this node is ES6 syntax. - if (asteriskToken || (subtreeFlags & TransformFlags.ES6FunctionSyntaxMask)) { + // If a FunctionDeclaration's subtree has marked the container as needing to capture the + // lexical this, or the function contains parameters with initializers, then this node is + // ES6 syntax. + if (subtreeFlags & TransformFlags.ES6FunctionSyntaxMask) { 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) { + transformFlags |= TransformFlags.AssertGenerator; + } } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -2659,12 +2674,22 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - // If a FunctionExpression contains an asterisk token, or its subtree has marked the container - // as needing to capture the lexical this, then this node is ES6 syntax. - if (asteriskToken || (subtreeFlags & TransformFlags.ES6FunctionSyntaxMask)) { + // If a FunctionExpression's subtree has marked the container as needing to capture the + // lexical this, or the function contains parameters with initializers, then this node is + // ES6 syntax. + if (subtreeFlags & TransformFlags.ES6FunctionSyntaxMask) { 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 + // down-level generator. + // Currently we do not support transforming any other generator fucntions + // down level. + if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) { + transformFlags |= TransformFlags.AssertGenerator; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.FunctionExcludes; } @@ -2794,7 +2819,7 @@ namespace ts { } function computeVariableDeclarationList(node: VariableDeclarationList, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; + let transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; if (subtreeFlags & TransformFlags.ContainsBindingPattern) { transformFlags |= TransformFlags.AssertES6; @@ -2859,11 +2884,15 @@ namespace ts { case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.ForOfStatement: - case SyntaxKind.YieldExpression: // These nodes are ES6 syntax. transformFlags |= TransformFlags.AssertES6; break; + case SyntaxKind.YieldExpression: + // This node is ES6 syntax. + transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsYield; + break; + case SyntaxKind.AnyKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.NeverKeyword: @@ -2985,6 +3014,12 @@ namespace ts { } break; + + case SyntaxKind.ReturnStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion; + break; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f1b7ab43f6ed..b4aecd2d1e6bc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -149,6 +149,7 @@ namespace ts { let getGlobalESSymbolConstructorSymbol: () => Symbol; let getGlobalPromiseConstructorSymbol: () => Symbol; + let tryGetGlobalPromiseConstructorSymbol: () => Symbol; let globalObjectType: ObjectType; let globalFunctionType: ObjectType; @@ -8337,10 +8338,13 @@ namespace ts { // can explicitly bound arguments objects if (symbol === argumentsSymbol) { const container = getContainingFunction(node); - if (container.kind === SyntaxKind.ArrowFunction) { - if (languageVersion < ScriptTarget.ES6) { + if (languageVersion < ScriptTarget.ES6) { + if (container.kind === SyntaxKind.ArrowFunction) { error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); } + 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); + } } if (node.flags & NodeFlags.AwaitContext) { @@ -12991,7 +12995,7 @@ namespace ts { return type; } - function checkNumericLiteral(node: LiteralExpression): Type { + function checkNumericLiteral(node: NumericLiteral): Type { // Grammar checking checkGrammarNumericLiteral(node); return numberType; @@ -13011,7 +13015,7 @@ namespace ts { case SyntaxKind.FalseKeyword: return booleanType; case SyntaxKind.NumericLiteral: - return checkNumericLiteral(node); + return checkNumericLiteral(node); case SyntaxKind.TemplateExpression: return checkTemplateExpression(node); case SyntaxKind.StringLiteral: @@ -14194,7 +14198,7 @@ namespace ts { * @param returnType The return type of a FunctionLikeDeclaration * @param location The node on which to report the error. */ - function checkCorrectPromiseType(returnType: Type, location: Node) { + function checkCorrectPromiseType(returnType: Type, location: Node, diagnostic: DiagnosticMessage, typeName?: string) { if (returnType === unknownType) { // The return type already had some other error, so we ignore and return // the unknown type. @@ -14213,7 +14217,7 @@ namespace ts { // 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(location, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + error(location, diagnostic, typeName); return unknownType; } @@ -14233,7 +14237,7 @@ namespace ts { function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration): Type { if (languageVersion >= ScriptTarget.ES6) { const returnType = getTypeFromTypeNode(node.type); - return checkCorrectPromiseType(returnType, node.type); + return checkCorrectPromiseType(returnType, node.type, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); } const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); @@ -14279,11 +14283,11 @@ namespace ts { const promiseConstructor = getNodeLinks(node.type).resolvedSymbol; if (!promiseConstructor || !symbolIsValue(promiseConstructor)) { + // try to fall back to global promise type. const typeName = promiseConstructor ? symbolToString(promiseConstructor) : typeToString(promiseType); - error(node, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeName); - return unknownType; + return checkCorrectPromiseType(promiseType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeName); } // If the Promise constructor, resolved locally, is an alias symbol we should mark it as referenced. @@ -14291,7 +14295,7 @@ namespace ts { // 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)) { + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) { return unknownType; } @@ -16272,7 +16276,7 @@ namespace ts { } return undefined; case SyntaxKind.NumericLiteral: - return +(e).text; + return +(e).text; case SyntaxKind.ParenthesizedExpression: return evalConstant((e).expression); case SyntaxKind.Identifier: @@ -17491,7 +17495,7 @@ namespace ts { if (objectType === unknownType) return undefined; const apparentType = getApparentType(objectType); if (apparentType === unknownType) return undefined; - return getPropertyOfType(apparentType, (node).text); + return getPropertyOfType(apparentType, (node).text); } break; } @@ -17976,6 +17980,11 @@ namespace ts { function getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind { // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); + const globalPromiseSymbol = tryGetGlobalPromiseConstructorSymbol(); + if (globalPromiseSymbol && valueSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; + } + const constructorType = valueSymbol ? getTypeOfSymbol(valueSymbol) : undefined; if (constructorType && isConstructorType(constructorType)) { return TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; @@ -17994,8 +18003,8 @@ namespace ts { else if (type.flags & TypeFlags.Any) { return TypeReferenceSerializationKind.ObjectType; } - else if (isTypeOfKind(type, TypeFlags.Void)) { - return TypeReferenceSerializationKind.VoidType; + else if (isTypeOfKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { + return TypeReferenceSerializationKind.VoidNullableOrNeverType; } else if (isTypeOfKind(type, TypeFlags.Boolean)) { return TypeReferenceSerializationKind.BooleanType; @@ -18293,6 +18302,7 @@ namespace ts { getGlobalPromiseLikeType = memoize(() => getGlobalType("PromiseLike", /*arity*/ 1)); getInstantiatedGlobalPromiseLikeType = memoize(createInstantiatedPromiseLikeType); getGlobalPromiseConstructorSymbol = memoize(() => getGlobalValueSymbol("Promise")); + tryGetGlobalPromiseConstructorSymbol = memoize(() => getGlobalSymbol("Promise", SymbolFlags.Value, /*diagnostic*/ undefined) && getGlobalPromiseConstructorSymbol()); getGlobalPromiseConstructorLikeType = memoize(() => getGlobalType("PromiseConstructorLike")); getGlobalThenableType = memoize(createThenableType); @@ -18348,6 +18358,9 @@ namespace ts { } if (requestedExternalEmitHelpers & NodeFlags.HasAsyncFunctions) { verifyHelperSymbol(exports, "__awaiter", SymbolFlags.Value); + if (languageVersion < ScriptTarget.ES6) { + verifyHelperSymbol(exports, "__generator", SymbolFlags.Value); + } } } } @@ -18654,10 +18667,6 @@ namespace ts { } function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { - if (languageVersion < ScriptTarget.ES6) { - return grammarErrorOnNode(asyncModifier, Diagnostics.Async_functions_are_only_available_when_targeting_ECMAScript_2015_or_higher); - } - switch (node.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.FunctionDeclaration: @@ -18967,7 +18976,7 @@ namespace ts { // Grammar checking for computedPropertyName and shorthandPropertyAssignment checkGrammarForInvalidQuestionMark(prop, (prop).questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); if (name.kind === SyntaxKind.NumericLiteral) { - checkGrammarNumericLiteral(name); + checkGrammarNumericLiteral(name); } currentKind = Property; } @@ -19489,7 +19498,7 @@ namespace ts { } } - function checkGrammarNumericLiteral(node: LiteralExpression): boolean { + function checkGrammarNumericLiteral(node: NumericLiteral): boolean { // Grammar checking if (node.isOctalLiteral && languageVersion >= ScriptTarget.ES5) { return grammarErrorOnNode(node, Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5f1fe111c2d12..cf2d85c018fda 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -426,13 +426,14 @@ namespace ts { return ~low; } - export function reduceLeft(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceLeft(array: T[], f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T): T; - export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { - if (array) { - const count = array.length; - if (count > 0) { - let pos = 0; + export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T { + if (array && array.length > 0) { + const size = array.length; + if (size > 0) { + let pos = start === undefined || start < 0 ? 0 : start; + const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; let result: T; if (arguments.length <= 2) { result = array[pos]; @@ -441,7 +442,7 @@ namespace ts { else { result = initial; } - while (pos < count) { + while (pos <= end) { result = f(result, array[pos], pos); pos++; } @@ -451,12 +452,14 @@ namespace ts { return initial; } - export function reduceRight(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceRight(array: T[], f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T): T; - export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { + export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T { if (array) { - let pos = array.length - 1; - if (pos >= 0) { + const size = array.length; + if (size > 0) { + let pos = start === undefined || start > size - 1 ? size - 1 : start; + const end = count === undefined || pos - count < 0 ? 0 : pos - count; let result: T; if (arguments.length <= 2) { result = array[pos]; @@ -465,7 +468,7 @@ namespace ts { else { result = initial; } - while (pos >= 0) { + while (pos >= end) { result = f(result, array[pos], pos); pos--; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 61c3593077edb..8886a83f9a5af 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -827,10 +827,6 @@ "category": "Error", "code": 1308 }, - "Async functions are only available when targeting ECMAScript 2015 or higher.": { - "category": "Error", - "code": 1311 - }, "'=' can only be used in an object literal property inside a destructuring assignment.": { "category": "Error", "code": 1312 @@ -1703,7 +1699,7 @@ "category": "Error", "code": 2521 }, - "The 'arguments' object cannot be referenced in an async arrow function. Consider using a standard async function expression.": { + "The 'arguments' object cannot be referenced in an async function or method in ES3 and ES5. Consider using a standard function or method.": { "category": "Error", "code": 2522 }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f1de958cbf6d1..a21902d2c593f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -71,6 +71,45 @@ 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; + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (1) { + if (_.done) switch (op[0]) { + case 0: return { value: void 0, done: true }; + case 1: case 6: throw op[1]; + case 2: return { value: op[1], done: true }; + } + try { + switch (f = 1, op[0]) { + case 0: case 1: sent = op; break; + case 4: return _.label++, { value: op[1], done: false }; + case 7: op = _.stack.pop(), _.trys.pop(); continue; + default: + var r = _.trys.length > 0 && _.trys[_.trys.length - 1]; + if (!r && (op[0] === 6 || op[0] === 2)) { _.done = 1; continue; } + if (op[0] === 3 && (!r || (op[1] > r[0] && op[1] < r[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < r[1]) { _.label = r[1], sent = op; break; } + if (r && _.label < r[2]) { _.label = r[2], _.stack.push(op); break; } + if (r[2]) { _.stack.pop(); } + _.trys.pop(); + continue; + } + op = body.call(thisArg, _); + } + catch (e) { op = [6, e]; } + finally { f = 0, sent = void 0; } + } + } + return { + next: function (v) { return step([0, v]); }, + "throw": function (v) { return step([1, v]); }, + "return": function (v) { return step([2, v]); } + }; +};`; + // emit output for the __export helper function const exportStarHelper = ` function __export(m) { @@ -685,6 +724,8 @@ const _super = (function (geti, seti) { switch (kind) { // Literals case SyntaxKind.NumericLiteral: + return emitNumericLiteral(node); + case SyntaxKind.StringLiteral: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: @@ -773,6 +814,13 @@ const _super = (function (geti, seti) { // // SyntaxKind.NumericLiteral + function emitNumericLiteral(node: NumericLiteral) { + emitLiteral(node); + if (node.trailingComment) { + write(` /*${node.trailingComment}*/`); + } + } + // SyntaxKind.StringLiteral // SyntaxKind.RegularExpressionLiteral // SyntaxKind.NoSubstitutionTemplateLiteral @@ -1563,15 +1611,21 @@ const _super = (function (geti, seti) { increaseIndent(); } - const savedTempFlags = tempFlags; - tempFlags = 0; - emitSignatureHead(node); - emitBlockFunctionBody(node, body); + if (node.emitFlags & NodeEmitFlags.ReuseTempVariableScope) { + emitSignatureHead(node); + emitBlockFunctionBody(node, body); + } + else { + const savedTempFlags = tempFlags; + tempFlags = 0; + emitSignatureHead(node); + emitBlockFunctionBody(node, body); + tempFlags = savedTempFlags; + } + if (indentedFlag) { decreaseIndent(); } - - tempFlags = savedTempFlags; } else { emitSignatureHead(node); @@ -2157,6 +2211,10 @@ const _super = (function (geti, seti) { if (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions) { writeLines(awaiterHelper); + if (languageVersion < ScriptTarget.ES6) { + writeLines(generatorHelper); + } + awaiterEmitted = true; helpersEmitted = true; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index e516d9d1463b8..57ddd67b0a30a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -102,7 +102,7 @@ namespace ts { export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange): StringLiteral; export function createLiteral(value: string, location?: TextRange): StringLiteral; - export function createLiteral(value: number, location?: TextRange): LiteralExpression; + export function createLiteral(value: number, location?: TextRange): NumericLiteral; export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange): PrimaryExpression { if (typeof value === "number") { @@ -139,7 +139,7 @@ namespace ts { return node; } - export function createTempVariable(recordTempVariable: (node: Identifier) => void, location?: TextRange): Identifier { + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); name.text = ""; name.originalKeywordKind = SyntaxKind.Unknown; @@ -324,8 +324,9 @@ namespace ts { const node = createNode(SyntaxKind.ArrayLiteralExpression, location); node.elements = parenthesizeListElements(createNodeArray(elements)); if (multiLine) { - node.multiLine = multiLine; + node.multiLine = true; } + return node; } @@ -333,8 +334,9 @@ namespace ts { const node = createNode(SyntaxKind.ObjectLiteralExpression, location); node.properties = createNodeArray(properties); if (multiLine) { - node.multiLine = multiLine; + node.multiLine = true; } + return node; } @@ -363,6 +365,13 @@ namespace ts { return node; } + export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + if (node.expression !== expression || node.argumentExpression !== argumentExpression) { + return updateNode(createElementAccess(expression, argumentExpression, node), node); + } + return node; + } + export function createCall(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[], location?: TextRange, flags?: NodeFlags) { const node = createNode(SyntaxKind.CallExpression, location, flags); node.expression = parenthesizeForAccess(expression); @@ -538,6 +547,7 @@ namespace ts { if (multiLine) { block.multiLine = true; } + return block; } @@ -640,7 +650,7 @@ namespace ts { return node; } - export function createCaseBlock(clauses: CaseClause[], location?: TextRange): CaseBlock { + export function createCaseBlock(clauses: CaseOrDefaultClause[], location?: TextRange): CaseBlock { const node = createNode(SyntaxKind.CaseBlock, location); node.clauses = createNodeArray(clauses); return node; @@ -655,6 +665,13 @@ namespace ts { return node; } + export function updateFor(node: ForStatement, initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement) { + if (node.initializer !== initializer || node.condition !== condition || node.incrementor !== incrementor || node.statement !== statement) { + return updateNode(createFor(initializer, condition, incrementor, statement, node), node); + } + return node; + } + export function createLabel(label: string | Identifier, statement: Statement, location?: TextRange) { const node = createNode(SyntaxKind.LabeledStatement, location); node.label = typeof label === "string" ? createIdentifier(label) : label; @@ -662,17 +679,17 @@ namespace ts { return node; } - export function createDo(expression: Expression, statement: Statement, location?: TextRange) { + export function createDo(statement: Statement, expression: Expression, location?: TextRange) { const node = createNode(SyntaxKind.DoStatement, location); - node.expression = expression; node.statement = statement; + node.expression = expression; return node; } - export function createWhile(statement: Statement, expression: Expression, location?: TextRange) { + export function createWhile(expression: Expression, statement: Statement, location?: TextRange) { const node = createNode(SyntaxKind.WhileStatement, location); - node.statement = statement; node.expression = expression; + node.statement = statement; return node; } @@ -684,6 +701,13 @@ namespace ts { return node; } + export function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { + if (node.initializer !== initializer || node.expression !== expression || node.statement !== statement) { + return updateNode(createForIn(initializer, expression, statement, node), node); + } + return node; + } + export function createForOf(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { const node = createNode(SyntaxKind.ForOfStatement, location); node.initializer = initializer; @@ -698,6 +722,35 @@ namespace ts { return node; } + export function createWith(expression: Expression, statement: Statement, location?: TextRange): ReturnStatement { + const node = createNode(SyntaxKind.WithStatement, location); + node.expression = expression; + node.statement = statement; + return node; + } + + export function createThrow(expression: Expression, location?: TextRange): ReturnStatement { + const node = createNode(SyntaxKind.ThrowStatement, location); + node.expression = expression; + return node; + } + + export function createTryCatchFinally(tryBlock: Block, catchClause: CatchClause, finallyBlock: Block, location?: TextRange) { + const node = createNode(SyntaxKind.TryStatement, location); + node.tryBlock = tryBlock; + node.catchClause = catchClause; + node.finallyBlock = finallyBlock; + return node; + } + + export function createTryCatch(tryBlock: Block, catchClause: CatchClause, location?: TextRange) { + return createTryCatchFinally(tryBlock, catchClause, /*finallyBlock*/ undefined, location); + } + + export function createTryFinally(tryBlock: Block, finallyBlock: Block, location?: TextRange) { + return createTryCatchFinally(tryBlock, /*catchClause*/ undefined, finallyBlock, location); + } + export function updateReturn(node: ReturnStatement, expression: Expression) { if (node.expression !== expression) { return updateNode(createReturn(expression, /*location*/ node), node); @@ -935,13 +988,13 @@ namespace ts { return node; } - export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, setNodeEmitFlags: (node: Node, flags: NodeEmitFlags) => void, location?: TextRange): MemberExpression { + export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { if (isComputedPropertyName(memberName)) { return createElementAccess(target, memberName.expression, location); } else { const expression = isIdentifier(memberName) ? createPropertyAccess(target, memberName, location) : createElementAccess(target, memberName, location); - setNodeEmitFlags(expression, expression.emitFlags ? NodeEmitFlags.NoNestedSourceMaps | expression.emitFlags : NodeEmitFlags.NoNestedSourceMaps); + expression.emitFlags |= NodeEmitFlags.NoNestedSourceMaps; return expression; } } @@ -1124,6 +1177,18 @@ namespace ts { } export function createAwaiterHelper(externalHelpersModuleName: Identifier | undefined, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + const generatorFunc = createFunctionExpression( + createNode(SyntaxKind.AsteriskToken), + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ [], + /*type*/ undefined, + body + ); + + // Mark this node as originally an async function + generatorFunc.emitFlags |= NodeEmitFlags.AsyncFunctionBody; + return createCall( createHelperName(externalHelpersModuleName, "__awaiter"), /*typeArguments*/ undefined, @@ -1131,14 +1196,7 @@ namespace ts { createThis(), hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), - createFunctionExpression( - createNode(SyntaxKind.AsteriskToken), - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ [], - /*type*/ undefined, - body - ) + generatorFunc ] ); } @@ -1336,19 +1394,29 @@ namespace ts { thisArg: Expression; } - function shouldBeCapturedInTempVariable(node: Expression): boolean { - switch (skipParentheses(node).kind) { + function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { + const target = skipParentheses(node); + switch (target.kind) { case SyntaxKind.Identifier: + return cacheIdentifiers; case SyntaxKind.ThisKeyword: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: return false; + case SyntaxKind.ArrayLiteralExpression: + const elements = (target).elements; + if (elements.length === 0) { + return false; + } + return true; + case SyntaxKind.ObjectLiteralExpression: + return (target).properties.length > 0; default: return true; } } - export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget): CallBinding { + export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers?: boolean): CallBinding { const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); let thisArg: Expression; let target: LeftHandSideExpression; @@ -1363,7 +1431,7 @@ namespace ts { else { switch (callee.kind) { case SyntaxKind.PropertyAccessExpression: { - if (shouldBeCapturedInTempVariable((callee).expression)) { + if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { // for `a.b()` target is `(_a = a).b` and thisArg is `_a` thisArg = createTempVariable(recordTempVariable); target = createPropertyAccess( @@ -1384,7 +1452,7 @@ namespace ts { } case SyntaxKind.ElementAccessExpression: { - if (shouldBeCapturedInTempVariable((callee).expression)) { + if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` thisArg = createTempVariable(recordTempVariable); target = createElementAccess( @@ -1444,6 +1512,124 @@ namespace ts { } } + export function createExpressionForObjectLiteralElement(node: ObjectLiteralExpression, property: ObjectLiteralElement, receiver: Expression): Expression { + switch (property.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return createExpressionForAccessorDeclaration(node.properties, property, receiver, node.multiLine); + case SyntaxKind.PropertyAssignment: + return createExpressionForPropertyAssignment(property, receiver); + case SyntaxKind.ShorthandPropertyAssignment: + return createExpressionForShorthandPropertyAssignment(property, receiver); + case SyntaxKind.MethodDeclaration: + return createExpressionForMethodDeclaration(property, receiver); + } + } + + function createExpressionForAccessorDeclaration(properties: NodeArray, property: AccessorDeclaration, receiver: Expression, multiLine: boolean) { + const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); + if (property === firstAccessor) { + const properties: ObjectLiteralElement[] = []; + if (getAccessor) { + const getterFunction = createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + getAccessor.parameters, + /*type*/ undefined, + getAccessor.body, + /*location*/ getAccessor + ); + setOriginalNode(getterFunction, getAccessor); + const getter = createPropertyAssignment("get", getterFunction); + properties.push(getter); + } + + if (setAccessor) { + const setterFunction = createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + setAccessor.parameters, + /*type*/ undefined, + setAccessor.body, + /*location*/ setAccessor + ); + setOriginalNode(setterFunction, setAccessor); + const setter = createPropertyAssignment("set", setterFunction); + properties.push(setter); + } + + properties.push(createPropertyAssignment("enumerable", createLiteral(true))); + properties.push(createPropertyAssignment("configurable", createLiteral(true))); + + const expression = createCall( + createPropertyAccess(createIdentifier("Object"), "defineProperty"), + /*typeArguments*/ undefined, + [ + receiver, + createExpressionForPropertyName(property.name), + createObjectLiteral(properties, /*location*/ undefined, multiLine) + ], + /*location*/ firstAccessor + ); + + return aggregateTransformFlags(expression); + } + + return undefined; + } + + function createExpressionForPropertyAssignment(property: PropertyAssignment, receiver: Expression) { + return aggregateTransformFlags( + setOriginalNode( + createAssignment( + createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), + property.initializer, + /*location*/ property + ), + /*original*/ property + ) + ); + } + + function createExpressionForShorthandPropertyAssignment(property: ShorthandPropertyAssignment, receiver: Expression) { + return aggregateTransformFlags( + setOriginalNode( + createAssignment( + createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), + getSynthesizedClone(property.name), + /*location*/ property + ), + /*original*/ property + ) + ); + } + + function createExpressionForMethodDeclaration(method: MethodDeclaration, receiver: Expression) { + return aggregateTransformFlags( + setOriginalNode( + createAssignment( + createMemberAccessForPropertyName(receiver, method.name, /*location*/ method.name), + setOriginalNode( + createFunctionExpression( + method.asteriskToken, + /*name*/ undefined, + /*typeParameters*/ undefined, + method.parameters, + /*type*/ undefined, + method.body, + /*location*/ method + ), + /*original*/ method + ), + /*location*/ method + ), + /*original*/ method + ) + ); + } + // Utilities function isUseStrictPrologue(node: ExpressionStatement): boolean { @@ -1459,28 +1645,36 @@ namespace ts { * @param target: result statements array * @param source: origin statements array * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives + * @param visitor: Optional callback used to visit any custom prologue directives. */ - export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean): number { + export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { Debug.assert(target.length === 0, "PrologueDirectives should be at the first statement in the target statements array"); let foundUseStrict = false; - for (let i = 0; i < source.length; i++) { - if (isPrologueDirective(source[i])) { - if (isUseStrictPrologue(source[i] as ExpressionStatement)) { + let statementOffset = 0; + const numStatements = source.length; + while (statementOffset < numStatements) { + const statement = source[statementOffset]; + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement as ExpressionStatement)) { foundUseStrict = true; } - - target.push(source[i]); + target.push(statement); } else { if (ensureUseStrict && !foundUseStrict) { target.push(startOnNewLine(createStatement(createLiteral("use strict")))); + foundUseStrict = true; + } + if (statement.emitFlags & NodeEmitFlags.CustomPrologue) { + target.push(visitor ? visitNode(statement, visitor, isStatement) : statement); + } + else { + break; } - - return i; } + statementOffset++; } - - return source.length; + return statementOffset; } /** diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index e71c8a9256b75..03a34bc1e277f 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -3,6 +3,7 @@ /// /// /// +/// /// /// /// @@ -179,6 +180,7 @@ namespace ts { if (languageVersion < ScriptTarget.ES6) { transformers.push(transformES6); + transformers.push(transformGenerators); } return transformers; diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 060ad043e7828..7deaa38d1489c 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -376,6 +376,9 @@ namespace ts { case SyntaxKind.TemplateExpression: return visitTemplateExpression(node); + case SyntaxKind.YieldExpression: + return visitYieldExpression(node); + case SyntaxKind.SuperKeyword: return visitSuperKeyword(node); @@ -927,21 +930,27 @@ namespace ts { // of an initializer, we must emit that expression to preserve side effects. if (name.elements.length > 0) { statements.push( - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList( - flattenParameterDestructuring(context, parameter, temp, visitor) - ) + setNodeEmitFlags( + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList( + flattenParameterDestructuring(context, parameter, temp, visitor) + ) + ), + NodeEmitFlags.CustomPrologue ) ); } else if (initializer) { statements.push( - createStatement( - createAssignment( - temp, - visitNode(initializer, visitor, isExpression) - ) + setNodeEmitFlags( + createStatement( + createAssignment( + temp, + visitNode(initializer, visitor, isExpression) + ) + ), + NodeEmitFlags.CustomPrologue ) ); } @@ -978,7 +987,7 @@ namespace ts { /*location*/ parameter ); statement.startsOnNewLine = true; - setNodeEmitFlags(statement, NodeEmitFlags.NoTokenSourceMaps | NodeEmitFlags.NoTrailingSourceMap); + setNodeEmitFlags(statement, NodeEmitFlags.NoTokenSourceMaps | NodeEmitFlags.NoTrailingSourceMap | NodeEmitFlags.CustomPrologue); statements.push(statement); } @@ -1020,16 +1029,19 @@ namespace ts { // var param = []; statements.push( - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - declarationName, - /*type*/ undefined, - createArrayLiteral([]) - ) - ]), - /*location*/ parameter + setNodeEmitFlags( + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + declarationName, + /*type*/ undefined, + createArrayLiteral([]) + ) + ]), + /*location*/ parameter + ), + NodeEmitFlags.CustomPrologue ) ); @@ -1062,7 +1074,7 @@ namespace ts { ]) ); - setNodeEmitFlags(forStatement, NodeEmitFlags.SourceMapAdjustRestParameterLoop); + setNodeEmitFlags(forStatement, NodeEmitFlags.SourceMapAdjustRestParameterLoop | NodeEmitFlags.CustomPrologue); startOnNewLine(forStatement); statements.push(forStatement); } @@ -1087,7 +1099,7 @@ namespace ts { ]) ); - setNodeEmitFlags(captureThisStatement, NodeEmitFlags.NoComments); + setNodeEmitFlags(captureThisStatement, NodeEmitFlags.NoComments | NodeEmitFlags.CustomPrologue); setSourceMapRange(captureThisStatement, node); statements.push(captureThisStatement); } @@ -1159,7 +1171,6 @@ namespace ts { createMemberAccessForPropertyName( receiver, visitNode(member.name, visitor, isPropertyName), - setNodeEmitFlags, /*location*/ member.name ), func @@ -1345,7 +1356,7 @@ namespace ts { if (isBlock(body)) { // ensureUseStrict is false because no new prologue-directive should be added. // addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array - statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false); + statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); } addCaptureThisForNodeIfNeeded(statements, node); @@ -1859,22 +1870,24 @@ namespace ts { * * @param node An ObjectLiteralExpression node. */ - function visitObjectLiteralExpression(node: ObjectLiteralExpression): LeftHandSideExpression { + function visitObjectLiteralExpression(node: ObjectLiteralExpression): Expression { // We are here because a ComputedPropertyName was used somewhere in the expression. const properties = node.properties; const numProperties = properties.length; // Find the first computed property. // Everything until that point can be emitted as part of the initial object literal. - let numInitialNonComputedProperties = numProperties; - for (let i = 0, n = properties.length; i < n; i++) { - if (properties[i].name.kind === SyntaxKind.ComputedPropertyName) { - numInitialNonComputedProperties = i; + let numInitialProperties = numProperties; + for (let i = 0; i < numProperties; i++) { + const property = properties[i]; + if (property.transformFlags & TransformFlags.ContainsYield + || property.name.kind === SyntaxKind.ComputedPropertyName) { + numInitialProperties = i; break; } } - Debug.assert(numInitialNonComputedProperties !== numProperties); + Debug.assert(numInitialProperties !== numProperties); // For computed properties, we need to create a unique handle to the object // literal so we can modify it without risking internal assignments tainting the object. @@ -1887,7 +1900,7 @@ namespace ts { temp, setNodeEmitFlags( createObjectLiteral( - visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialNonComputedProperties), + visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialProperties), /*location*/ undefined, node.multiLine ), @@ -1897,12 +1910,12 @@ namespace ts { node.multiLine ); - addObjectLiteralMembers(expressions, node, temp, numInitialNonComputedProperties); + addObjectLiteralMembers(expressions, node, temp, numInitialProperties); // We need to clone the temporary identifier so that we can write it on a // new line addNode(expressions, getMutableClone(temp), node.multiLine); - return createParen(inlineExpressions(expressions)); + return inlineExpressions(expressions); } function shouldConvertIterationStatementBody(node: IterationStatement): boolean { @@ -2290,10 +2303,10 @@ namespace ts { * @param numInitialNonComputedProperties The number of initial properties without * computed property names. */ - function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, numInitialNonComputedProperties: number) { + function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, start: number) { const properties = node.properties; const numProperties = properties.length; - for (let i = numInitialNonComputedProperties; i < numProperties; i++) { + for (let i = start; i < numProperties; i++) { const property = properties[i]; switch (property.kind) { case SyntaxKind.GetAccessor: @@ -2335,8 +2348,7 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(property.name, visitor, isPropertyName), - setNodeEmitFlags + visitNode(property.name, visitor, isPropertyName) ), visitNode(property.initializer, visitor, isExpression), /*location*/ property @@ -2354,8 +2366,7 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(property.name, visitor, isPropertyName), - setNodeEmitFlags + visitNode(property.name, visitor, isPropertyName) ), getSynthesizedClone(property.name), /*location*/ property @@ -2373,8 +2384,7 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(method.name, visitor, isPropertyName), - setNodeEmitFlags + visitNode(method.name, visitor, isPropertyName) ), transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined), /*location*/ method @@ -2414,6 +2424,16 @@ namespace ts { ); } + /** + * Visits a YieldExpression node. + * + * @param node A YieldExpression node. + */ + function visitYieldExpression(node: YieldExpression): Expression { + // `yield` expressions are transformed using the generators transformer. + return visitEachChild(node, visitor, context); + } + /** * Visits an ArrayLiteralExpression that contains a spread element. * @@ -2982,4 +3002,4 @@ namespace ts { return isIdentifier(expression) && expression === parameter.name; } } -} +} \ No newline at end of file diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts new file mode 100644 index 0000000000000..f96aacdce454e --- /dev/null +++ b/src/compiler/transformers/generators.ts @@ -0,0 +1,3022 @@ +/// +/// + +// Transforms generator functions into a compatible ES5 representation with similar runtime +// semantics. This is accomplished by first transforming the body of each generator +// function into an intermediate representation that is the compiled into a JavaScript +// switch statement. +// +// Many functions in this transformer will contain comments indicating the expected +// intermediate representation. For illustrative purposes, the following intermediate +// language is used to define this intermediate representation: +// +// .nop - Performs no operation. +// .local NAME, ... - Define local variable declarations. +// .mark LABEL - Mark the location of a label. +// .br LABEL - Jump to a label. If jumping out of a protected +// region, all .finally blocks are executed. +// .brtrue LABEL, (x) - Jump to a label IIF the expression `x` is truthy. +// If jumping out of a protected region, all .finally +// blocks are executed. +// .brfalse LABEL, (x) - Jump to a label IIF the expression `x` is falsey. +// If jumping out of a protected region, all .finally +// blocks are executed. +// .yield RESUME, (x) - Yield the value of the optional expression `x`. +// Resume at the label RESUME. +// .yieldstar RESUME, (x) - Delegate yield to the value of the optional +// expression `x`. Resume at the label RESUME. +// .loop CONTINUE, BREAK - Marks the beginning of a loop. Any "continue" or +// "break" abrupt completions jump to the CONTINUE or +// BREAK labels, respectively. +// .endloop - Marks the end of a loop. +// .with (x) - Marks the beginning of a WithStatement block, using +// the supplied expression. +// .endwith - Marks the end of a WithStatement. +// .switch - Marks the beginning of a SwitchStatement. +// .endswitch - Marks the end of a SwitchStatement. +// .labeled NAME - Marks the beginning of a LabeledStatement with the +// supplied name. +// .endlabeled - Marks the end of a LabeledStatement. +// .try TRY, CATCH, FINALLY, END - Marks the beginning of a protected region, and the +// labels for each block. +// .catch (x) - Marks the beginning of a catch block. +// .finally - Marks the beginning of a finally block. +// .endfinally - Marks the end of a finally block. +// .endtry - Marks the end of a protected region. +// .throw (x) - Throws the value of the expression `x`. +// .return (x) - Returns the value of the expression `x`. +// +// In addition, the illustrative intermediate representation introduces some special +// variables: +// +// %sent% - Either returns the next value sent to the generator, +// returns the result of a delegated yield, or throws +// the exception sent to the generator. +// %error% - Returns the value of the current exception in a +// catch block. +// +// This intermediate representation is then compiled into JavaScript syntax. The resulting +// compilation output looks something like the following: +// +// function f() { +// var /*locals*/; +// /*functions*/ +// return __generator(function (state) { +// switch (state.label) { +// /*cases per label*/ +// } +// }); +// } +// +// Each of the above instructions corresponds to JavaScript emit similar to the following: +// +// .local NAME | var NAME; +// -------------------------------|---------------------------------------------- +// .mark LABEL | case LABEL: +// -------------------------------|---------------------------------------------- +// .br LABEL | return [3 /*break*/, LABEL]; +// -------------------------------|---------------------------------------------- +// .brtrue LABEL, (x) | if (x) return [3 /*break*/, LABEL]; +// -------------------------------|---------------------------------------------- +// .brfalse LABEL, (x) | if (!(x)) return [3, /*break*/, LABEL]; +// -------------------------------|---------------------------------------------- +// .yield RESUME, (x) | return [4 /*yield*/, x]; +// .mark RESUME | case RESUME: +// a = %sent%; | a = state.sent(); +// -------------------------------|---------------------------------------------- +// .yieldstar RESUME, (X) | return [5 /*yield**/, x]; +// .mark RESUME | case RESUME: +// a = %sent%; | a = state.sent(); +// -------------------------------|---------------------------------------------- +// .with (_a) | with (_a) { +// a(); | a(); +// | } +// | state.label = LABEL; +// .mark LABEL | case LABEL: +// | with (_a) { +// b(); | b(); +// | } +// .endwith | +// -------------------------------|---------------------------------------------- +// | case 0: +// | state.trys = []; +// | ... +// .try TRY, CATCH, FINALLY, END | +// .mark TRY | case TRY: +// | state.trys.push([TRY, CATCH, FINALLY, END]); +// .nop | +// a(); | a(); +// .br END | return [3 /*break*/, END]; +// .catch (e) | +// .mark CATCH | case CATCH: +// | e = state.error; +// b(); | b(); +// .br END | return [3 /*break*/, END]; +// .finally | +// .mark FINALLY | case FINALLY: +// c(); | c(); +// .endfinally | return [7 /*endfinally*/]; +// .endtry | +// .mark END | case END: + +/*@internal*/ +namespace ts { + type Label = number; + + const enum OpCode { + Nop, // No operation, used to force a new case in the state machine + Statement, // A regular javascript statement + Assign, // An assignment + Break, // A break instruction used to jump to a label + BreakWhenTrue, // A break instruction used to jump to a label if a condition evaluates to true + BreakWhenFalse, // A break instruction used to jump to a label if a condition evaluates to false + Yield, // A completion instruction for the `yield` keyword + YieldStar, // A completion instruction for the `yield*` keyword (not implemented, but reserved for future use) + Return, // A completion instruction for the `return` keyword + Throw, // A completion instruction for the `throw` keyword + Endfinally // Marks the end of a `finally` block + } + + type OperationArguments = [Label] | [Label, Expression] | [Statement] | [Expression] | [Expression, Expression]; + + // whether a generated code block is opening or closing at the current operation for a FunctionBuilder + const enum BlockAction { + Open, + Close, + } + + // the kind for a generated code block in a FunctionBuilder + const enum CodeBlockKind { + Exception, + With, + Switch, + Loop, + Labeled + } + + // the state for a generated code exception block + const enum ExceptionBlockState { + Try, + Catch, + Finally, + Done + } + + // A generated code block + interface CodeBlock { + kind: CodeBlockKind; + } + + // a generated exception block, used for 'try' statements + interface ExceptionBlock extends CodeBlock { + state: ExceptionBlockState; + startLabel: Label; + catchVariable?: Identifier; + catchLabel?: Label; + finallyLabel?: Label; + endLabel: Label; + } + + // A generated code that tracks the target for 'break' statements in a LabeledStatement. + interface LabeledBlock extends CodeBlock { + labelText: string; + isScript: boolean; + breakLabel: Label; + } + + // a generated block that tracks the target for 'break' statements in a 'switch' statement + interface SwitchBlock extends CodeBlock { + isScript: boolean; + breakLabel: Label; + } + + // a generated block that tracks the targets for 'break' and 'continue' statements, used for iteration statements + interface LoopBlock extends CodeBlock { + continueLabel: Label; + isScript: boolean; + breakLabel: Label; + } + + // a generated block associated with a 'with' statement + interface WithBlock extends CodeBlock { + expression: Identifier; + startLabel: Label; + endLabel: Label; + } + + // NOTE: changes to this enum should be reflected in the __generator helper. + const enum Instruction { + Next = 0, + Throw = 1, + Return = 2, + Break = 3, + Yield = 4, + YieldStar = 5, + Catch = 6, + Endfinally = 7, + } + + const instructionNames: Map = { + [Instruction.Return]: "return", + [Instruction.Break]: "break", + [Instruction.Yield]: "yield", + [Instruction.YieldStar]: "yield*", + [Instruction.Endfinally]: "endfinally", + }; + + export function transformGenerators(context: TransformationContext) { + const { + startLexicalEnvironment, + endLexicalEnvironment, + hoistFunctionDeclaration, + hoistVariableDeclaration, + setSourceMapRange, + setCommentRange, + setNodeEmitFlags + } = context; + + const compilerOptions = context.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const resolver = context.getEmitResolver(); + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + + let currentSourceFile: SourceFile; + let renamedCatchVariables: Map; + let renamedCatchVariableDeclarations: Map; + + let inGeneratorFunctionBody: boolean; + let inStatementContainingYield: boolean; + + // The following three arrays store information about generated code blocks. + // All three arrays are correlated by their index. This approach is used over allocating + // objects to store the same information to avoid GC overhead. + // + let blocks: CodeBlock[]; // Information about the code block + let blockOffsets: number[]; // The operation offset at which a code block begins or ends + let blockActions: BlockAction[]; // Whether the code block is opened or closed + let blockStack: CodeBlock[]; // A stack of currently open code blocks + + // Labels are used to mark locations in the code that can be the target of a Break (jump) + // operation. These are translated into case clauses in a switch statement. + // The following two arrays are correlated by their index. This approach is used over + // allocating objects to store the same information to avoid GC overhead. + // + let labelOffsets: number[]; // The operation offset at which the label is defined. + let labelExpressions: LiteralExpression[][]; // The NumericLiteral nodes bound to each label. + let nextLabelId = 1; // The next label id to use. + + // Operations store information about generated code for the function body. This + // Includes things like statements, assignments, breaks (jumps), and yields. + // The following three arrays are correlated by their index. This approach is used over + // allocating objects to store the same information to avoid GC overhead. + // + let operations: OpCode[]; // The operation to perform. + let operationArguments: OperationArguments[]; // The arguments to the operation. + let operationLocations: TextRange[]; // The source map location for the operation. + + let state: Identifier; // The name of the state object used by the generator at runtime. + + // The following variables store information used by the `build` function: + // + let blockIndex = 0; // The index of the current block. + let labelNumber = 0; // The current label number. + let labelNumbers: number[][]; + let lastOperationWasAbrupt: boolean; // Indicates whether the last operation was abrupt (break/continue). + let lastOperationWasCompletion: boolean; // Indicates whether the last operation was a completion (return/throw). + let clauses: CaseClause[]; // The case clauses generated for labels. + let statements: Statement[]; // The statements for the current label. + let exceptionBlockStack: ExceptionBlock[]; // A stack of containing exception blocks. + let currentExceptionBlock: ExceptionBlock; // The current exception block. + let withBlockStack: WithBlock[]; // A stack containing `with` blocks. + + return transformSourceFile; + + function transformSourceFile(node: SourceFile) { + if (node.transformFlags & TransformFlags.ContainsGenerator) { + currentSourceFile = node; + node = visitEachChild(node, visitor, context); + currentSourceFile = undefined; + } + + return node; + } + + /** + * Visits a node. + * + * @param node The node to visit. + */ + function visitor(node: Node): VisitResult { + const transformFlags = node.transformFlags; + if (inStatementContainingYield) { + return visitJavaScriptInStatementContainingYield(node); + } + else if (inGeneratorFunctionBody) { + return visitJavaScriptInGeneratorFunctionBody(node); + } + else if (transformFlags & TransformFlags.Generator) { + return visitGenerator(node); + } + else if (transformFlags & TransformFlags.ContainsGenerator) { + return visitEachChild(node, visitor, context); + } + else { + return node; + } + } + + /** + * Visits a node that is contained within a statement that contains yield. + * + * @param node The node to visit. + */ + function visitJavaScriptInStatementContainingYield(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.DoStatement: + return visitDoStatement(node); + case SyntaxKind.WhileStatement: + return visitWhileStatement(node); + case SyntaxKind.SwitchStatement: + return visitSwitchStatement(node); + case SyntaxKind.LabeledStatement: + return visitLabeledStatement(node); + default: + return visitJavaScriptInGeneratorFunctionBody(node); + } + } + + /** + * Visits a node that is contained within a generator function. + * + * @param node The node to visit. + */ + function visitJavaScriptInGeneratorFunctionBody(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunctionDeclaration(node); + case SyntaxKind.FunctionExpression: + return visitFunctionExpression(node); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return visitAccessorDeclaration(node); + case SyntaxKind.VariableStatement: + return visitVariableStatement(node); + case SyntaxKind.ForStatement: + return visitForStatement(node); + case SyntaxKind.ForInStatement: + return visitForInStatement(node); + case SyntaxKind.BreakStatement: + return visitBreakStatement(node); + case SyntaxKind.ContinueStatement: + return visitContinueStatement(node); + case SyntaxKind.ReturnStatement: + return visitReturnStatement(node); + default: + if (node.transformFlags & TransformFlags.ContainsYield) { + return visitJavaScriptContainingYield(node); + } + else if (node.transformFlags & (TransformFlags.ContainsGenerator | TransformFlags.ContainsHoistedDeclarationOrCompletion)) { + return visitEachChild(node, visitor, context); + } + else { + return node; + } + } + } + + /** + * Visits a node that contains a YieldExpression. + * + * @param node The node to visit. + */ + function visitJavaScriptContainingYield(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.BinaryExpression: + return visitBinaryExpression(node); + case SyntaxKind.ConditionalExpression: + return visitConditionalExpression(node); + case SyntaxKind.YieldExpression: + return visitYieldExpression(node); + case SyntaxKind.ArrayLiteralExpression: + return visitArrayLiteralExpression(node); + case SyntaxKind.ObjectLiteralExpression: + return visitObjectLiteralExpression(node); + case SyntaxKind.ElementAccessExpression: + return visitElementAccessExpression(node); + case SyntaxKind.CallExpression: + return visitCallExpression(node); + case SyntaxKind.NewExpression: + return visitNewExpression(node); + default: + return visitEachChild(node, visitor, context); + } + } + + /** + * Visits a generator function. + * + * @param node The node to visit. + */ + function visitGenerator(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunctionDeclaration(node); + + case SyntaxKind.FunctionExpression: + return visitFunctionExpression(node); + + default: + Debug.failBadSyntaxKind(node); + return visitEachChild(node, visitor, context); + } + } + + /** + * Visits a function declaration. + * + * This will be called when one of the following conditions are met: + * - The function declaration is a generator function. + * - The function declaration is contained within the body of a generator function. + * + * @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) { + node = setOriginalNode( + createFunctionDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + node.name, + /*typeParameters*/ undefined, + node.parameters, + /*type*/ undefined, + transformGeneratorFunctionBody(node.body), + /*location*/ node + ), + node + ); + } + else { + const savedInGeneratorFunctionBody = inGeneratorFunctionBody; + const savedInStatementContainingYield = inStatementContainingYield; + inGeneratorFunctionBody = false; + inStatementContainingYield = false; + node = visitEachChild(node, visitor, context); + inGeneratorFunctionBody = savedInGeneratorFunctionBody; + inStatementContainingYield = savedInStatementContainingYield; + } + + if (inGeneratorFunctionBody) { + // Function declarations in a generator function body are hoisted + // to the top of the lexical scope and elided from the current statement. + hoistFunctionDeclaration(node); + return undefined; + } + else { + return node; + } + } + + /** + * Visits a function expression. + * + * This will be called when one of the following conditions are met: + * - The function expression is a generator function. + * - The function expression is contained within the body of a generator function. + * + * @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) { + node = setOriginalNode( + createFunctionExpression( + /*asteriskToken*/ undefined, + node.name, + /*typeParameters*/ undefined, + node.parameters, + /*type*/ undefined, + transformGeneratorFunctionBody(node.body), + /*location*/ node + ), + node + ); + } + else { + const savedInGeneratorFunctionBody = inGeneratorFunctionBody; + const savedInStatementContainingYield = inStatementContainingYield; + inGeneratorFunctionBody = false; + inStatementContainingYield = false; + node = visitEachChild(node, visitor, context); + inGeneratorFunctionBody = savedInGeneratorFunctionBody; + inStatementContainingYield = savedInStatementContainingYield; + } + + return node; + } + + /** + * Visits a get or set accessor declaration. + * + * This will be called when one of the following conditions are met: + * - The accessor is contained within the body of a generator function. + * + * @param node The node to visit. + */ + function visitAccessorDeclaration(node: GetAccessorDeclaration) { + const savedInGeneratorFunctionBody = inGeneratorFunctionBody; + const savedInStatementContainingYield = inStatementContainingYield; + inGeneratorFunctionBody = false; + inStatementContainingYield = false; + node = visitEachChild(node, visitor, context); + inGeneratorFunctionBody = savedInGeneratorFunctionBody; + inStatementContainingYield = savedInStatementContainingYield; + return node; + } + + /** + * Transforms the body of a generator function declaration. + * + * @param node The function body to transform. + */ + function transformGeneratorFunctionBody(body: Block) { + // Save existing generator state + const statements: Statement[] = []; + const savedInGeneratorFunctionBody = inGeneratorFunctionBody; + const savedInStatementContainingYield = inStatementContainingYield; + const savedBlocks = blocks; + const savedBlockOffsets = blockOffsets; + const savedBlockActions = blockActions; + const savedLabelOffsets = labelOffsets; + const savedLabelExpressions = labelExpressions; + const savedNextLabelId = nextLabelId; + const savedOperations = operations; + const savedOperationArguments = operationArguments; + const savedOperationLocations = operationLocations; + const savedState = state; + + // Initialize generator state + inGeneratorFunctionBody = true; + inStatementContainingYield = false; + blocks = undefined; + blockOffsets = undefined; + blockActions = undefined; + labelOffsets = undefined; + labelExpressions = undefined; + nextLabelId = 1; + operations = undefined; + operationArguments = undefined; + operationLocations = undefined; + state = createTempVariable(/*recordTempVariable*/ undefined); + + const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); + + // Build the generator + startLexicalEnvironment(); + + transformAndEmitStatements(body.statements, statementOffset); + + const buildResult = build(); + addNodes(statements, endLexicalEnvironment()); + addNode(statements, createReturn(buildResult)); + + // Restore previous generator state + inGeneratorFunctionBody = savedInGeneratorFunctionBody; + inStatementContainingYield = savedInStatementContainingYield; + blocks = savedBlocks; + blockOffsets = savedBlockOffsets; + blockActions = savedBlockActions; + labelOffsets = savedLabelOffsets; + labelExpressions = savedLabelExpressions; + nextLabelId = savedNextLabelId; + operations = savedOperations; + operationArguments = savedOperationArguments; + operationLocations = savedOperationLocations; + state = savedState; + + return createBlock(statements, /*location*/ body, body.multiLine); + } + + /** + * Visits a variable statement. + * + * This will be called when one of the following conditions are met: + * - The variable statement is contained within the body of a generator function. + * + * @param node The node to visit. + */ + function visitVariableStatement(node: VariableStatement): Statement { + if (node.transformFlags & TransformFlags.ContainsYield) { + transformAndEmitVariableDeclarationList(node.declarationList); + return undefined; + } + else { + for (const variable of node.declarationList.declarations) { + hoistVariableDeclaration(variable.name); + } + + const variables = getInitializedVariables(node.declarationList); + if (variables.length === 0) { + return undefined; + } + + return createStatement( + inlineExpressions( + map(variables, transformInitializedVariable) + ) + ); + } + } + + /** + * Visits a binary expression. + * + * This will be called when one of the following conditions are met: + * - The node contains a YieldExpression. + * + * @param node The node to visit. + */ + function visitBinaryExpression(node: BinaryExpression): Expression { + switch (getExpressionAssociativity(node)) { + case Associativity.Left: + return visitLeftAssociativeBinaryExpression(node); + case Associativity.Right: + return visitRightAssociativeBinaryExpression(node); + default: + Debug.fail("Unknown associativity."); + } + } + + function isCompoundAssignment(kind: SyntaxKind) { + return kind >= SyntaxKind.FirstCompoundAssignment + && kind <= SyntaxKind.LastCompoundAssignment; + } + + function getOperatorForCompoundAssignment(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken; + case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken; + case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken; + case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken; + case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken; + case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken; + case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken; + case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken; + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken; + case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken; + case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken; + } + } + + /** + * Visits a right-associative binary expression containing `yield`. + * + * @param node The node to visit. + */ + function visitRightAssociativeBinaryExpression(node: BinaryExpression) { + const { left, right } = node; + if (containsYield(right)) { + let target: Expression; + switch (left.kind) { + case SyntaxKind.PropertyAccessExpression: + // [source] + // a.b = yield; + // + // [intermediate] + // .local _a + // _a = a; + // .yield resumeLabel + // .mark resumeLabel + // _a.b = %sent%; + + target = updatePropertyAccess( + left, + cacheExpression(visitNode((left).expression, visitor, isLeftHandSideExpression)), + (left).name + ); + break; + + case SyntaxKind.ElementAccessExpression: + // [source] + // a[b] = yield; + // + // [intermediate] + // .local _a, _b + // _a = a; + // _b = b; + // .yield resumeLabel + // .mark resumeLabel + // _a[_b] = %sent%; + + target = updateElementAccess(left, + cacheExpression(visitNode((left).expression, visitor, isLeftHandSideExpression)), + cacheExpression(visitNode((left).argumentExpression, visitor, isExpression)) + ); + break; + + default: + target = visitNode(left, visitor, isExpression); + break; + } + + const operator = node.operatorToken.kind; + if (isCompoundAssignment(operator)) { + return createBinary( + target, + SyntaxKind.EqualsToken, + createBinary( + cacheExpression(target), + getOperatorForCompoundAssignment(operator), + visitNode(right, visitor, isExpression), + node + ), + node + ); + } + else { + return updateBinary(node, target, visitNode(right, visitor, isExpression)); + } + } + + return visitEachChild(node, visitor, context); + } + + function visitLeftAssociativeBinaryExpression(node: BinaryExpression) { + if (containsYield(node.right)) { + if (isLogicalOperator(node.operatorToken.kind)) { + return visitLogicalBinaryExpression(node); + } + else if (node.operatorToken.kind === SyntaxKind.CommaToken) { + return visitCommaExpression(node); + } + + // [source] + // a() + (yield) + c() + // + // [intermediate] + // .local _a + // _a = a(); + // .yield resumeLabel + // _a + %sent% + c() + + const clone = getMutableClone(node); + clone.left = cacheExpression(visitNode(node.left, visitor, isExpression)); + clone.right = visitNode(node.right, visitor, isExpression); + return clone; + } + + return visitEachChild(node, visitor, context); + } + + /** + * Visits a logical binary expression containing `yield`. + * + * @param node A node to visit. + */ + function visitLogicalBinaryExpression(node: BinaryExpression) { + // Logical binary expressions (`&&` and `||`) are shortcutting expressions and need + // to be transformed as such: + // + // [source] + // x = a() && yield; + // + // [intermediate] + // .local _a + // _a = a(); + // .brfalse resultLabel, (_a) + // .yield resumeLabel + // .mark resumeLabel + // _a = %sent%; + // .mark resultLabel + // x = _a; + // + // [source] + // x = a() || yield; + // + // [intermediate] + // .local _a + // _a = a(); + // .brtrue resultLabel, (_a) + // .yield resumeLabel + // .mark resumeLabel + // _a = %sent%; + // .mark resultLabel + // x = _a; + + const resultLabel = defineLabel(); + const resultLocal = declareLocal(); + + emitAssignment(resultLocal, visitNode(node.left, visitor, isExpression), /*location*/ node.left); + if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + // Logical `&&` shortcuts when the left-hand operand is falsey. + emitBreakWhenFalse(resultLabel, resultLocal, /*location*/ node.left); + } + else { + // Logical `||` shortcuts when the left-hand operand is truthy. + emitBreakWhenTrue(resultLabel, resultLocal, /*location*/ node.left); + } + + emitAssignment(resultLocal, visitNode(node.right, visitor, isExpression), /*location*/ node.right); + markLabel(resultLabel); + return resultLocal; + } + + /** + * Visits a comma expression containing `yield`. + * + * @param node The node to visit. + */ + function visitCommaExpression(node: BinaryExpression) { + // [source] + // x = a(), yield, b(); + // + // [intermediate] + // a(); + // .yield resumeLabel + // .mark resumeLabel + // x = %sent%, b(); + + let pendingExpressions: Expression[] = []; + visit(node.left); + visit(node.right); + return inlineExpressions(pendingExpressions); + + function visit(node: Expression) { + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + visit(node.left); + visit(node.right); + } + else { + if (containsYield(node) && pendingExpressions.length > 0) { + emitWorker(OpCode.Statement, [createStatement(inlineExpressions(pendingExpressions))]); + pendingExpressions = []; + } + + pendingExpressions.push(visitNode(node, visitor, isExpression)); + } + } + } + + /** + * Visits a conditional expression containing `yield`. + * + * @param node The node to visit. + */ + function visitConditionalExpression(node: ConditionalExpression): Expression { + // [source] + // x = a() ? yield : b(); + // + // [intermediate] + // .local _a + // .brfalse whenFalseLabel, (a()) + // .yield resumeLabel + // .mark resumeLabel + // _a = %sent%; + // .br resultLabel + // .mark whenFalseLabel + // _a = b(); + // .mark resultLabel + // x = _a; + + // We only need to perform a specific transformation if a `yield` expression exists + // in either the `whenTrue` or `whenFalse` branches. + // A `yield` in the condition will be handled by the normal visitor. + if (containsYield(node.whenTrue) || containsYield(node.whenFalse)) { + const whenFalseLabel = defineLabel(); + const resultLabel = defineLabel(); + const resultLocal = declareLocal(); + emitBreakWhenFalse(whenFalseLabel, visitNode(node.condition, visitor, isExpression), /*location*/ node.condition); + emitAssignment(resultLocal, visitNode(node.whenTrue, visitor, isExpression), /*location*/ node.whenTrue); + emitBreak(resultLabel); + markLabel(whenFalseLabel); + emitAssignment(resultLocal, visitNode(node.whenFalse, visitor, isExpression), /*location*/ node.whenFalse); + markLabel(resultLabel); + return resultLocal; + } + + return visitEachChild(node, visitor, context); + } + + /** + * Visits a `yield` expression. + * + * @param node The node to visit. + */ + function visitYieldExpression(node: YieldExpression) { + // [source] + // x = yield a(); + // + // [intermediate] + // .yield resumeLabel, (a()) + // .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); + markLabel(resumeLabel); + return createGeneratorResume(); + } + + /** + * Visits an ArrayLiteralExpression that contains a YieldExpression. + * + * @param node The node to visit. + */ + function visitArrayLiteralExpression(node: ArrayLiteralExpression) { + return visitElements(node.elements, node.multiLine); + } + + /** + * Visits an array of expressions containing one or more YieldExpression nodes + * and returns an expression for the resulting value. + * + * @param elements The elements to visit. + * @param multiLine Whether array literals created should be emitted on multiple lines. + */ + function visitElements(elements: NodeArray, multiLine: boolean) { + // [source] + // ar = [1, yield, 2]; + // + // [intermediate] + // .local _a + // _a = [1]; + // .yield resumeLabel + // .mark resumeLabel + // ar = _a.concat([%sent%, 2]); + + const numInitialElements = countInitialNodesWithoutYield(elements); + const temp = declareLocal(); + let hasAssignedTemp = false; + if (numInitialElements > 0) { + emitAssignment(temp, + createArrayLiteral( + visitNodes(elements, visitor, isExpression, 0, numInitialElements) + ) + ); + hasAssignedTemp = true; + } + + const expressions = reduceLeft(elements, reduceElement, [], numInitialElements); + return hasAssignedTemp + ? createArrayConcat(temp, [createArrayLiteral(expressions)]) + : createArrayLiteral(expressions); + + function reduceElement(expressions: Expression[], element: Expression) { + if (containsYield(element) && expressions.length > 0) { + emitAssignment( + temp, + hasAssignedTemp + ? createArrayConcat( + temp, + [createArrayLiteral(expressions)] + ) + : createArrayLiteral(expressions) + ); + hasAssignedTemp = true; + expressions = []; + } + + expressions.push(visitNode(element, visitor, isExpression)); + return expressions; + } + } + + function visitObjectLiteralExpression(node: ObjectLiteralExpression) { + // [source] + // o = { + // a: 1, + // b: yield, + // c: 2 + // }; + // + // [intermediate] + // .local _a + // _a = { + // a: 1 + // }; + // .yield resumeLabel + // .mark resumeLabel + // o = (_a.b = %sent%, + // _a.c = 2, + // _a); + + const properties = node.properties; + const multiLine = node.multiLine; + const numInitialProperties = countInitialNodesWithoutYield(properties); + + const temp = declareLocal(); + emitAssignment(temp, + createObjectLiteral( + visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialProperties), + /*location*/ undefined, + multiLine + ) + ); + + const expressions = reduceLeft(properties, reduceProperty, [], numInitialProperties); + addNode(expressions, getMutableClone(temp), multiLine); + return inlineExpressions(expressions); + + function reduceProperty(expressions: Expression[], property: ObjectLiteralElement) { + if (containsYield(property) && expressions.length > 0) { + emitStatement(createStatement(inlineExpressions(expressions))); + expressions = []; + } + + const expression = createExpressionForObjectLiteralElement(node, property, temp); + addNode(expressions, visitNode(expression, visitor, isExpression), multiLine); + return expressions; + } + } + + /** + * Visits an ElementAccessExpression that contains a YieldExpression. + * + * @param node The node to visit. + */ + function visitElementAccessExpression(node: ElementAccessExpression) { + if (containsYield(node.argumentExpression)) { + // [source] + // a = x[yield]; + // + // [intermediate] + // .local _a + // _a = x; + // .yield resumeLabel + // .mark resumeLabel + // a = _a[%sent%] + + const clone = getMutableClone(node); + clone.expression = cacheExpression(visitNode(node.expression, visitor, isLeftHandSideExpression)); + clone.argumentExpression = visitNode(node.argumentExpression, visitor, isExpression); + return clone; + } + + return visitEachChild(node, visitor, context); + } + + function visitCallExpression(node: CallExpression) { + if (forEach(node.arguments, containsYield)) { + // [source] + // a.b(1, yield, 2); + // + // [intermediate] + // .local _a, _b, _c + // _b = (_a = a).b; + // _c = [1]; + // .yield resumeLabel + // .mark resumeLabel + // _b.apply(_a, _c.concat([%sent%, 2])); + + const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*cacheIdentifiers*/ true); + return setOriginalNode( + createFunctionApply( + cacheExpression(visitNode(target, visitor, isLeftHandSideExpression)), + thisArg, + visitElements(node.arguments, /*multiLine*/ false), + /*location*/ node + ), + node + ); + } + + return visitEachChild(node, visitor, context); + } + + function visitNewExpression(node: NewExpression) { + if (forEach(node.arguments, containsYield)) { + // [source] + // new a.b(1, yield, 2); + // + // [intermediate] + // .local _a, _b, _c + // _b = (_a = a.b).bind; + // _c = [1]; + // .yield resumeLabel + // .mark resumeLabel + // new (_b.apply(_a, _c.concat([%sent%, 2]))); + + const { target, thisArg } = createCallBinding(createPropertyAccess(node.expression, "bind"), hoistVariableDeclaration); + return setOriginalNode( + createNew( + createFunctionApply( + cacheExpression(visitNode(target, visitor, isExpression)), + thisArg, + visitElements(node.arguments, /*multiLine*/ false) + ), + /*typeArguments*/ undefined, + [], + /*location*/ node + ), + node + ); + } + return visitEachChild(node, visitor, context); + } + + function transformAndEmitStatements(statements: Statement[], start = 0) { + const numStatements = statements.length; + for (let i = start; i < numStatements; i++) { + transformAndEmitStatement(statements[i]); + } + } + + function transformAndEmitEmbeddedStatement(node: Statement) { + if (isBlock(node)) { + transformAndEmitStatements(node.statements); + } + else { + transformAndEmitStatement(node); + } + } + + function transformAndEmitStatement(node: Statement): void { + const savedInStatementContainingYield = inStatementContainingYield; + if (!inStatementContainingYield) { + inStatementContainingYield = containsYield(node); + } + + transformAndEmitStatementWorker(node); + inStatementContainingYield = savedInStatementContainingYield; + } + + function transformAndEmitStatementWorker(node: Statement): void { + switch (node.kind) { + case SyntaxKind.Block: + return transformAndEmitBlock(node); + case SyntaxKind.ExpressionStatement: + return transformAndEmitExpressionStatement(node); + case SyntaxKind.IfStatement: + return transformAndEmitIfStatement(node); + case SyntaxKind.DoStatement: + return transformAndEmitDoStatement(node); + case SyntaxKind.WhileStatement: + return transformAndEmitWhileStatement(node); + case SyntaxKind.ForStatement: + return transformAndEmitForStatement(node); + case SyntaxKind.ForInStatement: + return transformAndEmitForInStatement(node); + case SyntaxKind.ContinueStatement: + return transformAndEmitContinueStatement(node); + case SyntaxKind.BreakStatement: + return transformAndEmitBreakStatement(node); + case SyntaxKind.ReturnStatement: + return transformAndEmitReturnStatement(node); + case SyntaxKind.WithStatement: + return transformAndEmitWithStatement(node); + case SyntaxKind.SwitchStatement: + return transformAndEmitSwitchStatement(node); + case SyntaxKind.LabeledStatement: + return transformAndEmitLabeledStatement(node); + case SyntaxKind.ThrowStatement: + return transformAndEmitThrowStatement(node); + case SyntaxKind.TryStatement: + return transformAndEmitTryStatement(node); + default: + return emitStatement(visitNode(node, visitor, isStatement, /*optional*/ true)); + } + } + + function transformAndEmitBlock(node: Block): void { + if (containsYield(node)) { + transformAndEmitStatements(node.statements); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function transformAndEmitExpressionStatement(node: ExpressionStatement) { + emitStatement(visitNode(node, visitor, isStatement)); + } + + function transformAndEmitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList { + for (const variable of node.declarations) { + hoistVariableDeclaration(variable.name); + } + + const variables = getInitializedVariables(node); + const numVariables = variables.length; + let variablesWritten = 0; + let pendingExpressions: Expression[] = []; + while (variablesWritten < numVariables) { + for (let i = variablesWritten; i < numVariables; i++) { + const variable = variables[i]; + if (containsYield(variable.initializer) && pendingExpressions.length > 0) { + break; + } + + pendingExpressions.push(transformInitializedVariable(variable)); + } + + if (pendingExpressions.length) { + emitStatement(createStatement(inlineExpressions(pendingExpressions))); + variablesWritten += pendingExpressions.length; + pendingExpressions = []; + } + } + + return undefined; + } + + function transformInitializedVariable(node: VariableDeclaration) { + return createAssignment( + getSynthesizedClone(node.name), + visitNode(node.initializer, visitor, isExpression) + ); + } + + function transformAndEmitIfStatement(node: IfStatement) { + if (containsYield(node)) { + // [source] + // if (x) + // /*thenStatement*/ + // else + // /*elseStatement*/ + // + // [intermediate] + // .brfalse elseLabel, (x) + // /*thenStatement*/ + // .br endLabel + // .mark elseLabel + // /*elseStatement*/ + // .mark endLabel + + if (containsYield(node.thenStatement) || containsYield(node.elseStatement)) { + const endLabel = defineLabel(); + const elseLabel = node.elseStatement ? defineLabel() : undefined; + emitBreakWhenFalse(node.elseStatement ? elseLabel : endLabel, visitNode(node.expression, visitor, isExpression)); + transformAndEmitEmbeddedStatement(node.thenStatement); + if (node.elseStatement) { + emitBreak(endLabel); + markLabel(elseLabel); + transformAndEmitEmbeddedStatement(node.elseStatement); + } + markLabel(endLabel); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function transformAndEmitDoStatement(node: DoStatement) { + if (containsYield(node)) { + // [source] + // do { + // /*body*/ + // } + // while (i < 10); + // + // [intermediate] + // .loop conditionLabel, endLabel + // .mark loopLabel + // /*body*/ + // .mark conditionLabel + // .brtrue loopLabel, (i < 10) + // .endloop + // .mark endLabel + + const conditionLabel = defineLabel(); + const loopLabel = defineLabel(); + beginLoopBlock(/*continueLabel*/ conditionLabel); + markLabel(loopLabel); + transformAndEmitEmbeddedStatement(node.statement); + markLabel(conditionLabel); + emitBreakWhenTrue(loopLabel, visitNode(node.expression, visitor, isExpression)); + endLoopBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitDoStatement(node: DoStatement) { + if (inStatementContainingYield) { + beginScriptLoopBlock(); + node = visitEachChild(node, visitor, context); + endLoopBlock(); + return node; + } + else { + return visitEachChild(node, visitor, context); + } + } + + function transformAndEmitWhileStatement(node: WhileStatement) { + if (containsYield(node)) { + // [source] + // while (i < 10) { + // /*body*/ + // } + // + // [intermediate] + // .loop loopLabel, endLabel + // .mark loopLabel + // .brfalse endLabel, (i < 10) + // /*body*/ + // .br loopLabel + // .endloop + // .mark endLabel + + const loopLabel = defineLabel(); + const endLabel = beginLoopBlock(loopLabel); + markLabel(loopLabel); + emitBreakWhenFalse(endLabel, visitNode(node.expression, visitor, isExpression)); + transformAndEmitEmbeddedStatement(node.statement); + emitBreak(loopLabel); + endLoopBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitWhileStatement(node: WhileStatement) { + if (inStatementContainingYield) { + beginScriptLoopBlock(); + node = visitEachChild(node, visitor, context); + endLoopBlock(); + return node; + } + else { + return visitEachChild(node, visitor, context); + } + } + + function transformAndEmitForStatement(node: ForStatement) { + if (containsYield(node)) { + // [source] + // for (var i = 0; i < 10; i++) { + // /*body*/ + // } + // + // [intermediate] + // .local i + // i = 0; + // .loop incrementLabel, endLoopLabel + // .mark conditionLabel + // .brfalse endLoopLabel, (i < 10) + // /*body*/ + // .mark incrementLabel + // i++; + // .br conditionLabel + // .endloop + // .mark endLoopLabel + + const conditionLabel = defineLabel(); + const incrementLabel = defineLabel(); + const endLabel = beginLoopBlock(incrementLabel); + if (node.initializer) { + const initializer = node.initializer; + if (isVariableDeclarationList(initializer)) { + transformAndEmitVariableDeclarationList(initializer); + } + else { + emitStatement( + createStatement( + visitNode(initializer, visitor, isExpression), + /*location*/ initializer + ) + ); + } + } + + markLabel(conditionLabel); + if (node.condition) { + emitBreakWhenFalse(endLabel, visitNode(node.condition, visitor, isExpression)); + } + + transformAndEmitEmbeddedStatement(node.statement); + + markLabel(incrementLabel); + if (node.incrementor) { + emitStatement( + createStatement( + visitNode(node.incrementor, visitor, isExpression), + /*location*/ node.incrementor + ) + ); + } + emitBreak(conditionLabel); + endLoopBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitForStatement(node: ForStatement) { + if (inStatementContainingYield) { + beginScriptLoopBlock(); + } + + const initializer = node.initializer; + if (isVariableDeclarationList(initializer)) { + for (const variable of initializer.declarations) { + hoistVariableDeclaration(variable.name); + } + + const variables = getInitializedVariables(initializer); + node = updateFor(node, + variables.length > 0 + ? inlineExpressions(map(variables, transformInitializedVariable)) + : undefined, + visitNode(node.condition, visitor, isExpression, /*optional*/ true), + visitNode(node.incrementor, visitor, isExpression, /*optional*/ true), + visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock) + ); + } + else { + node = visitEachChild(node, visitor, context); + } + + if (inStatementContainingYield) { + endLoopBlock(); + } + + return node; + } + + function transformAndEmitForInStatement(node: ForInStatement) { + // TODO(rbuckton): Source map locations + if (containsYield(node)) { + // [source] + // for (var p in o) { + // /*body*/ + // } + // + // [intermediate] + // .local _a, _b, _i + // _a = []; + // for (_b in o) _a.push(_b); + // _i = 0; + // .loop incrementLabel, endLoopLabel + // .mark conditionLabel + // .brfalse endLoopLabel, (_i < _a.length) + // p = _a[_i]; + // /*body*/ + // .mark incrementLabel + // _b++; + // .br conditionLabel + // .endloop + // .mark endLoopLabel + + const keysArray = declareLocal(); // _a + const key = declareLocal(); // _b + const keysIndex = createLoopVariable(); // _i + const initializer = node.initializer; + hoistVariableDeclaration(keysIndex); + emitAssignment(keysArray, createArrayLiteral()); + + emitStatement( + createForIn( + key, + visitNode(node.expression, visitor, isExpression), + createStatement( + createCall( + createPropertyAccess(keysArray, "push"), + /*typeArguments*/ undefined, + [key] + ) + ) + ) + ); + + emitAssignment(keysIndex, createLiteral(0)); + + const conditionLabel = defineLabel(); + const incrementLabel = defineLabel(); + const endLabel = beginLoopBlock(incrementLabel); + + markLabel(conditionLabel); + emitBreakWhenFalse(endLabel, createLessThan(keysIndex, createPropertyAccess(keysArray, "length"))); + + let variable: Expression; + if (isVariableDeclarationList(initializer)) { + for (const variable of initializer.declarations) { + hoistVariableDeclaration(variable.name); + } + + variable = getSynthesizedClone(initializer.declarations[0].name); + } + else { + variable = visitNode(initializer, visitor, isExpression); + Debug.assert(isLeftHandSideExpression(variable)); + } + + emitAssignment(variable, createElementAccess(keysArray, keysIndex)); + transformAndEmitEmbeddedStatement(node.statement); + + markLabel(incrementLabel); + emitStatement(createStatement(createPostfixIncrement(keysIndex))); + + emitBreak(conditionLabel); + endLoopBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitForInStatement(node: ForInStatement) { + // [source] + // for (var x in a) { + // /*body*/ + // } + // + // [intermediate] + // .local x + // .loop + // for (x in a) { + // /*body*/ + // } + // .endloop + + if (inStatementContainingYield) { + beginScriptLoopBlock(); + } + + const initializer = node.initializer; + if (isVariableDeclarationList(initializer)) { + for (const variable of initializer.declarations) { + hoistVariableDeclaration(variable.name); + } + + node = updateForIn(node, + initializer.declarations[0].name, + visitNode(node.expression, visitor, isExpression), + visitNode(node.statement, visitor, isStatement, /*optional*/ false, liftToBlock) + ); + } + else { + node = visitEachChild(node, visitor, context); + } + + if (inStatementContainingYield) { + endLoopBlock(); + } + + return node; + } + + function transformAndEmitContinueStatement(node: ContinueStatement): void { + const label = findContinueTarget(node.label ? node.label.text : undefined); + Debug.assert(label > 0, "Expected continue statment to point to a valid Label."); + emitBreak(label, /*location*/ node); + } + + function visitContinueStatement(node: ContinueStatement): Statement { + if (inStatementContainingYield) { + const label = findContinueTarget(node.label && node.label.text); + if (label > 0) { + return createInlineBreak(label, /*location*/ node); + } + } + + return visitEachChild(node, visitor, context); + } + + function transformAndEmitBreakStatement(node: BreakStatement): void { + const label = findBreakTarget(node.label ? node.label.text : undefined); + Debug.assert(label > 0, "Expected break statment to point to a valid Label."); + emitBreak(label, /*location*/ node); + } + + function visitBreakStatement(node: BreakStatement): Statement { + if (inStatementContainingYield) { + const label = findBreakTarget(node.label && node.label.text); + if (label > 0) { + return createInlineBreak(label, /*location*/ node); + } + } + + return visitEachChild(node, visitor, context); + } + + function transformAndEmitReturnStatement(node: ReturnStatement): void { + emitReturn( + visitNode(node.expression, visitor, isExpression, /*optional*/ true), + /*location*/ node + ); + } + + function visitReturnStatement(node: ReturnStatement) { + return createInlineReturn( + visitNode(node.expression, visitor, isExpression, /*optional*/ true), + /*location*/ node + ); + } + + function transformAndEmitWithStatement(node: WithStatement) { + if (containsYield(node)) { + // [source] + // with (x) { + // /*body*/ + // } + // + // [intermediate] + // .with (x) + // /*body*/ + // .endwith + beginWithBlock(cacheExpression(visitNode(node.expression, visitor, isExpression))); + transformAndEmitEmbeddedStatement(node.statement); + endWithBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function transformAndEmitSwitchStatement(node: SwitchStatement) { + if (containsYield(node.caseBlock)) { + // [source] + // switch (x) { + // case a: + // /*caseStatements*/ + // case b: + // /*caseStatements*/ + // default: + // /*defaultStatements*/ + // } + // + // [intermediate] + // .local _a + // .switch endLabel + // _a = x; + // switch (_a) { + // case a: + // .br clauseLabels[0] + // } + // switch (_a) { + // case b: + // .br clauseLabels[1] + // } + // .br clauseLabels[2] + // .mark clauseLabels[0] + // /*caseStatements*/ + // .mark clauseLabels[1] + // /*caseStatements*/ + // .mark clauseLabels[2] + // /*caseStatements*/ + // .endswitch + // .mark endLabel + + const caseBlock = node.caseBlock; + const numClauses = caseBlock.clauses.length; + const endLabel = beginSwitchBlock(); + + const expression = cacheExpression(visitNode(node.expression, visitor, isExpression)); + + // Create labels for each clause and find the index of the first default clause. + const clauseLabels: Label[] = []; + let defaultClauseIndex = -1; + for (let i = 0; i < numClauses; i++) { + const clause = caseBlock.clauses[i]; + clauseLabels.push(defineLabel()); + if (clause.kind === SyntaxKind.DefaultClause && defaultClauseIndex === -1) { + defaultClauseIndex = i; + } + } + + // Emit switch statements for each run of case clauses either from the first case + // clause or the next case clause with a `yield` in its expression, up to the next + // case clause with a `yield` in its expression. + let clausesWritten = 0; + let pendingClauses: CaseClause[] = []; + while (clausesWritten < numClauses) { + let defaultClausesSkipped = 0; + for (let i = clausesWritten; i < numClauses; i++) { + const clause = caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + const caseClause = clause; + if (containsYield(caseClause.expression) && pendingClauses.length > 0) { + break; + } + + pendingClauses.push( + createCaseClause( + visitNode(caseClause.expression, visitor, isExpression), + [ + createInlineBreak(clauseLabels[i], /*location*/ caseClause.expression) + ] + ) + ); + } + else { + defaultClausesSkipped++; + } + } + + if (pendingClauses.length) { + emitStatement(createSwitch(expression, createCaseBlock(pendingClauses))); + clausesWritten += pendingClauses.length; + pendingClauses = []; + } + if (defaultClausesSkipped > 0) { + clausesWritten += defaultClausesSkipped; + defaultClausesSkipped = 0; + } + } + + if (defaultClauseIndex >= 0) { + emitBreak(clauseLabels[defaultClauseIndex]); + } + else { + emitBreak(endLabel); + } + + for (let i = 0; i < numClauses; i++) { + markLabel(clauseLabels[i]); + transformAndEmitStatements(caseBlock.clauses[i].statements); + } + + endSwitchBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitSwitchStatement(node: SwitchStatement) { + if (inStatementContainingYield) { + beginScriptSwitchBlock(); + } + + node = visitEachChild(node, visitor, context); + + if (inStatementContainingYield) { + endSwitchBlock(); + } + + return node; + } + + function transformAndEmitLabeledStatement(node: LabeledStatement) { + if (containsYield(node)) { + // [source] + // x: { + // /*body*/ + // } + // + // [intermediate] + // .labeled "x", endLabel + // /*body*/ + // .endlabeled + // .mark endLabel + beginLabeledBlock(node.label.text); + transformAndEmitEmbeddedStatement(node.statement); + endLabeledBlock(); + } + else { + emitStatement(visitNode(node, visitor, isStatement)); + } + } + + function visitLabeledStatement(node: LabeledStatement) { + if (inStatementContainingYield) { + beginScriptLabeledBlock(node.label.text); + } + + node = visitEachChild(node, visitor, context); + + if (inStatementContainingYield) { + endLabeledBlock(); + } + + return node; + } + + function transformAndEmitThrowStatement(node: ThrowStatement): void { + emitThrow( + visitNode(node.expression, visitor, isExpression), + /*location*/ node + ); + } + + function transformAndEmitTryStatement(node: TryStatement) { + if (containsYield(node)) { + // [source] + // try { + // /*tryBlock*/ + // } + // catch (e) { + // /*catchBlock*/ + // } + // finally { + // /*finallyBlock*/ + // } + // + // [intermediate] + // .local _a + // .try tryLabel, catchLabel, finallyLabel, endLabel + // .mark tryLabel + // .nop + // /*tryBlock*/ + // .br endLabel + // .catch + // .mark catchLabel + // _a = %error%; + // /*catchBlock*/ + // .br endLabel + // .finally + // .mark finallyLabel + // /*finallyBlock*/ + // .endfinally + // .endtry + // .mark endLabel + + beginExceptionBlock(); + transformAndEmitEmbeddedStatement(node.tryBlock); + if (node.catchClause) { + beginCatchBlock(node.catchClause.variableDeclaration); + transformAndEmitEmbeddedStatement(node.catchClause.block); + } + + if (node.finallyBlock) { + beginFinallyBlock(); + transformAndEmitEmbeddedStatement(node.finallyBlock); + } + + endExceptionBlock(); + } + else { + emitStatement(visitEachChild(node, visitor, context)); + } + } + + function containsYield(node: Node) { + return node && (node.transformFlags & TransformFlags.ContainsYield) !== 0; + } + + function countInitialNodesWithoutYield(nodes: NodeArray) { + const numNodes = nodes.length; + for (let i = 0; i < numNodes; i++) { + if (containsYield(nodes[i])) { + return i; + } + } + + return -1; + } + + function onSubstituteNode(node: Node, isExpression: boolean): Node { + node = previousOnSubstituteNode(node, isExpression); + if (isExpression) { + return substituteExpression( node); + } + return node; + } + + function substituteExpression(node: Expression): Expression { + if (isIdentifier(node)) { + return substituteExpressionIdentifier(node); + } + return node; + } + + function substituteExpressionIdentifier(node: Identifier) { + if (renamedCatchVariables && hasProperty(renamedCatchVariables, node.text)) { + const original = getOriginalNode(node); + if (isIdentifier(original) && original.parent) { + const declaration = resolver.getReferencedValueDeclaration(original); + if (declaration) { + const name = getProperty(renamedCatchVariableDeclarations, String(getOriginalNodeId(declaration))); + if (name) { + const clone = getMutableClone(name); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; + } + } + } + } + + return node; + } + + function cacheExpression(node: Expression): Identifier { + let temp: Identifier; + if (isGeneratedIdentifier(node)) { + return node; + } + + temp = createTempVariable(hoistVariableDeclaration); + emitAssignment(temp, node, /*location*/ node); + return temp; + } + + function declareLocal(name?: string): Identifier { + const temp = name + ? createUniqueName(name) + : createTempVariable(/*recordTempVariable*/ undefined); + hoistVariableDeclaration(temp); + return temp; + } + + /** + * Defines a label, uses as the target of a Break operation. + */ + function defineLabel(): Label { + if (!labelOffsets) { + labelOffsets = []; + } + + const label = nextLabelId; + nextLabelId++; + labelOffsets[label] = -1; + return label; + } + + /** + * Marks the current operation with the specified label. + */ + function markLabel(label: Label): void { + Debug.assert(labelOffsets !== undefined, "No labels were defined."); + labelOffsets[label] = operations ? operations.length : 0; + } + + /** + * Begins a block operation (With, Break/Continue, Try/Catch/Finally) + * + * @param block Information about the block. + */ + function beginBlock(block: CodeBlock): number { + if (!blocks) { + blocks = []; + blockActions = []; + blockOffsets = []; + blockStack = []; + } + + const index = blockActions.length; + blockActions[index] = BlockAction.Open; + blockOffsets[index] = operations ? operations.length : 0; + blocks[index] = block; + blockStack.push(block); + return index; + } + + /** + * Ends the current block operation. + */ + function endBlock(): CodeBlock { + const block = peekBlock(); + Debug.assert(block !== undefined, "beginBlock was never called."); + + const index = blockActions.length; + blockActions[index] = BlockAction.Close; + blockOffsets[index] = operations ? operations.length : 0; + blocks[index] = block; + blockStack.pop(); + return block; + } + + /** + * Gets the current open block. + */ + function peekBlock() { + return lastOrUndefined(blockStack); + } + + /** + * Gets the kind of the current open block. + */ + function peekBlockKind(): CodeBlockKind { + const block = peekBlock(); + return block && block.kind; + } + + /** + * Begins a code block for a generated `with` statement. + * + * @param expression An identifier representing expression for the `with` block. + */ + function beginWithBlock(expression: Identifier): void { + const startLabel = defineLabel(); + const endLabel = defineLabel(); + markLabel(startLabel); + beginBlock({ + kind: CodeBlockKind.With, + expression, + startLabel, + endLabel + }); + } + + /** + * Ends a code block for a generated `with` statement. + */ + function endWithBlock(): void { + Debug.assert(peekBlockKind() === CodeBlockKind.With); + const block = endBlock(); + markLabel(block.endLabel); + } + + function isWithBlock(block: CodeBlock): block is WithBlock { + return block.kind === CodeBlockKind.With; + } + + /** + * Begins a code block for a generated `try` statement. + */ + function beginExceptionBlock(): Label { + const startLabel = defineLabel(); + const endLabel = defineLabel(); + markLabel(startLabel); + beginBlock({ + kind: CodeBlockKind.Exception, + state: ExceptionBlockState.Try, + startLabel, + endLabel + }); + emitNop(); + return endLabel; + } + + /** + * Enters the `catch` clause of a generated `try` statement. + * + * @param variable The catch variable. + */ + function beginCatchBlock(variable: VariableDeclaration): void { + Debug.assert(peekBlockKind() === CodeBlockKind.Exception); + + const text = (variable.name).text; + const name = declareLocal(text); + + if (!renamedCatchVariables) { + renamedCatchVariables = {}; + renamedCatchVariableDeclarations = {}; + context.enableSubstitution(SyntaxKind.Identifier); + } + + renamedCatchVariables[text] = true; + renamedCatchVariableDeclarations[getOriginalNodeId(variable)] = name; + + const exception = peekBlock(); + Debug.assert(exception.state < ExceptionBlockState.Catch); + + const endLabel = exception.endLabel; + emitBreak(endLabel); + + const catchLabel = defineLabel(); + markLabel(catchLabel); + exception.state = ExceptionBlockState.Catch; + exception.catchVariable = name; + exception.catchLabel = catchLabel; + + emitAssignment(name, createCall(createPropertyAccess(state, "sent"), /*typeArguments*/ undefined, [])); + emitNop(); + } + + /** + * Enters the `finally` block of a generated `try` statement. + */ + function beginFinallyBlock(): void { + Debug.assert(peekBlockKind() === CodeBlockKind.Exception); + + const exception = peekBlock(); + Debug.assert(exception.state < ExceptionBlockState.Finally); + + const endLabel = exception.endLabel; + emitBreak(endLabel); + + const finallyLabel = defineLabel(); + markLabel(finallyLabel); + exception.state = ExceptionBlockState.Finally; + exception.finallyLabel = finallyLabel; + } + + /** + * Ends the code block for a generated `try` statement. + */ + function endExceptionBlock(): void { + Debug.assert(peekBlockKind() === CodeBlockKind.Exception); + const exception = endBlock(); + const state = exception.state; + if (state < ExceptionBlockState.Finally) { + emitBreak(exception.endLabel); + } + else { + emitEndfinally(); + } + + markLabel(exception.endLabel); + emitNop(); + exception.state = ExceptionBlockState.Done; + } + + function isExceptionBlock(block: CodeBlock): block is ExceptionBlock { + return block.kind === CodeBlockKind.Exception; + } + + /** + * Begins a code block that supports `break` or `continue` statements that are defined in + * the source tree and not from generated code. + * + * @param labelText Names from containing labeled statements. + */ + function beginScriptLoopBlock(): void { + beginBlock({ + kind: CodeBlockKind.Loop, + isScript: true, + breakLabel: -1, + continueLabel: -1 + }); + } + + /** + * Begins a code block that supports `break` or `continue` statements that are defined in + * generated code. Returns a label used to mark the operation to which to jump when a + * `break` statement targets this block. + * + * @param continueLabel A Label used to mark the operation to which to jump when a + * `continue` statement targets this block. + */ + function beginLoopBlock(continueLabel: Label): Label { + const breakLabel = defineLabel(); + beginBlock({ + kind: CodeBlockKind.Loop, + isScript: false, + breakLabel: breakLabel, + continueLabel: continueLabel + }); + return breakLabel; + } + + /** + * Ends a code block that supports `break` or `continue` statements that are defined in + * generated code or in the source tree. + */ + function endLoopBlock(): void { + Debug.assert(peekBlockKind() === CodeBlockKind.Loop); + const block = endBlock(); + const breakLabel = block.breakLabel; + if (!block.isScript) { + markLabel(breakLabel); + } + } + + /** + * Begins a code block that supports `break` statements that are defined in the source + * tree and not from generated code. + * + */ + function beginScriptSwitchBlock(): void { + beginBlock({ + kind: CodeBlockKind.Switch, + isScript: true, + breakLabel: -1 + }); + } + + /** + * Begins a code block that supports `break` statements that are defined in generated code. + * Returns a label used to mark the operation to which to jump when a `break` statement + * targets this block. + */ + function beginSwitchBlock(): Label { + const breakLabel = defineLabel(); + beginBlock({ + kind: CodeBlockKind.Switch, + isScript: false, + breakLabel: breakLabel + }); + return breakLabel; + } + + /** + * Ends a code block that supports `break` statements that are defined in generated code. + */ + function endSwitchBlock(): void { + Debug.assert(peekBlockKind() === CodeBlockKind.Switch); + const block = endBlock(); + const breakLabel = block.breakLabel; + if (!block.isScript) { + markLabel(breakLabel); + } + } + + function beginScriptLabeledBlock(labelText: string) { + beginBlock({ + kind: CodeBlockKind.Labeled, + isScript: true, + labelText, + breakLabel: -1 + }); + } + + function beginLabeledBlock(labelText: string) { + const breakLabel = defineLabel(); + beginBlock({ + kind: CodeBlockKind.Labeled, + isScript: false, + labelText, + breakLabel + }); + } + + function endLabeledBlock() { + Debug.assert(peekBlockKind() === CodeBlockKind.Labeled); + const block = endBlock(); + if (!block.isScript) { + markLabel(block.breakLabel); + } + } + + /** + * Indicates whether the provided block supports `break` statements. + * + * @param block A code block. + */ + function supportsUnlabeledBreak(block: CodeBlock): block is SwitchBlock | LoopBlock { + return block.kind === CodeBlockKind.Switch + || block.kind === CodeBlockKind.Loop; + } + + /** + * Indicates whether the provided block supports `break` statements with labels. + * + * @param block A code block. + */ + function supportsLabeledBreakOrContinue(block: CodeBlock): block is LabeledBlock { + return block.kind === CodeBlockKind.Labeled; + } + + /** + * Indicates whether the provided block supports `continue` statements. + * + * @param block A code block. + */ + function supportsUnlabeledContinue(block: CodeBlock): block is LoopBlock { + return block.kind === CodeBlockKind.Loop; + } + + function hasImmediateContainingLabeledBlock(labelText: string, start: number) { + for (let j = start; j >= 0; j--) { + const containingBlock = blockStack[j]; + if (supportsLabeledBreakOrContinue(containingBlock)) { + if (containingBlock.labelText === labelText) { + return true; + } + } + else { + break; + } + } + + return false; + } + + /** + * Finds the label that is the target for a `break` statement. + * + * @param labelText An optional name of a containing labeled statement. + */ + function findBreakTarget(labelText?: string): Label { + Debug.assert(blocks !== undefined); + if (labelText) { + for (let i = blockStack.length - 1; i >= 0; i--) { + const block = blockStack[i]; + if (supportsLabeledBreakOrContinue(block) && block.labelText === labelText) { + return block.breakLabel; + } + else if (supportsUnlabeledBreak(block) && hasImmediateContainingLabeledBlock(labelText, i - 1)) { + return block.breakLabel; + } + } + } + else { + for (let i = blockStack.length - 1; i >= 0; i--) { + const block = blockStack[i]; + if (supportsUnlabeledBreak(block)) { + return block.breakLabel; + } + } + } + + return 0; + } + + /** + * Finds the label that is the target for a `continue` statement. + * + * @param labelText An optional name of a containing labeled statement. + */ + function findContinueTarget(labelText?: string): Label { + Debug.assert(blocks !== undefined); + if (labelText) { + for (let i = blockStack.length - 1; i >= 0; i--) { + const block = blockStack[i]; + if (supportsUnlabeledContinue(block) && hasImmediateContainingLabeledBlock(labelText, i - 1)) { + return block.continueLabel; + } + } + } + else { + for (let i = blockStack.length - 1; i >= 0; i--) { + const block = blockStack[i]; + if (supportsUnlabeledContinue(block)) { + return block.continueLabel; + } + } + } + + return 0; + } + + /** + * Creates an expression that can be used to indicate the value for a label. + * + * @param label A label. + */ + function createLabel(label: Label): Expression { + if (label > 0) { + if (labelExpressions === undefined) { + labelExpressions = []; + } + + const expression = createSynthesizedNode(SyntaxKind.NumericLiteral); + if (labelExpressions[label] === undefined) { + labelExpressions[label] = [expression]; + } + else { + labelExpressions[label].push(expression); + } + + return expression; + } + + return createNode(SyntaxKind.OmittedExpression); + } + + /** + * Creates a numeric literal for the provided instruction. + */ + function createInstruction(instruction: Instruction): NumericLiteral { + const literal = createLiteral(instruction); + literal.trailingComment = instructionNames[instruction]; + return literal; + } + + /** + * Creates a statement that can be used indicate a Break operation to the provided label. + * + * @param label A label. + * @param location An optional source map location for the statement. + */ + function createInlineBreak(label: Label, location?: TextRange): ReturnStatement { + Debug.assert(label > 0, `Invalid label: ${label}`); + return createReturn( + createArrayLiteral([ + createInstruction(Instruction.Break), + createLabel(label) + ]), + location + ); + } + + /** + * Creates a statement that can be used indicate a Return operation. + * + * @param expression The expression for the return statement. + * @param location An optional source map location for the statement. + */ + function createInlineReturn(expression?: Expression, location?: TextRange): ReturnStatement { + return createReturn( + createArrayLiteral(expression + ? [createInstruction(Instruction.Return), expression] + : [createInstruction(Instruction.Return)] + ), + location + ); + } + + /** + * Creates an expression that can be used to resume from a Yield operation. + */ + function createGeneratorResume(location?: TextRange): LeftHandSideExpression { + return createCall(createPropertyAccess(state, "sent"), /*typeArguments*/ undefined, [], location); + } + + /** + * Emits an empty instruction. + */ + function emitNop() { + emitWorker(OpCode.Nop); + } + + /** + * Emits a Statement. + * + * @param node A statement. + */ + function emitStatement(node: Statement): void { + if (node) { + emitWorker(OpCode.Statement, [node]); + } + else { + emitNop(); + } + } + + /** + * Emits an Assignment operation. + * + * @param left The left-hand side of the assignment. + * @param right The right-hand side of the assignment. + * @param location An optional source map location for the assignment. + */ + function emitAssignment(left: Expression, right: Expression, location?: TextRange): void { + emitWorker(OpCode.Assign, [left, right], location); + } + + /** + * Emits a Break operation to the specified label. + * + * @param label A label. + * @param location An optional source map location for the assignment. + */ + function emitBreak(label: Label, location?: TextRange): void { + emitWorker(OpCode.Break, [label], location); + } + + /** + * Emits a Break operation to the specified label when a condition evaluates to a truthy + * value at runtime. + * + * @param label A label. + * @param condition The condition. + * @param location An optional source map location for the assignment. + */ + function emitBreakWhenTrue(label: Label, condition: Expression, location?: TextRange): void { + emitWorker(OpCode.BreakWhenTrue, [label, condition], location); + } + + /** + * Emits a Break to the specified label when a condition evaluates to a falsey value at + * runtime. + * + * @param label A label. + * @param condition The condition. + * @param location An optional source map location for the assignment. + */ + function emitBreakWhenFalse(label: Label, condition: Expression, location?: TextRange): void { + emitWorker(OpCode.BreakWhenFalse, [label, condition], 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 emitYield(expression?: Expression, location?: TextRange): void { + emitWorker(OpCode.Yield, [expression], location); + } + + /** + * Emits a Return operation for the provided expression. + * + * @param expression An optional value for the operation. + * @param location An optional source map location for the assignment. + */ + function emitReturn(expression?: Expression, location?: TextRange): void { + emitWorker(OpCode.Return, [expression], location); + } + + /** + * Emits a Throw operation for the provided expression. + * + * @param expression A value for the operation. + * @param location An optional source map location for the assignment. + */ + function emitThrow(expression: Expression, location?: TextRange): void { + emitWorker(OpCode.Throw, [expression], location); + } + + /** + * Emits an Endfinally operation. This is used to handle `finally` block semantics. + */ + function emitEndfinally(): void { + emitWorker(OpCode.Endfinally); + } + + /** + * Emits an operation. + * + * @param code The OpCode for the operation. + * @param args The optional arguments for the operation. + */ + function emitWorker(code: OpCode, args?: OperationArguments, location?: TextRange): void { + if (operations === undefined) { + operations = []; + operationArguments = []; + operationLocations = []; + } + + if (labelOffsets === undefined) { + // mark entry point + markLabel(defineLabel()); + } + + const operationIndex = operations.length; + operations[operationIndex] = code; + operationArguments[operationIndex] = args; + operationLocations[operationIndex] = location; + } + + /** + * Builds the generator function body. + */ + function build() { + blockIndex = 0; + labelNumber = 0; + labelNumbers = undefined; + lastOperationWasAbrupt = false; + lastOperationWasCompletion = false; + clauses = undefined; + statements = undefined; + exceptionBlockStack = undefined; + currentExceptionBlock = undefined; + withBlockStack = undefined; + + const buildResult = buildStatements(); + return createCall( + createHelperName(currentSourceFile.externalHelpersModuleName, "__generator"), + /*typeArguments*/ undefined, + [ + createThis(), + setNodeEmitFlags( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [createParameter(state)], + /*type*/ undefined, + createBlock( + buildResult, + /*location*/ undefined, + /*multiLine*/ buildResult.length > 0 + ) + ), + NodeEmitFlags.ReuseTempVariableScope + ) + ] + ); + } + + /** + * Builds the statements for the generator function body. + */ + function buildStatements(): Statement[] { + if (operations) { + for (let operationIndex = 0; operationIndex < operations.length; operationIndex++) { + writeOperation(operationIndex); + } + + flushFinalLabel(operations.length); + } + else { + flushFinalLabel(0); + } + + if (clauses) { + const labelExpression = createPropertyAccess(state, "label"); + const switchStatement = createSwitch(labelExpression, createCaseBlock(clauses)); + switchStatement.startsOnNewLine = true; + return [switchStatement]; + } + + if (statements) { + return statements; + } + + return []; + } + + /** + * Flush the current label and advance to a new label. + */ + function flushLabel(): void { + if (!statements) { + return; + } + + appendLabel(/*markLabelEnd*/ !lastOperationWasAbrupt); + + lastOperationWasAbrupt = false; + lastOperationWasCompletion = false; + labelNumber++; + } + + /** + * Flush the final label of the generator function body. + */ + function flushFinalLabel(operationIndex: number): void { + if (!lastOperationWasCompletion) { + tryEnterLabel(operationIndex); + withBlockStack = undefined; + writeReturn(/*expression*/ undefined, /*operationLocation*/ undefined); + } + + if (statements && clauses) { + appendLabel(/*markLabelEnd*/ false); + } + + updateLabelExpressions(); + } + + /** + * Appends a case clause for the last label and sets the new label. + * + * @param markLabelEnd Indicates that the transition between labels was a fall-through + * from a previous case clause and the change in labels should be + * reflected on the `state` object. + */ + function appendLabel(markLabelEnd: boolean): void { + if (!clauses) { + clauses = []; + } + + if (statements) { + if (withBlockStack) { + // The previous label was nested inside one or more `with` blocks, so we + // surround the statements in generated `with` blocks to create the same environment. + for (let i = withBlockStack.length - 1; i >= 0; i--) { + const withBlock = withBlockStack[i]; + statements = [createWith(withBlock.expression, createBlock(statements))]; + } + } + + if (currentExceptionBlock) { + // The previous label was nested inside of an exception block, so we must + // indicate entry into a protected region by pushing the label numbers + // for each block in the protected region. + const { startLabel, catchLabel, finallyLabel, endLabel } = currentExceptionBlock; + statements.unshift( + createStatement( + createCall( + createPropertyAccess(createPropertyAccess(state, "trys"), "push"), + /*typeArguments*/ undefined, + [ + createArrayLiteral([ + createLabel(startLabel), + createLabel(catchLabel), + createLabel(finallyLabel), + createLabel(endLabel) + ]) + ] + ) + ) + ); + + currentExceptionBlock = undefined; + } + + if (markLabelEnd) { + // The case clause for the last label falls through to this label, so we + // add an assignment statement to reflect the change in labels. + statements.push( + createStatement( + createAssignment( + createPropertyAccess(state, "label"), + createLiteral(labelNumber + 1) + ) + ) + ); + } + } + + clauses.push( + createCaseClause( + createLiteral(labelNumber), + statements || [] + ) + ); + + statements = undefined; + } + + /** + * Tries to enter into a new label at the current operation index. + */ + function tryEnterLabel(operationIndex: number): void { + if (!labelOffsets) { + return; + } + + for (let label = 0; label < labelOffsets.length; label++) { + if (labelOffsets[label] === operationIndex) { + flushLabel(); + if (labelNumbers === undefined) { + labelNumbers = []; + } + if (labelNumbers[labelNumber] === undefined) { + labelNumbers[labelNumber] = [label]; + } + else { + labelNumbers[labelNumber].push(label); + } + } + } + } + + /** + * Updates literal expressions for labels with actual label numbers. + */ + function updateLabelExpressions() { + if (labelExpressions !== undefined && labelNumbers !== undefined) { + for (let labelNumber = 0; labelNumber < labelNumbers.length; labelNumber++) { + const labels = labelNumbers[labelNumber]; + if (labels !== undefined) { + for (const label of labels) { + const expressions = labelExpressions[label]; + if (expressions !== undefined) { + for (const expression of expressions) { + expression.text = String(labelNumber); + } + } + } + } + } + } + } + + /** + * Tries to enter or leave a code block. + */ + function tryEnterOrLeaveBlock(operationIndex: number): void { + if (blocks) { + for (; blockIndex < blockActions.length && blockOffsets[blockIndex] <= operationIndex; blockIndex++) { + const block = blocks[blockIndex]; + const blockAction = blockActions[blockIndex]; + if (isExceptionBlock(block)) { + if (blockAction === BlockAction.Open) { + if (!exceptionBlockStack) { + exceptionBlockStack = []; + } + + if (!statements) { + statements = []; + } + + exceptionBlockStack.push(currentExceptionBlock); + currentExceptionBlock = block; + } + else if (blockAction === BlockAction.Close) { + currentExceptionBlock = exceptionBlockStack.pop(); + } + } + else if (isWithBlock(block)) { + if (blockAction === BlockAction.Open) { + if (!withBlockStack) { + withBlockStack = []; + } + + withBlockStack.push(block); + } + else if (blockAction === BlockAction.Close) { + withBlockStack.pop(); + } + } + } + } + } + + /** + * Writes an operation as a statement to the current label's statement list. + * + * @param operation The OpCode of the operation + */ + function writeOperation(operationIndex: number): void { + tryEnterLabel(operationIndex); + tryEnterOrLeaveBlock(operationIndex); + + // early termination, nothing else to process in this label + if (lastOperationWasAbrupt) { + return; + } + + lastOperationWasAbrupt = false; + lastOperationWasCompletion = false; + + const opcode = operations[operationIndex]; + if (opcode === OpCode.Nop) { + return; + } + else if (opcode === OpCode.Endfinally) { + return writeEndfinally(); + } + + const args = operationArguments[operationIndex]; + if (opcode === OpCode.Statement) { + return writeStatement(args[0]); + } + + const location = operationLocations[operationIndex]; + switch (opcode) { + case OpCode.Assign: + return writeAssign(args[0], args[1], location); + case OpCode.Break: + return writeBreak(