Index: pkg/compiler/lib/src/js/rewrite_async.dart |
diff --git a/pkg/compiler/lib/src/js/rewrite_async.dart b/pkg/compiler/lib/src/js/rewrite_async.dart |
index 656b1af9d9bdff50e11d41d8c8b1ef0961d644d1..71de2911e53d9afde7205ce9a6183b7b720225cb 100644 |
--- a/pkg/compiler/lib/src/js/rewrite_async.dart |
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart |
@@ -4,13 +4,15 @@ |
library rewrite_async; |
-// TODO(sigurdm): Throws in catch-handlers are handled wrong. |
// TODO(sigurdm): Avoid using variables in templates. It could blow up memory |
// use. |
import "dart:math" show max; |
import 'dart:collection'; |
+import 'package:_internal/compiler/js_lib/shared/async_await_error_codes.dart' |
floitsch
2015/02/13 15:30:06
I think we import it relative for embedded names.
sigurdm
2015/02/17 08:43:10
At least in js_backend/js_backend.dart it is impor
|
+ as error_codes; |
floitsch
2015/02/13 15:30:07
not 100% about the naming convention of imports. W
sigurdm
2015/02/17 08:43:10
The style guide says lower_case_underscores.
|
+ |
import "js.dart" as js; |
import '../util/util.dart'; |
@@ -38,7 +40,9 @@ class AsyncRewriter extends js.NodeVisitor { |
Map<js.Node, int> continueLabels = new Map<js.Node, int>(); |
Map<js.Node, int> breakLabels = new Map<js.Node, int>(); |
Map<js.Node, int> finallyLabels = new Map<js.Node, int>(); |
+ Map<js.Node, int> handlerLabels = new Map<js.Node, int>(); |
int exitLabel; |
+ int rethrowLabel; |
// A stack of all enclosing jump targets (including the function for |
// representing the target of a return, and all enclosing try-blocks that have |
@@ -86,13 +90,17 @@ class AsyncRewriter extends js.NodeVisitor { |
/// } |
/// } |
/// |
- /// It is a parameter to the [helperName] function, so that [thenHelper] and |
- /// [streamHelper] can call [helperName] with the result of an awaited Future. |
+ /// It is a parameter to the [bodyName] function, so that [asyncHelper] and |
+ /// [streamHelper] can call [bodyName] with the result of an awaited Future. |
String resultName; |
+ /// A parameter to the [bodyName] function. Indicating if we are in success |
+ /// or error case. |
+ String errorCodeName; |
+ |
/// The name of the inner function that is scheduled to do each await/yield, |
/// and called to do a new iteration for sync*. |
- String helperName; |
+ String bodyName; |
/// The Completer that will finish an async function. |
/// |
@@ -121,9 +129,16 @@ class AsyncRewriter extends js.NodeVisitor { |
/// the last. |
String nextName; |
+ /// The stack of labels of finally blocks to assign to [nextName] if the |
+ /// async* [StreamSubscription] was canceled during a yield. |
+ String yieldNextName; |
floitsch
2015/02/13 15:30:06
nextWhenCanceledName ?
sigurdm
2015/02/17 08:43:09
Better, Donde
|
+ |
/// The current returned value (a finally block may overwrite it). |
String returnValueName; |
+ /// The thrown error (it might need to be rethrown after a finally block). |
floitsch
2015/02/13 15:30:06
currentErrorName ?
/// If we are in the process o
sigurdm
2015/02/17 08:43:09
Done.
|
+ String storedErrorName; |
+ |
/// The label of the outer loop. |
/// |
/// Used if there are untransformed loops containing break or continues to |
@@ -131,7 +146,7 @@ class AsyncRewriter extends js.NodeVisitor { |
String outerLabelName; |
/// If javascript `this` is used, it is accessed via this variable, in the |
- /// [helperName] function. |
+ /// [bodyName] function. |
String selfName; |
// These expressions are hooks for communicating with the runtime. |
@@ -141,7 +156,7 @@ class AsyncRewriter extends js.NodeVisitor { |
/// For an await it is called with: |
/// |
/// - The value to await |
- /// - The [helperName] |
+ /// - The [bodyName] |
/// - The [completerName] |
/// - A JavaScript function that is executed if the future completed with |
/// an error. That function is responsible for executing the right error |
@@ -153,7 +168,7 @@ class AsyncRewriter extends js.NodeVisitor { |
/// - null |
/// - The [completerName] |
/// - null. |
- final js.Expression thenHelper; |
+ final js.Expression asyncHelper; |
/// The function called by an async* function to simulate an await, yield or |
/// yield*. |
@@ -162,7 +177,7 @@ class AsyncRewriter extends js.NodeVisitor { |
/// |
/// - The value to await/yieldExpression(value to yield)/ |
/// yieldStarExpression(stream to yield) |
- /// - The [helperName] |
+ /// - The [bodyName] |
floitsch
2015/02/13 15:30:06
The body function ([bodyName]). ?
Otherwise it so
sigurdm
2015/02/17 08:43:10
Done.
|
/// - The [controllerName] |
/// - A JavaScript function that is executed if the future completed with |
/// an error. That function is responsible for executing the right error |
@@ -192,7 +207,7 @@ class AsyncRewriter extends js.NodeVisitor { |
final js.Expression streamOfController; |
/// Contructor creating the Iterable for a sync* method. Called with |
- /// [helperName]. |
+ /// [bodyName]. |
final js.Expression newIterable; |
/// A JS Expression that creates a marker showing that iteration is over. |
@@ -235,7 +250,7 @@ class AsyncRewriter extends js.NodeVisitor { |
AsyncRewriter(this.diagnosticListener, |
spannable, |
- {this.thenHelper, |
+ {this.asyncHelper, |
this.streamHelper, |
this.streamOfController, |
this.newCompleter, |
@@ -263,14 +278,17 @@ class AsyncRewriter extends js.NodeVisitor { |
// To avoid name collisions with existing names, the fresh names are |
// generated after the analysis. |
resultName = freshName("result"); |
+ errorCodeName = freshName("errorCode"); |
completerName = freshName("completer"); |
controllerName = freshName("controller"); |
- helperName = freshName("helper"); |
+ bodyName = freshName("body"); |
gotoName = freshName("goto"); |
handlerName = freshName("handler"); |
errorName = freshName("error"); |
nextName = freshName("next"); |
+ yieldNextName = freshName("yieldNext"); |
returnValueName = freshName("returnValue"); |
+ storedErrorName = freshName("storedError"); |
outerLabelName = freshName("outer"); |
selfName = freshName("self"); |
@@ -279,7 +297,7 @@ class AsyncRewriter extends js.NodeVisitor { |
js.Expression get currentErrorHandler { |
return errorHandlerLabels.isEmpty |
- ? new js.LiteralNull() |
+ ? js.number(rethrowLabel) |
: js.number(errorHandlerLabels.last); |
} |
@@ -542,7 +560,7 @@ class AsyncRewriter extends js.NodeVisitor { |
/// through all the enclosing finally blocks). The jump to here is made in |
/// [visitReturn]. |
/// |
- /// Returning from an async method calls the [thenHelper] with the result. |
+ /// Returning from an async method calls the [asyncHelper] with the result. |
/// (the result might have been stored in [returnValueName] by some finally |
/// block). |
/// |
@@ -561,16 +579,17 @@ class AsyncRewriter extends js.NodeVisitor { |
String returnValue = |
analysis.hasExplicitReturns ? returnValueName : "null"; |
addStatement(js.js.statement( |
- "return #thenHelper($returnValue, null, $completerName, null)", { |
- "thenHelper": thenHelper |
- })); |
+ "return #thenHelper($returnValue, ${error_codes.SUCCESS}, " |
floitsch
2015/02/13 15:30:06
Don't use "$" if you don't need to.
This is a val
sigurdm
2015/02/17 08:43:10
Done.
|
+ "$completerName, null)", |
+ {"thenHelper": asyncHelper})); |
break; |
case const js.AsyncModifier.syncStar(): |
addStatement(new js.Return(new js.Call(endOfIteration, []))); |
break; |
case const js.AsyncModifier.asyncStar(): |
addStatement(js.js.statement( |
- "return #streamHelper(null, null, $controllerName, null)", { |
+ "return #streamHelper(null, ${error_codes.SUCCESS}, " |
floitsch
2015/02/13 15:30:06
ditto.
sigurdm
2015/02/17 08:43:10
Done.
|
+ "$controllerName)", { |
"streamHelper": streamHelper |
})); |
break; |
@@ -578,9 +597,16 @@ class AsyncRewriter extends js.NodeVisitor { |
diagnosticListener.internalError( |
spannable, "Internal error, unexpected asyncmodifier $async"); |
} |
+ if (isAsync || isAsyncStar) { |
+ beginLabel(rethrowLabel); |
+ addStatement(js.js.statement( |
+ "return #thenHelper($storedErrorName, ${error_codes.ERROR}, " |
floitsch
2015/02/13 15:30:06
ditto.
sigurdm
2015/02/17 08:43:09
Done.
|
+ "${isAsync ? completerName : controllerName})", |
+ {"thenHelper": isAsync ? asyncHelper : streamHelper})); |
+ } |
} |
- /// The initial call to [thenHelper]/[streamHelper]. |
+ /// The initial call to [asyncHelper]/[streamHelper]. |
/// |
/// There is no value to await/yield, so the first argument is `null` and |
/// also the errorCallback is `null`. |
@@ -590,8 +616,8 @@ class AsyncRewriter extends js.NodeVisitor { |
js.Statement generateInitializer() { |
if (isAsync) { |
return js.js.statement( |
- "return #thenHelper(null, $helperName, $completerName, null);", { |
- "thenHelper": thenHelper |
+ "return #asyncHelper(null, $bodyName, $completerName, null);", { |
+ "asyncHelper": asyncHelper |
}); |
} else if (isAsyncStar) { |
return js.js.statement( |
@@ -607,17 +633,17 @@ class AsyncRewriter extends js.NodeVisitor { |
/// Rewrites an async/sync*/async* function to a normal Javascript function. |
/// |
/// The control flow is flattened by simulating 'goto' using a switch in a |
- /// loop and a state variable [gotoName] inside a helper-function that can be |
- /// called back by [thenHelper]/[streamHelper]/the [Iterator]. |
+ /// loop and a state variable [gotoName] inside a nested function [bodyName] |
+ /// that can be called back by [asyncHelper]/[asyncStarHelper]/the [Iterator]. |
/// |
/// Local variables are hoisted outside the helper. |
/// |
/// Awaits in async/async* are translated to code that remembers the current |
/// location (so the function can resume from where it was) followed by a call |
- /// to the [thenHelper]. The helper sets up the waiting for the awaited value |
+ /// to the [asyncHelper]. The helper sets up the waiting for the awaited value |
/// and returns a future which is immediately returned by the translated |
/// await. |
- /// Yields in async* are translated to a call to the [streamHelper]. They, |
+ /// Yields in async* are translated to a call to the [asyncStarHelper]. They, |
/// too, need to be prepared to be interrupted in case the stream is paused or |
/// canceled. (Currently we always suspend - this is different from the spec, |
/// see `streamHelper` in `js_helper.dart`). |
@@ -635,7 +661,7 @@ class AsyncRewriter extends js.NodeVisitor { |
/// return bar(p); |
/// } |
/// |
- /// Becomes: |
+ /// Becomes (without error handling): |
/// |
/// function(x, y, z) { |
/// var goto = 0, returnValue, completer = new Completer(), p; |
@@ -644,17 +670,17 @@ class AsyncRewriter extends js.NodeVisitor { |
/// switch (goto) { |
/// case 0: |
/// goto = 1 // Remember where to continue when the future succeeds. |
- /// return thenHelper(foo(), helper, completer, null); |
+ /// return thenHelper(foo(), helper, completer); |
/// case 1: |
/// p = result; |
/// returnValue = bar(p); |
/// goto = 2; |
/// break; |
/// case 2: |
- /// return thenHelper(returnValue, null, completer, null) |
+ /// return thenHelper(returnValue, null, completer) |
/// } |
/// } |
- /// return thenHelper(null, helper, completer, null); |
+ /// return thenHelper(null, helper, completer); |
/// } |
/// } |
/// |
@@ -689,11 +715,16 @@ class AsyncRewriter extends js.NodeVisitor { |
/// var goto = 0; |
/// var returnValue; |
/// var completer = new Completer(); |
- /// var handler = null; |
+ /// var handler = 8; // Outside try-blocks go to the rethrow label. |
floitsch
2015/02/13 15:30:06
Handler is slightly weird, because we also use it
sigurdm
2015/02/17 08:43:10
Acknowledged. I mentioned it in visitTry.
|
/// var p; |
+ /// var storedError; |
/// // The result can be either the result of an awaited future, or an |
/// // error if the future completed with an error. |
- /// function helper(result) { |
+ /// function helper(errorCode, result) { |
+ /// if (errorCode == 1) { |
+ /// storedError = result; |
+ /// goto = handler; |
+ /// } |
/// while (true) { |
/// try { |
/// switch (goto) { |
@@ -718,7 +749,9 @@ class AsyncRewriter extends js.NodeVisitor { |
/// goto = 5; // finally handler for outer try. |
/// break; |
/// case 4: // catch handler for outer try. |
- /// e = result; |
+ /// handler = 5; // If we throw in this handler, do the finally |
floitsch
2015/02/13 15:30:06
?
// If the handler throws, do the finally ...
//
floitsch
2015/02/13 15:30:07
?
// If the handler throws, do the finally ...
//
sigurdm
2015/02/17 08:43:10
Acknowledged.
sigurdm
2015/02/17 08:43:10
Done.
|
+ /// next = [8] // And rethrow. |
+ /// e = storedError; |
/// handle(e); |
/// // Fall through to finally. |
/// case 5: // finally handler for outer try. |
@@ -728,14 +761,16 @@ class AsyncRewriter extends js.NodeVisitor { |
/// break; |
/// case 6: // Exiting outer try. |
/// case 7: // return |
- /// return thenHelper(returnValue, null, completer, null); |
+ /// return thenHelper(returnValue, 0, completer); |
+ /// case 8: // Rethrow |
+ /// return thenHelper(storedError, 1, completer); |
/// } |
/// } catch (error) { |
- /// result = error; |
+ /// storedError = error; |
/// goto = handler; |
/// } |
/// } |
- /// return thenHelper(null, helper, completer, null); |
+ /// return thenHelper(null, helper, completer); |
/// } |
/// } |
/// |
@@ -748,6 +783,8 @@ class AsyncRewriter extends js.NodeVisitor { |
// [visitDartYield]. |
exitLabel = |
analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; |
+ rethrowLabel = newLabel("rethrow"); |
+ handlerLabels[node] = rethrowLabel; |
js.Statement body = node.body; |
targetsAndTries.add(node); |
visitStatement(body); |
@@ -762,17 +799,13 @@ class AsyncRewriter extends js.NodeVisitor { |
if (hasJumpThoughOuterLabel) { |
helperBody = js.js.statement("$outerLabelName: #", [helperBody]); |
} |
- if (hasTryBlocks) { |
- helperBody = js.js.statement(""" |
- try { |
- #body |
- } catch ($errorName){ |
- if ($handlerName === null) |
- throw $errorName; |
- $resultName = $errorName; |
- $gotoName = $handlerName; |
- }""", {"body": helperBody}); |
- } |
+ helperBody = js.js.statement(""" |
+ try { |
+ #body |
+ } catch ($errorName){ |
+ $storedErrorName = $errorName; |
+ $gotoName = $handlerName; |
+ }""", {"body": helperBody}); |
List<js.VariableInitialization> inits = <js.VariableInitialization>[]; |
js.VariableInitialization makeInit(String name, js.Expression initValue) { |
@@ -785,17 +818,19 @@ class AsyncRewriter extends js.NodeVisitor { |
inits.add(makeInit(completerName, new js.New(newCompleter, []))); |
} else if (isAsyncStar) { |
inits.add(makeInit(controllerName, |
- new js.Call(newController, [new js.VariableUse(helperName)]))); |
- } |
- if (hasTryBlocks) { |
- inits.add(makeInit(handlerName, new js.LiteralNull())); |
+ new js.Call(newController, [new js.VariableUse(bodyName)]))); |
} |
+ inits.add(makeInit(handlerName, js.number(rethrowLabel))); |
+ inits.add(makeInit(storedErrorName, null)); |
if (hasJumpThroughFinally) { |
inits.add(makeInit(nextName, null)); |
} |
if (analysis.hasExplicitReturns && isAsync) { |
inits.add(makeInit(returnValueName, null)); |
} |
+ if (isSyncStar) { |
+ inits.add(makeInit(resultName, null)); |
+ } |
if (analysis.hasThis && !isSyncStar) { |
// Sync* functions must remember `this` on the level of the outer |
// function. |
@@ -814,7 +849,7 @@ class AsyncRewriter extends js.NodeVisitor { |
var $selfName = this; |
return new #newIterable(function () { |
#varDecl; |
- return function $helperName($resultName) { |
+ return function $bodyName() { |
while (true) |
#helperBody; |
}; |
@@ -831,15 +866,30 @@ class AsyncRewriter extends js.NodeVisitor { |
return js.js(""" |
function (#params) { |
#varDecl; |
- function $helperName($resultName) { |
+ function $bodyName($errorCodeName, $resultName) { |
+ if (#hasYield) |
+ switch ($errorCodeName) { |
+ case ${error_codes.STREAM_WAS_CANCELED}: |
+ $nextName = $yieldNextName; |
+ // fallthrough |
floitsch
2015/02/13 15:30:06
Why fall through?
Is it to avoid repeating the $go
sigurdm
2015/02/17 08:43:09
Only for the handler. I changed the code, so it do
|
+ case ${error_codes.ERROR}: |
+ $storedErrorName = $resultName; |
+ $gotoName = $handlerName; |
+ } |
+ else |
+ if ($errorCodeName == ${error_codes.ERROR}) { |
+ $storedErrorName = $resultName; |
+ $gotoName = $handlerName; |
+ } |
while (true) |
#helperBody; |
} |
#init; |
}""", { |
"params": node.params, |
- "helperBody": helperBody, |
"varDecl": varDecl, |
+ "hasYield": analysis.hasYield, |
+ "helperBody": helperBody, |
"init": generateInitializer() |
}); |
} |
@@ -888,7 +938,7 @@ class AsyncRewriter extends js.NodeVisitor { |
} |
} |
- /// An await is translated to a call to [thenHelper]/[streamHelper]. |
+ /// An await is translated to a call to [asyncHelper]/[streamHelper]. |
/// |
/// See the comments of [visitFun] for an example. |
@override |
@@ -897,23 +947,13 @@ class AsyncRewriter extends js.NodeVisitor { |
int afterAwait = newLabel("returning from await."); |
withExpression(node.expression, (js.Expression value) { |
addStatement(setGotoVariable(afterAwait)); |
- js.Expression errorCallback = errorHandlerLabels.isEmpty |
- ? new js.LiteralNull() |
- : js.js(""" |
- function($errorName) { |
- $gotoName = #currentHandler; |
- $helperName($errorName); |
- }""", {"currentHandler": currentErrorHandler}); |
- |
addStatement(js.js.statement(""" |
return #thenHelper(#value, |
- $helperName, |
- ${isAsync ? completerName : controllerName}, |
- #errorCallback); |
+ $bodyName, |
+ ${isAsync ? completerName : controllerName}); |
""", { |
- "thenHelper": isAsync ? thenHelper : streamHelper, |
+ "thenHelper": isAsync ? asyncHelper : streamHelper, |
floitsch
2015/02/13 15:30:07
rename "thenHelper" to "asyncHelper" ?
sigurdm
2015/02/17 08:43:10
Done.
|
"value": value, |
- "errorCallback": errorCallback |
})); |
}, store: false); |
beginLabel(afterAwait); |
@@ -1523,12 +1563,30 @@ class AsyncRewriter extends js.NodeVisitor { |
}, store: false); |
} |
- setErrorHandler() { |
+ setErrorHandler([int errorHandler]) { |
addExpressionStatement(new js.Assignment( |
- new js.VariableUse(handlerName), currentErrorHandler)); |
+ new js.VariableUse(handlerName), |
+ errorHandler == null ? currentErrorHandler : js.number(errorHandler))); |
} |
- @override |
+ List<int> _finalliesAndEnclosingHandler() { |
+ List<int> result = new List<int>(); |
+ for (int i = targetsAndTries.length - 1; i >= 0; i--) { |
+ js.Node node = targetsAndTries[i]; |
+ int handlerLabel = handlerLabels[node]; |
+ if (handlerLabel != null) { |
+ result.add(handlerLabel); |
+ break; |
+ } |
+ if (node is js.Try) { |
+ assert(node.finallyPart != null); |
+ result.add(finallyLabels[node]); |
+ } |
+ } |
+ return result.reversed.toList(); |
+ } |
+ |
+ /// See the comments of [visitFun] for more explanation. |
void visitTry(js.Try node) { |
if (!shouldTransform(node)) { |
js.Block body = translateInBlock(node.body); |
@@ -1542,6 +1600,7 @@ class AsyncRewriter extends js.NodeVisitor { |
addStatement(new js.Try(body, catchPart, finallyPart)); |
return; |
} |
+ |
hasTryBlocks = true; |
int handlerLabel = newLabel("catch"); |
int finallyLabel = newLabel("finally"); |
@@ -1567,7 +1626,18 @@ class AsyncRewriter extends js.NodeVisitor { |
} |
beginLabel(handlerLabel); |
if (node.catchPart != null) { |
- setErrorHandler(); |
+ if (node.finallyPart == null) { |
+ setErrorHandler(); |
+ } else { |
+ // If an error is thrown in the catch-part, and there is a finally |
+ // block, goto that. |
+ // And from that finally block, go to all enclosing finally blocks until |
floitsch
2015/02/13 15:30:06
If the catch-part throws visit the finally block f
sigurdm
2015/02/17 08:43:09
Done.
|
+ // the first enclosing catch-handler, and go to those. |
+ List<int> finallies = _finalliesAndEnclosingHandler(); |
+ setErrorHandler(finallies.removeLast()); |
+ addStatement(js.js.statement("$nextName = #;", |
+ [new js.ArrayInitializer(finallies.map(js.number).toList())])); |
+ } |
// The catch declaration name can shadow outer variables, so a fresh name |
// is needed to avoid collisions. See Ecma 262, 3rd edition, |
// section 12.14. |
@@ -1576,10 +1646,12 @@ class AsyncRewriter extends js.NodeVisitor { |
variableRenamings |
.add(new Pair(node.catchPart.declaration.name, errorRename)); |
addExpressionStatement(new js.Assignment( |
- new js.VariableUse(errorRename), new js.VariableUse(resultName))); |
+ new js.VariableUse(errorRename), |
+ new js.VariableUse(storedErrorName))); |
visitStatement(node.catchPart.body); |
variableRenamings.removeLast(); |
} |
+ |
if (node.finallyPart != null) { |
targetsAndTries.removeLast(); |
setErrorHandler(); |
@@ -1683,10 +1755,8 @@ class AsyncRewriter extends js.NodeVisitor { |
/// |
/// yield/yield* in an async* function is translated much like the `await` is |
/// translated in [visitAwait], only the object is wrapped in a |
- /// [yieldExpression]/[yieldStarExpression] to let [streamHelper] distinguish. |
- /// |
- /// Because there is no Future that can fail (as there is in await) null is |
- /// passed as the errorCallback. |
+ /// [yieldExpression]/[yieldStarExpression] to let [asyncStarHelper] |
+ /// distinguish. |
floitsch
2015/02/13 15:30:06
distinguish them.
sigurdm
2015/02/17 08:43:09
Done.
|
void addAsyncYield(js.DartYield node, js.Expression expression) { |
assert(isAsyncStar); |
// Find all the finally blocks that should be performed if the stream is |
@@ -1696,23 +1766,16 @@ class AsyncRewriter extends js.NodeVisitor { |
enclosingFinallyLabels.addAll(targetsAndTries |
.where((js.Node node) => node is js.Try) |
.map((js.Try node) => finallyLabels[node])); |
- int destinationOnCancel = enclosingFinallyLabels.removeLast(); |
- js.ArrayInitializer finallyListInitializer = new js.ArrayInitializer( |
- enclosingFinallyLabels.map(js.number).toList()); |
+ setErrorHandler(enclosingFinallyLabels.removeLast()); |
sigurdm
2015/02/17 08:43:09
I realized that I forget to reset the error-handle
|
+ addStatement(js.js.statement("$yieldNextName = #", |
+ [new js.ArrayInitializer(enclosingFinallyLabels.map(js.number) |
+ .toList())])); |
addStatement(js.js.statement(""" |
return #streamHelper(#yieldExpression(#expression), |
- $helperName, $controllerName, function () { |
- if (#notEmptyFinallyList) |
- $nextName = #finallyList; |
- $gotoName = #destinationOnCancel; |
- $helperName(); |
- });""", { |
+ $bodyName, $controllerName);""", { |
"streamHelper": streamHelper, |
"yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, |
"expression": expression, |
- "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty, |
- "finallyList": finallyListInitializer, |
- "destinationOnCancel": js.number(destinationOnCancel) |
})); |
} |
@@ -1753,6 +1816,8 @@ class PreTranslationAnalysis extends js.NodeVisitor<bool> { |
bool hasThis = false; |
+ bool hasYield = false; |
+ |
// The function currently being analyzed. |
js.Fun currentFunction; |
@@ -2143,6 +2208,7 @@ class PreTranslationAnalysis extends js.NodeVisitor<bool> { |
@override |
bool visitDartYield(js.DartYield node) { |
+ hasYield = true; |
visit(node.expression); |
return true; |
} |