| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index a20b3799c0412b9e9cc952b87c0753909900112e..0e4c39b0f2b455d34ef1fd5be92c9032eb752ab6 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -67,6 +67,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| /// The variable for the current catch clause
|
| SimpleIdentifier _catchParameter;
|
|
|
| + /// In an async* function, this represents the stream controller parameter.
|
| + JS.TemporaryId _asyncStarController;
|
| +
|
| /// Imported libraries, and the temporaries used to refer to them.
|
| final _imports = new Map<LibraryElement, JS.TemporaryId>();
|
| final _exports = new Set<String>();
|
| @@ -1165,7 +1168,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| if (params == null) params = <JS.Parameter>[];
|
|
|
| return new JS.Method(
|
| - _elementMemberName(node.element), _emitJsFunction(params, node.body),
|
| + _elementMemberName(node.element), _emitFunctionBody(params, node.body),
|
| isGetter: node.isGetter,
|
| isSetter: node.isSetter,
|
| isStatic: node.isStatic);
|
| @@ -1252,19 +1255,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| var parent = node.parent;
|
| var inStmt = parent.parent is FunctionDeclarationStatement;
|
| if (parent is FunctionDeclaration) {
|
| - return _emitJsFunction(params, node.body);
|
| + return _emitFunctionBody(params, node.body);
|
| } else {
|
| String code;
|
| - AstNode body;
|
| - var nodeBody = node.body;
|
| - if (nodeBody is ExpressionFunctionBody) {
|
| + JS.Node jsBody;
|
| + var body = node.body;
|
| + if (body.isGenerator || body.isAsynchronous) {
|
| + code = '(#) => #';
|
| + jsBody = _emitGeneratorFunctionBody(params, body);
|
| + } else if (body is ExpressionFunctionBody) {
|
| code = '(#) => #';
|
| - body = nodeBody.expression;
|
| + jsBody = _visit(body.expression);
|
| } else {
|
| code = '(#) => { #; }';
|
| - body = nodeBody;
|
| + jsBody = _visit(body);
|
| }
|
| - var clos = js.call(code, [params, _visit(body)]);
|
| + var clos = js.call(code, [params, jsBody]);
|
| if (!inStmt) {
|
| var type = getStaticType(node);
|
| return _emitFunctionTagged(clos, type,
|
| @@ -1274,10 +1280,69 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| }
|
| }
|
|
|
| - JS.Fun _emitJsFunction(List<JS.Parameter> params, FunctionBody body) {
|
| - // TODO(jmesserly): async/async*
|
| - var syncStar = body.isSynchronous && body.star != null;
|
| - return new JS.Fun(params, _visit(body), isGenerator: syncStar);
|
| + JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body) {
|
| + // sync*, async, async*
|
| + if (body.isAsynchronous || body.isGenerator) {
|
| + return new JS.Fun(params, js.statement(
|
| + '{ return #; }', [_emitGeneratorFunctionBody(params, body)]));
|
| + }
|
| + // normal function (sync)
|
| + return new JS.Fun(params, _visit(body));
|
| + }
|
| +
|
| + JS.Expression _emitGeneratorFunctionBody(
|
| + List<JS.Parameter> params, FunctionBody body) {
|
| + var kind = body.isSynchronous ? 'sync' : 'async';
|
| + if (body.isGenerator) kind += 'Star';
|
| +
|
| + // Transforms `sync*` `async` and `async*` function bodies
|
| + // using ES6 generators.
|
| + //
|
| + // `sync*` wraps a generator in a Dart Iterable<T>:
|
| + //
|
| + // function name(<args>) {
|
| + // return dart.syncStar(function*(<args>) {
|
| + // <body>
|
| + // }, T, <args>).bind(this);
|
| + // }
|
| + //
|
| + // We need to include <args> in case any are mutated, so each `.iterator`
|
| + // gets the same initial values.
|
| + //
|
| + // TODO(jmesserly): we could omit the args for the common case where args
|
| + // are not mutated inside the generator.
|
| + //
|
| + // In the future, we might be able to simplify this, see:
|
| + // https://github.com/dart-lang/dev_compiler/issues/247.
|
| + //
|
| + // `async` works the same, but uses the `dart.async` helper.
|
| + //
|
| + // In the body of a `sync*` and `async`, `yield`/`await` are both generated
|
| + // simply as `yield`.
|
| + //
|
| + // `async*` uses the `dart.asyncStar` helper, and also has an extra `stream`
|
| + // argument to the generator, which is used for passing values to the
|
| + // _AsyncStarStreamController implementation type.
|
| + // `yield` is specially generated inside `async*`, see visitYieldStatement.
|
| + // `await` is generated as `yield`.
|
| + // runtime/_generators.js has an example of what the code is generated as.
|
| + var savedController = _asyncStarController;
|
| + List jsParams;
|
| + if (kind == 'asyncStar') {
|
| + _asyncStarController = new JS.TemporaryId('stream');
|
| + jsParams = [_asyncStarController]..addAll(params);
|
| + } else {
|
| + _asyncStarController = null;
|
| + jsParams = params;
|
| + }
|
| + JS.Expression gen = new JS.Fun(jsParams, _visit(body), isGenerator: true);
|
| + if (JS.This.foundIn(gen)) {
|
| + gen = js.call('#.bind(this)', gen);
|
| + }
|
| + _asyncStarController = savedController;
|
| +
|
| + var T = _emitTypeName(rules.getExpectedReturnType(body));
|
| + return js.call('dart.#(#)', [kind, [gen, T]..addAll(params)]);
|
| }
|
|
|
| @override
|
| @@ -1708,7 +1773,33 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| @override
|
| JS.Statement visitYieldStatement(YieldStatement node) {
|
| JS.Expression jsExpr = _visit(node.expression);
|
| - return jsExpr.toYieldStatement(star: node.star != null);
|
| + var star = node.star != null;
|
| + if (_asyncStarController != null) {
|
| + // async* yields are generated differently from sync* yields. `yield e`
|
| + // becomes:
|
| + //
|
| + // if (stream.add(e)) return;
|
| + // yield;
|
| + //
|
| + // `yield* e` becomes:
|
| + //
|
| + // if (stream.addStream(e)) return;
|
| + // yield;
|
| + var helperName = star ? 'addStream' : 'add';
|
| + return js.statement('{ if(#.#(#)) return; #; }', [
|
| + _asyncStarController,
|
| + helperName,
|
| + jsExpr,
|
| + new JS.Yield(null)
|
| + ]);
|
| + }
|
| + // A normal yield in a sync*
|
| + return jsExpr.toYieldStatement(star: star);
|
| + }
|
| +
|
| + @override
|
| + JS.Expression visitAwaitExpression(AwaitExpression node) {
|
| + return new JS.Yield(_visit(node.expression));
|
| }
|
|
|
| @override
|
| @@ -2414,7 +2505,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| }
|
|
|
| @override
|
| - JS.ForOf visitForEachStatement(ForEachStatement node) {
|
| + JS.Statement visitForEachStatement(ForEachStatement node) {
|
| + if (node.awaitKeyword != null) {
|
| + return _emitAwaitFor(node);
|
| + }
|
| +
|
| var init = _visit(node.identifier);
|
| if (init == null) {
|
| init = js.call('let #', node.loopVariable.identifier.name);
|
| @@ -2422,6 +2517,58 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
|
| return new JS.ForOf(init, _visit(node.iterable), _visit(node.body));
|
| }
|
|
|
| + JS.Statement _emitAwaitFor(ForEachStatement node) {
|
| + // Emits `await for (var value in stream) ...`, which desugars as:
|
| + //
|
| + // var iter = new StreamIterator<T>(stream);
|
| + // try {
|
| + // while (await iter.moveNext()) {
|
| + // var value = iter.current;
|
| + // ...
|
| + // }
|
| + // } finally {
|
| + // await iter.cancel();
|
| + // }
|
| + //
|
| + // Like the Dart VM, we call cancel() always, as it's safe to call if the
|
| + // stream has already been cancelled.
|
| + //
|
| + // TODO(jmesserly): we may want a helper if these become common. For now the
|
| + // full desugaring seems okay.
|
| + var context = compiler.context;
|
| + var dart_async = context
|
| + .computeLibraryElement(context.sourceFactory.forUri('dart:async'));
|
| + var T = node.loopVariable.element.type;
|
| + var StreamIterator_T =
|
| + dart_async.getType('StreamIterator').type.substitute4([T]);
|
| +
|
| + var createStreamIter = _emitInstanceCreationExpression(
|
| + StreamIterator_T.element.unnamedConstructor, StreamIterator_T, null,
|
| + AstBuilder.argumentList([node.iterable]), false);
|
| + var iter = _visit(_createTemporary('it', StreamIterator_T));
|
| +
|
| + var init = _visit(node.identifier);
|
| + if (init == null) {
|
| + init = js.call(
|
| + 'let # = #.current', [node.loopVariable.identifier.name, iter]);
|
| + } else {
|
| + init = js.call('# = #.current', [init, iter]);
|
| + }
|
| + return js.statement('{'
|
| + ' let # = #;'
|
| + ' try {'
|
| + ' while (#) { #; #; }'
|
| + ' } finally { #; }'
|
| + '}', [
|
| + iter,
|
| + createStreamIter,
|
| + new JS.Yield(js.call('#.moveNext()', iter)),
|
| + init,
|
| + _visit(node.body),
|
| + new JS.Yield(js.call('#.cancel()', iter))
|
| + ]);
|
| + }
|
| +
|
| @override
|
| visitBreakStatement(BreakStatement node) {
|
| var label = node.label;
|
|
|