Adds minimal down-level authoring support for generator functions.

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