Index: pkg/compiler/lib/src/js/rewrite_async.dart |
=================================================================== |
--- pkg/compiler/lib/src/js/rewrite_async.dart (revision 43839) |
+++ pkg/compiler/lib/src/js/rewrite_async.dart (working copy) |
@@ -4,13 +4,17 @@ |
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. |
+// TODO(sigurdm): Move the try/catch expression to a js_helper function. |
+// That would also simplify the sync* case, where the error can just be thrown. |
import "dart:math" show max; |
import 'dart:collection'; |
+import 'package:_internal/compiler/js_lib/shared/async_await_error_codes.dart' |
+ as error_codes; |
+ |
import "js.dart" as js; |
import '../util/util.dart'; |
@@ -37,14 +41,39 @@ |
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>(); |
+ |
+ /// The label of a finally part. |
+ Map<js.Block, int> finallyLabels = new Map<js.Block, int>(); |
+ |
+ /// The label of the catch handler of a [js.Try] or a [js.Fun] or [js.Catch]. |
+ /// |
+ /// These mark the points an error can be consumed. |
+ /// |
+ /// - The handler of a [js.Fun] is the outermost and will rethrow the error. |
+ /// - The handler of a [js.Try] will run the catch handler. |
+ /// - The handler of a [js.Catch] is a synthetic handler that ensures the |
+ /// right finally blocks are run if an error is thrown inside a |
+ /// catch-handler. |
+ 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 |
- // finally part, this way ensuring all the finally blocks between a jump and |
- // its target are run before the jump. |
- List<js.Node> targetsAndTries = new List<js.Node>(); |
+ /// A stack of all (surrounding) jump targets. |
+ /// |
+ /// Jump targets are: |
+ /// |
+ /// * The function, signalling a return or uncaught throw. |
+ /// * Loops. |
+ /// * LabeledStatements (also used for 'continue' when attached to loops). |
+ /// * Try statements, for catch and finally handlers. |
+ /// * Catch handlers, when inside a catch-part of a try, the catch-handler is |
+ /// used to associate with a synthetic handler that will ensure the right |
+ /// finally blocks are visited. |
+ /// |
+ /// When jumping to a target it is necessary to visit all finallies that |
+ /// are on the way to target (i.e. more nested than the jump target). |
+ List<js.Node> jumpTargets = new List<js.Node>(); |
List<int> continueStack = new List<int>(); |
List<int> breakStack = new List<int>(); |
@@ -55,8 +84,6 @@ |
PreTranslationAnalysis analysis; |
- List<int> errorHandlerLabels = new List<int>(); |
- |
final Function safeVariableName; |
// All the <x>Name variables are names of Javascript variables used in the |
@@ -86,13 +113,17 @@ |
/// } |
/// } |
/// |
- /// 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 +152,16 @@ |
/// 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 nextWhenCanceledName; |
+ |
/// The current returned value (a finally block may overwrite it). |
String returnValueName; |
+ /// If we are in the process of handling an error, stores the current error. |
+ String currentErrorName; |
+ |
/// The label of the outer loop. |
/// |
/// Used if there are untransformed loops containing break or continues to |
@@ -131,7 +169,7 @@ |
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,19 +179,21 @@ |
/// For an await it is called with: |
/// |
/// - The value to await |
- /// - The [helperName] |
- /// - The [completerName] |
- /// - A JavaScript function that is executed if the future completed with |
- /// an error. That function is responsible for executing the right error |
- /// handler and/or finally blocks). |
+ /// - The body function [bodyName] |
+ /// - The completer object [completerName] |
/// |
/// For a return it is called with: |
/// |
/// - The value to complete the completer with. |
- /// - null |
- /// - The [completerName] |
- /// - null. |
- final js.Expression thenHelper; |
+ /// - [error_codes.SUCCESS] |
+ /// - The completer object [completerName] |
+ /// |
+ /// For a throw it is called with: |
+ /// |
+ /// - The error to complete the completer with. |
+ /// - [error_codes.ERROR] |
+ /// - The completer object [completerName] |
+ final js.Expression asyncHelper; |
/// The function called by an async* function to simulate an await, yield or |
/// yield*. |
@@ -162,11 +202,8 @@ |
/// |
/// - The value to await/yieldExpression(value to yield)/ |
/// yieldStarExpression(stream to yield) |
- /// - The [helperName] |
- /// - The [controllerName] |
- /// - A JavaScript function that is executed if the future completed with |
- /// an error. That function is responsible for executing the right error |
- /// handler and/or finally blocks). |
+ /// - The body function [bodyName] |
+ /// - The controller object [controllerName] |
/// |
/// For a return it is called with: |
/// |
@@ -192,7 +229,7 @@ |
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. |
@@ -210,6 +247,9 @@ |
/// Called with the stream to yield from. |
final js.Expression yieldStarExpression; |
+ /// Used by sync* functions to throw exeptions. |
+ final js.Expression uncaughtErrorExpression; |
+ |
final DiagnosticListener diagnosticListener; |
// For error reporting only. |
Spannable get spannable { |
@@ -235,7 +275,7 @@ |
AsyncRewriter(this.diagnosticListener, |
spannable, |
- {this.thenHelper, |
+ {this.asyncHelper, |
this.streamHelper, |
this.streamOfController, |
this.newCompleter, |
@@ -244,6 +284,7 @@ |
this.newIterable, |
this.yieldExpression, |
this.yieldStarExpression, |
+ this.uncaughtErrorExpression, |
this.safeVariableName}) |
: _spannable = spannable; |
@@ -263,14 +304,17 @@ |
// 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"); |
+ nextWhenCanceledName = freshName("nextWhenCanceled"); |
returnValueName = freshName("returnValue"); |
+ currentErrorName = freshName("currentError"); |
outerLabelName = freshName("outer"); |
selfName = freshName("self"); |
@@ -278,9 +322,8 @@ |
} |
js.Expression get currentErrorHandler { |
- return errorHandlerLabels.isEmpty |
- ? new js.LiteralNull() |
- : js.number(errorHandlerLabels.last); |
+ return js.number(handlerLabels[jumpTargets.lastWhere( |
+ (node) => handlerLabels[node] != null)]); |
} |
int allocateTempVar() { |
@@ -542,7 +585,7 @@ |
/// 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,9 +604,10 @@ |
String returnValue = |
analysis.hasExplicitReturns ? returnValueName : "null"; |
addStatement(js.js.statement( |
- "return #thenHelper($returnValue, null, $completerName, null)", { |
- "thenHelper": thenHelper |
- })); |
+ "return #thenHelper($returnValue, #successCode, " |
+ "$completerName, null)", { |
+ "thenHelper": asyncHelper, |
+ "successCode": js.number(error_codes.SUCCESS)})); |
break; |
case const js.AsyncModifier.syncStar(): |
addStatement(new js.Return(new js.Call(endOfIteration, []))); |
@@ -570,17 +614,30 @@ |
break; |
case const js.AsyncModifier.asyncStar(): |
addStatement(js.js.statement( |
- "return #streamHelper(null, null, $controllerName, null)", { |
- "streamHelper": streamHelper |
- })); |
+ "return #streamHelper(null, #successCode, $controllerName)", { |
+ "streamHelper": streamHelper, |
+ "successCode": js.number(error_codes.SUCCESS)})); |
break; |
default: |
diagnosticListener.internalError( |
spannable, "Internal error, unexpected asyncmodifier $async"); |
} |
+ if (isAsync || isAsyncStar) { |
+ beginLabel(rethrowLabel); |
+ addStatement(js.js.statement( |
+ "return #thenHelper($currentErrorName, #errorCode, " |
+ "${isAsync ? completerName : controllerName})", { |
+ "thenHelper": isAsync ? asyncHelper : streamHelper, |
+ "errorCode": js.number(error_codes.ERROR)})); |
+ } else { |
+ assert(isSyncStar); |
+ beginLabel(rethrowLabel); |
+ addStatement(new js.Return(new js.Call(uncaughtErrorExpression, |
+ [new js.VariableUse(currentErrorName)]))); |
+ } |
} |
- /// 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 +647,8 @@ |
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 +664,17 @@ |
/// 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 +692,7 @@ |
/// return bar(p); |
/// } |
/// |
- /// Becomes: |
+ /// Becomes (without error handling): |
/// |
/// function(x, y, z) { |
/// var goto = 0, returnValue, completer = new Completer(), p; |
@@ -644,7 +701,7 @@ |
/// 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); |
@@ -651,10 +708,10 @@ |
/// 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 +746,16 @@ |
/// var goto = 0; |
/// var returnValue; |
/// var completer = new Completer(); |
- /// var handler = null; |
+ /// var handler = 8; // Outside try-blocks go to the rethrow label. |
/// 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 +780,9 @@ |
/// goto = 5; // finally handler for outer try. |
/// break; |
/// case 4: // catch handler for outer try. |
- /// e = result; |
+ /// handler = 5; // If the handler throws, do the finally .. |
+ /// next = [8] // ... and rethrow. |
+ /// e = storedError; |
/// handle(e); |
/// // Fall through to finally. |
/// case 5: // finally handler for outer try. |
@@ -728,14 +792,16 @@ |
/// 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,10 +814,12 @@ |
// [visitDartYield]. |
exitLabel = |
analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; |
+ rethrowLabel = newLabel("rethrow"); |
+ handlerLabels[node] = rethrowLabel; |
js.Statement body = node.body; |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
visitStatement(body); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
addExit(); |
List<js.SwitchClause> clauses = labelledParts.keys.map((label) { |
@@ -762,17 +830,13 @@ |
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){ |
+ $currentErrorName = $errorName; |
+ $gotoName = $handlerName; |
+ }""", {"body": helperBody}); |
List<js.VariableInitialization> inits = <js.VariableInitialization>[]; |
js.VariableInitialization makeInit(String name, js.Expression initValue) { |
@@ -785,17 +849,19 @@ |
inits.add(makeInit(completerName, new js.New(newCompleter, []))); |
} else if (isAsyncStar) { |
inits.add(makeInit(controllerName, |
- new js.Call(newController, [new js.VariableUse(helperName)]))); |
+ new js.Call(newController, [new js.VariableUse(bodyName)]))); |
} |
- if (hasTryBlocks) { |
- inits.add(makeInit(handlerName, new js.LiteralNull())); |
- } |
- if (hasJumpThroughFinally) { |
+ inits.add(makeInit(handlerName, js.number(rethrowLabel))); |
+ inits.add(makeInit(currentErrorName, null)); |
+ if (hasJumpThroughFinally || analysis.hasYield) { |
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. |
@@ -807,6 +873,7 @@ |
inits.addAll(new Iterable.generate(tempVarHighWaterMark, |
(int i) => makeInit(useTempVar(i + 1).name, null))); |
js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); |
+ // TODO(sigurdm): Explain the difference between these cases. |
if (isSyncStar) { |
return js.js(""" |
function (#params) { |
@@ -814,7 +881,7 @@ |
var $selfName = this; |
return new #newIterable(function () { |
#varDecl; |
- return function $helperName($resultName) { |
+ return function $bodyName() { |
while (true) |
#helperBody; |
}; |
@@ -831,7 +898,22 @@ |
return js.js(""" |
function (#params) { |
#varDecl; |
- function $helperName($resultName) { |
+ function $bodyName($errorCodeName, $resultName) { |
+ if (#hasYield) |
+ switch ($errorCodeName) { |
+ case #streamWasCanceled: |
+ $nextName = $nextWhenCanceledName; |
+ $gotoName = $nextName.pop(); |
+ break; |
+ case #errorCode: |
+ $currentErrorName = $resultName; |
+ $gotoName = $handlerName; |
+ } |
+ else |
+ if ($errorCodeName == #errorCode) { |
+ $currentErrorName = $resultName; |
+ $gotoName = $handlerName; |
+ } |
while (true) |
#helperBody; |
} |
@@ -838,8 +920,11 @@ |
#init; |
}""", { |
"params": node.params, |
+ "varDecl": varDecl, |
+ "streamWasCanceled": js.number(error_codes.STREAM_WAS_CANCELED), |
+ "errorCode": js.number(error_codes.ERROR), |
+ "hasYield": analysis.hasYield, |
"helperBody": helperBody, |
- "varDecl": varDecl, |
"init": generateInitializer() |
}); |
} |
@@ -888,7 +973,7 @@ |
} |
} |
- /// 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 +982,13 @@ |
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); |
+ return #asyncHelper(#value, |
+ $bodyName, |
+ ${isAsync ? completerName : controllerName}); |
""", { |
- "thenHelper": isAsync ? thenHelper : streamHelper, |
+ "asyncHelper": isAsync ? asyncHelper : streamHelper, |
"value": value, |
- "errorCallback": errorCallback |
})); |
}, store: false); |
beginLabel(afterAwait); |
@@ -1073,9 +1148,8 @@ |
// the jump. |
// The bottom of the stack is the label where the jump goes to. |
List<int> jumpStack = new List<int>(); |
- for (js.Node node in targetsAndTries.reversed) { |
- if (node is js.Try) { |
- assert(node.finallyPart != null); |
+ for (js.Node node in jumpTargets.reversed) { |
+ if (finallyLabels[node] != null) { |
jumpStack.add(finallyLabels[node]); |
} else if (node == target) { |
jumpStack.add(targetLabel); |
@@ -1120,9 +1194,9 @@ |
beginLabel(startLabel); |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
visitStatement(node.body); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
beginLabel(continueLabel); |
withExpression(node.condition, (js.Expression condition) { |
@@ -1191,9 +1265,9 @@ |
new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
}, store: false); |
} |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
visitStatement(node.body); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
if (node.update != null) { |
beginLabel(continueLabel); |
visitExpressionIgnoreResult(node.update); |
@@ -1294,9 +1368,9 @@ |
continueLabels[node] = continueLabel; |
beginLabel(continueLabel); |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
visitStatement(node.body); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
beginLabel(breakLabel); |
} |
@@ -1502,13 +1576,13 @@ |
} |
} |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
for (int i = 0; i < labels.length; i++) { |
beginLabel(labels[i]); |
visitStatement(node.cases[i].body); |
} |
beginLabel(after); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
} |
@override |
@@ -1523,12 +1597,30 @@ |
}, 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> _finalliesUpToAndEnclosingHandler() { |
+ List<int> result = new List<int>(); |
+ for (int i = jumpTargets.length - 1; i >= 0; i--) { |
+ js.Node node = jumpTargets[i]; |
+ int handlerLabel = handlerLabels[node]; |
+ if (handlerLabel != null) { |
+ result.add(handlerLabel); |
+ break; |
+ } |
+ int finallyLabel = finallyLabels[node]; |
+ if (finallyLabel != null) { |
+ result.add(finallyLabel); |
+ } |
+ } |
+ 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,31 +1634,48 @@ |
addStatement(new js.Try(body, catchPart, finallyPart)); |
return; |
} |
+ |
hasTryBlocks = true; |
- int handlerLabel = newLabel("catch"); |
+ int uncaughtLabel = newLabel("uncaught"); |
+ int handlerLabel = (node.catchPart == null) |
+ ? uncaughtLabel |
+ : newLabel("catch"); |
+ |
int finallyLabel = newLabel("finally"); |
int afterFinallyLabel = newLabel("after finally"); |
- errorHandlerLabels.add(handlerLabel); |
+ if (node.finallyPart != null) { |
+ finallyLabels[node.finallyPart] = finallyLabel; |
+ jumpTargets.add(node.finallyPart); |
+ } |
+ |
+ handlerLabels[node] = handlerLabel; |
+ jumpTargets.add(node); |
+ |
// Set the error handler here. It must be cleared on every path out; |
// normal and error exit. |
setErrorHandler(); |
- if (node.finallyPart != null) { |
- finallyLabels[node] = finallyLabel; |
- targetsAndTries.add(node); |
- } |
+ |
visitStatement(node.body); |
- errorHandlerLabels.removeLast(); |
- addStatement( |
- js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); |
+ |
+ js.Node last = jumpTargets.removeLast(); |
+ assert(last == node); |
+ |
if (node.finallyPart == null) { |
setErrorHandler(); |
addGoto(afterFinallyLabel); |
} else { |
- // The handler is set as the first thing in the finally block. |
+ // The handler is reset as the first thing in the finally block. |
+ addStatement( |
+ js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); |
addGoto(finallyLabel); |
} |
- beginLabel(handlerLabel); |
+ |
if (node.catchPart != null) { |
+ beginLabel(handlerLabel); |
+ // [uncaughtLabel] is the handler for the code in the catch-part. |
+ // It ensures that [nextName] is set up to run the right finally blocks. |
+ handlerLabels[node.catchPart] = uncaughtLabel; |
+ jumpTargets.add(node.catchPart); |
setErrorHandler(); |
// The catch declaration name can shadow outer variables, so a fresh name |
// is needed to avoid collisions. See Ecma 262, 3rd edition, |
@@ -1576,19 +1685,46 @@ |
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(currentErrorName))); |
visitStatement(node.catchPart.body); |
variableRenamings.removeLast(); |
+ if (node.finallyPart != null) { |
+ // The error has been caught, so after the finally, continue after the |
+ // try. |
+ addStatement(js.js.statement("$nextName = [#];", |
+ [js.number(afterFinallyLabel)])); |
+ addGoto(finallyLabel); |
+ } else { |
+ addGoto(afterFinallyLabel); |
+ } |
+ js.Node last = jumpTargets.removeLast(); |
+ assert(last == node.catchPart); |
} |
+ |
+ // The "uncaught"-handler tells the finally-block to continue with |
+ // the enclosing finally-blocks until the current catch-handler. |
+ beginLabel(uncaughtLabel); |
+ |
+ List<int> enclosingFinallies = _finalliesUpToAndEnclosingHandler(); |
+ |
+ int nextLabel = enclosingFinallies.removeLast(); |
+ if (enclosingFinallies.isNotEmpty) { |
+ // [enclosingFinallies] can be empty if there is no surrounding finally |
+ // blocks. Then [nextLabel] will be [rethrowLabel]. |
+ addStatement( |
+ js.js.statement("$nextName = #;", new js.ArrayInitializer( |
+ enclosingFinallies.map(js.number).toList()))); |
+ } |
+ if (node.finallyPart == null) { |
+ // The finally-block belonging to [node] will be visited because of |
+ // fallthrough. If it does not exist, add an explicit goto. |
+ addGoto(nextLabel); |
+ } |
if (node.finallyPart != null) { |
- targetsAndTries.removeLast(); |
- setErrorHandler(); |
- // This belongs to the catch-part, but is only needed if there is a |
- // `finally`. Therefore it is in this branch. |
- // This is needed even if there is no explicit catch-branch, because |
- // if an exception is raised the finally part has to be run. |
- addStatement( |
- js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); |
+ js.Node last = jumpTargets.removeLast(); |
+ assert(last == node.finallyPart); |
+ |
beginLabel(finallyLabel); |
setErrorHandler(); |
visitStatement(node.finallyPart); |
@@ -1658,9 +1794,9 @@ |
new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
}, store: false); |
} |
- targetsAndTries.add(node); |
+ jumpTargets.add(node); |
visitStatement(node.body); |
- targetsAndTries.removeLast(); |
+ jumpTargets.removeLast(); |
addGoto(continueLabel); |
beginLabel(afterLabel); |
} |
@@ -1683,10 +1819,10 @@ |
/// |
/// 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 them. |
+ /// Also [nextWhenCanceledName] is set up to contain the finally blocks that |
+ /// must be run in case the stream was canceled. |
void addAsyncYield(js.DartYield node, js.Expression expression) { |
assert(isAsyncStar); |
// Find all the finally blocks that should be performed if the stream is |
@@ -1693,26 +1829,18 @@ |
// canceled during the yield. |
// At the bottom of the stack is the return label. |
List<int> enclosingFinallyLabels = <int>[exitLabel]; |
- 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()); |
+ enclosingFinallyLabels.addAll(jumpTargets |
+ .where((js.Node node) => finallyLabels[node] != null) |
+ .map((js.Block node) => finallyLabels[node])); |
+ addStatement(js.js.statement("$nextWhenCanceledName = #", |
+ [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 +1881,8 @@ |
bool hasThis = false; |
+ bool hasYield = false; |
+ |
// The function currently being analyzed. |
js.Fun currentFunction; |
@@ -2143,6 +2273,7 @@ |
@override |
bool visitDartYield(js.DartYield node) { |
+ hasYield = true; |
visit(node.expression); |
return true; |
} |