Skip to content
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

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
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
Expand Up @@ -2156,6 +2156,9 @@ namespace ts {
if (isAsyncFunctionLike(node)) {
emitFlags |= NodeFlags.HasAsyncFunctions;
}
if (node.asteriskToken) {
emitFlags |= NodeFlags.HasGenerators;
}
}

checkStrictModeFunctionName(<FunctionDeclaration>node);
Expand All @@ -2173,6 +2176,9 @@ namespace ts {
if (isAsyncFunctionLike(node)) {
emitFlags |= NodeFlags.HasAsyncFunctions;
}
if (node.asteriskToken) {
emitFlags |= NodeFlags.HasGenerators;
}
}
if (currentFlow) {
node.flowNode = currentFlow;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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;
}

Expand Down
56 changes: 45 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand All @@ -12287,7 +12305,7 @@ namespace ts {
}

if (funcIsGenerator) {
type = createIterableIteratorType(type);
type = createGeneratorReturnType(type);
}
}
if (!contextualSignature) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Member 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
Expand Down
29 changes: 21 additions & 8 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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];
Expand All @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -295,6 +304,7 @@ const _super = (function (geti, seti) {
decorateEmitted = false;
paramEmitted = false;
awaiterEmitted = false;
generatorEmitted = false;
isOwnFileEmit = false;
}

Expand Down Expand Up @@ -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();
}
Expand Down
5 changes: 1 addition & 4 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Loading