Chromium Code Reviews| Index: lib/src/codegen/js_codegen.dart |
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
| index 608628ea1af0dbea81b5dc626514f7ff42d5ebd1..3da8e91ca9c15a398f182aa53c916267da308326 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,71 @@ 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; |
| + } |
| + var jsThis = new _JsThisFinder(); |
| + JS.Expression gen = new JS.Fun(jsParams, _visit(body), isGenerator: true); |
| + gen.accept(jsThis); |
| + if (jsThis.found) { |
| + gen = js.call('#.bind(this)', gen); |
| + } |
| + _asyncStarController = savedController; |
| + |
| + var T = _emitTypeName(rules.getExpectedReturnType(body)); |
| + return js.call('dart.#(#)', [kind, [gen, T]..addAll(params)]); |
| } |
| @override |
| @@ -1710,7 +1777,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 |
| @@ -2416,7 +2509,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor { |
| } |
| @override |
| - JS.ForOf visitForEachStatement(ForEachStatement node) { |
| + visitForEachStatement(ForEachStatement node) { |
|
vsm
2015/07/27 21:03:24
Return "JS.Statement"?
Jennifer Messerly
2015/07/27 21:46:35
Done.
|
| + if (node.awaitKeyword != null) { |
| + return _emitAwaitFor(node); |
| + } |
| + |
| var init = _visit(node.identifier); |
| if (init == null) { |
| init = js.call('let #', node.loopVariable.identifier.name); |
| @@ -2424,6 +2521,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; |