Skip to content

[Transforms] Minimal authoring support for down-level generator functions #10106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -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;
}

56 changes: 45 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

8 changes: 4 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
@@ -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.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of suggesting using a standard function, why not suggest using a rest parameter, ie ...args: any[]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same argument could be made for arrow functions, which have pretty much the same error message. I'd rather not overcomplicate the error message here.

"category": "Error",
"code": 2536
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
29 changes: 21 additions & 8 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
@@ -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();
}
5 changes: 1 addition & 4 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
@@ -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,
Loading