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 |
new file mode 100644 |
index 0000000000000000000000000000000000000000..54f5bbed9faf0883967d314b2f0dd8063c873457 |
--- /dev/null |
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart |
@@ -0,0 +1,2153 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+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 "js.dart" as js; |
+ |
+import '../util/util.dart'; |
+import '../dart2jslib.dart' show DiagnosticListener; |
+ |
+import "../helpers/helpers.dart"; |
+ |
+/// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield |
+/// (with dart-like semantics) to an equivalent function without these. |
+/// await-for is not handled and must be rewritten before. (Currently handled |
+/// in ssa/builder.dart). |
+/// |
+/// When generating the input to this, special care must be taken that |
+/// parameters to sync* functions that are mutated in the body must be boxed. |
+/// (Currently handled in closure.dart). |
+/// |
+/// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. |
+class AsyncRewriter extends js.NodeVisitor { |
+ |
+ // Local variables are hoisted to the top of the function, so they are |
+ // collected here. |
+ List<js.VariableDeclaration> localVariables = |
+ new List<js.VariableDeclaration>(); |
+ |
+ 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>(); |
+ int exitLabel; |
+ |
+ // 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>(); |
+ |
+ List<int> continueStack = new List<int>(); |
+ List<int> breakStack = new List<int>(); |
+ List<int> returnStack = new List<int>(); |
+ |
+ List<Pair<String, String>> variableRenamings = |
+ new List<Pair<String, String>>(); |
+ |
+ PreTranslationAnalysis analysis; |
+ |
+ List<int> errorHandlerLabels = new List<int>(); |
+ |
+ final Function safeVariableName; |
+ |
+ // All the <x>Name variables are names of Javascript variables used in the |
+ // transformed code. |
+ |
+ /// Contains the result of an awaited expression, or a conditional or |
+ /// lazy boolean operator. |
+ /// |
+ /// For example a conditional expression is roughly translated like: |
+ /// [[cond ? a : b]] |
+ /// |
+ /// Becomes: |
+ /// |
+ /// while true { // outer while loop |
+ /// switch (goto) { // Simulates goto |
+ /// ... |
+ /// goto = [[cond]] ? thenLabel : elseLabel |
+ /// break; |
+ /// case thenLabel: |
+ /// result = [[a]]; |
+ /// goto = joinLabel; |
+ /// case elseLabel: |
+ /// result = [[b]]; |
+ /// case joinLabel: |
+ /// // Now the result of computing the condition is in result. |
+ /// .... |
+ /// } |
+ /// } |
+ /// |
+ /// It is a parameter to the [helperName] function, so that [thenHelper] and |
+ /// [streamHelper] can call [helperName] with the result of an awaited Future. |
+ String resultName; |
+ |
+ /// 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; |
+ |
+ /// The Completer that will finish an async function. |
+ /// |
+ /// Not used for sync* or async* functions. |
+ String completerName; |
+ |
+ /// The StreamController that controls an async* function. |
+ /// |
+ /// Not used for async and sync* functions |
+ String controllerName; |
+ |
+ /// Used to simulate a goto. |
+ /// |
+ /// To "goto" a label, the label is assigned to this |
+ /// variable, and break out of the switch to take another iteration in the |
+ /// while loop. See [addGoto] |
+ String gotoName; |
+ |
+ /// The label of the current error handler. |
+ String handlerName; |
+ |
+ /// Current caught error. |
+ String errorName; |
+ |
+ /// A stack of labels of finally blocks to visit, and the label to go to after |
+ /// the last. |
+ String nextName; |
+ |
+ /// The current returned value (a finally block may overwrite it). |
+ String returnValueName; |
+ |
+ /// The label of the outer loop. |
+ /// |
+ /// Used if there are untransformed loops containing break or continues to |
+ /// targets outside the loop. |
+ String outerLabelName; |
+ |
+ /// If javascript `this` is used, it is accessed via this variable, in the |
+ /// [helperName] function. |
+ String selfName; |
+ |
+ // These expressions are hooks for communicating with the runtime. |
+ |
+ /// The function called by an async function to simulate an await or return. |
+ /// |
+ /// 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). |
+ /// |
+ /// For a return it is called with: |
+ /// |
+ /// - The value to complete the completer with. |
+ /// - null |
+ /// - The [completerName] |
+ /// - null. |
+ final js.Expression thenHelper; |
+ |
+ /// The function called by an async* function to simulate an await, yield or |
+ /// yield*. |
+ /// |
+ /// For an await/yield/yield* it is called with: |
+ /// |
+ /// - 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). |
+ /// |
+ /// For a return it is called with: |
+ /// |
+ /// - null |
+ /// - null |
+ /// - The [controllerName] |
+ /// - null. |
+ final js.Expression streamHelper; |
+ |
+ /// Contructor used to initialize the [completerName] variable. |
+ /// |
+ /// Specific to async methods. |
+ final js.Expression newCompleter; |
+ |
+ /// Contructor used to initialize the [controllerName] variable. |
+ /// |
+ /// Specific to async* methods. |
+ final js.Expression newController; |
+ |
+ /// Contructor creating the Iterable for a sync* method. Called with |
+ /// [helperName]. |
+ final js.Expression newIterable; |
+ |
+ /// A JS Expression that creates a marker showing that iteration is over. |
+ /// |
+ /// Called without arguments. |
+ final js.Expression endOfIteration; |
+ |
+ /// A JS Expression that creates a marker indicating a 'yield' statement. |
+ /// |
+ /// Called with the value to yield. |
+ final js.Expression yieldExpression; |
+ |
+ /// A JS Expression that creates a marker indication a 'yield*' statement. |
+ /// |
+ /// Called with the stream to yield from. |
+ final js.Expression yieldStarExpression; |
+ |
+ final DiagnosticListener diagnosticListener; |
+ // For error reporting only. |
+ Spannable get spannable { |
+ return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; |
+ } |
+ |
+ Spannable _spannable; |
+ |
+ int _currentLabel = 0; |
+ |
+ // The highest temporary variable index currently in use. |
+ int currentTempVarIndex = 0; |
+ // The highest temporary variable index ever in use in this function. |
+ int tempVarHighWaterMark = 0; |
+ Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); |
+ |
+ js.AsyncModifier async; |
+ |
+ bool get isSync => async == const js.AsyncModifier.sync(); |
+ bool get isAsync => async == const js.AsyncModifier.async(); |
+ bool get isSyncStar => async == const js.AsyncModifier.syncStar(); |
+ bool get isAsyncStar => async == const js.AsyncModifier.asyncStar(); |
+ |
+ AsyncRewriter(this.diagnosticListener, |
+ spannable, |
+ {this.thenHelper, |
+ this.streamHelper, |
+ this.newCompleter, |
+ this.newController, |
+ this.endOfIteration, |
+ this.newIterable, |
+ this.yieldExpression, |
+ this.yieldStarExpression, |
+ this.safeVariableName}) |
+ : _spannable = spannable; |
+ |
+ /// Main entry point. |
+ /// Rewrites a sync*/async/async* function to an equivalent normal function. |
+ /// |
+ /// [spannable] can be passed to have a location for error messages. |
+ js.Fun rewrite(js.Fun node, [Spannable spannable]) { |
+ _spannable = spannable; |
+ |
+ async = node.asyncModifier; |
+ assert(!isSync); |
+ |
+ analysis = new PreTranslationAnalysis(unsupported); |
+ analysis.analyze(node); |
+ |
+ // To avoid name collisions with existing names, the fresh names are |
+ // generated after the analysis. |
+ resultName = freshName("result"); |
+ completerName = freshName("completer"); |
+ controllerName = freshName("controller"); |
+ helperName = freshName("helper"); |
+ gotoName = freshName("goto"); |
+ handlerName = freshName("handler"); |
+ errorName = freshName("error"); |
+ nextName = freshName("next"); |
+ returnValueName = freshName("returnValue"); |
+ outerLabelName = freshName("outer"); |
+ selfName = freshName("self"); |
+ |
+ return node.accept(this); |
+ } |
+ |
+ js.Expression get currentErrorHandler { |
+ return errorHandlerLabels.isEmpty |
+ ? new js.LiteralNull() |
+ : js.number(errorHandlerLabels.last); |
+ } |
+ |
+ int allocateTempVar() { |
+ assert(tempVarHighWaterMark >= currentTempVarIndex); |
+ currentTempVarIndex++; |
+ tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark); |
+ return currentTempVarIndex; |
+ } |
+ |
+ js.VariableUse useTempVar(int i) { |
+ return tempVarNames.putIfAbsent( |
+ i, () => new js.VariableUse(freshName("temp$i"))); |
+ } |
+ |
+ /// Generates a variable name with [safeVariableName] based on [originalName] |
+ /// with a suffix to guarantee it does not collide with already used names. |
+ String freshName(String originalName) { |
+ String safeName = safeVariableName(originalName); |
+ String result = safeName; |
+ int counter = 1; |
+ while (analysis.usedNames.contains(result)) { |
+ result = "$safeName$counter"; |
+ ++counter; |
+ } |
+ analysis.usedNames.add(result); |
+ return result; |
+ } |
+ |
+ /// All the pieces are collected in this map, to create a switch with a case |
+ /// for each label. |
+ /// |
+ /// The order is important, therefore the type is explicitly LinkedHashMap. |
+ LinkedHashMap<int, List<js.Statement>> labelledParts = |
+ new LinkedHashMap<int, List<js.Statement>>(); |
+ |
+ /// Description of each label for readability of the non-minified output. |
+ Map<int, String> labelComments = new Map<int, String>(); |
+ |
+ /// True if the function has any try blocks containing await. |
+ bool hasTryBlocks = false; |
+ |
+ /// True if any return, break or continue passes through a finally. |
+ bool hasJumpThroughFinally = false; |
+ |
+ /// True if the traversion currently is inside a loop or switch for which |
+ /// [shouldTransform] is false. |
+ bool insideUntranslatedBreakable = false; |
+ |
+ /// True if a label is used to break to an outer switch-statement. |
+ bool hasJumpThoughOuterLabel = false; |
+ |
+ /// True if there is a catch-handler protected by a finally with no enclosing |
+ /// catch-handlers. |
+ bool needsRethrow = false; |
+ |
+ /// Buffer for collecting translated statements belonging to the same switch |
+ /// case. |
+ List<js.Statement> currentStatementBuffer; |
+ |
+ // Labels will become cases in the big switch expression, and `goto label` |
+ // is expressed by assigning to the switch key [gotoName] and breaking out of |
+ // the switch. |
+ |
+ int newLabel([String comment]) { |
+ int result = _currentLabel; |
+ _currentLabel++; |
+ if (comment != null) { |
+ labelComments[result] = comment; |
+ } |
+ return result; |
+ } |
+ |
+ /// Begins outputting statements to a new buffer with label [label]. |
+ /// |
+ /// Each buffer ends up as its own case part in the big state-switch. |
+ void beginLabel(int label) { |
+ assert(!labelledParts.containsKey(label)); |
+ currentStatementBuffer = new List<js.Statement>(); |
+ labelledParts[label] = currentStatementBuffer; |
+ addStatement(new js.Comment(labelComments[label])); |
+ } |
+ |
+ /// Returns a statement assigning to the variable named [gotoName]. |
+ /// This should be followed by a break for the goto to be executed. Use |
+ /// [gotoWithBreak] or [addGoto] for this. |
+ js.Statement setGotoVariable(int label) { |
+ return new js.ExpressionStatement( |
+ new js.Assignment(new js.VariableUse(gotoName), js.number(label))); |
+ } |
+ |
+ /// Returns a block that has a goto to [label] including the break. |
+ /// |
+ /// Also inserts a comment describing the label if available. |
+ js.Block gotoAndBreak(int label) { |
+ List<js.Statement> statements = new List<js.Statement>(); |
+ if (labelComments.containsKey(label)) { |
+ statements.add(new js.Comment("goto ${labelComments[label]}")); |
+ } |
+ statements.add(setGotoVariable(label)); |
+ if (insideUntranslatedBreakable) { |
+ hasJumpThoughOuterLabel = true; |
+ statements.add(new js.Break(outerLabelName)); |
+ } else { |
+ statements.add(new js.Break(null)); |
+ } |
+ return new js.Block(statements); |
+ } |
+ |
+ /// Adds a goto to [label] including the break. |
+ /// |
+ /// Also inserts a comment describing the label if available. |
+ void addGoto(int label) { |
+ if (labelComments.containsKey(label)) { |
+ addStatement(new js.Comment("goto ${labelComments[label]}")); |
+ } |
+ addStatement(setGotoVariable(label)); |
+ |
+ addBreak(); |
+ } |
+ |
+ void addStatement(js.Statement node) { |
+ currentStatementBuffer.add(node); |
+ } |
+ |
+ void addExpressionStatement(js.Expression node) { |
+ addStatement(new js.ExpressionStatement(node)); |
+ } |
+ |
+ /// True if there is an await or yield in [node] or some subexpression. |
+ bool shouldTransform(js.Node node) { |
+ return analysis.hasAwaitOrYield.contains(node); |
+ } |
+ |
+ void unsupported(js.Node node) { |
+ throw new UnsupportedError( |
+ "Node $node cannot be transformed by the await-sync transformer"); |
+ } |
+ |
+ void unreachable(js.Node node) { |
+ diagnosticListener.internalError( |
+ spannable, "Internal error, trying to visit $node"); |
+ } |
+ |
+ visitStatement(js.Statement node) { |
+ node.accept(this); |
+ } |
+ |
+ /// Visits [node] to ensure its sideeffects are performed, but throwing away |
+ /// the result. |
+ /// |
+ /// If the return value of visiting [node] is an expression guaranteed to have |
+ /// no side effect, it is dropped. |
+ void visitExpressionIgnoreResult(js.Expression node) { |
+ js.Expression result = node.accept(this); |
+ if (!(result is js.Literal || result is js.VariableUse)) { |
+ addExpressionStatement(result); |
+ } |
+ } |
+ |
+ js.Expression visitExpression(js.Expression node) { |
+ return node.accept(this); |
+ } |
+ |
+ |
+ /// Calls [fn] with the value of evaluating [node1] and [node2]. |
+ /// |
+ /// Both nodes are evaluated in order. |
+ /// |
+ /// If node2 must be transformed (see [shouldTransform]), then the evaluation |
+ /// of node1 is added to the current statement-list and the result is stored |
+ /// in a temporary variable. The evaluation of node2 is then free to emit |
+ /// statements without affecting the result of node1. |
+ /// |
+ /// This is necessary, because await or yield expressions have to emit |
+ /// statements, and these statements could affect the value of node1. |
+ /// |
+ /// For example: |
+ /// |
+ /// - _storeIfNecessary(someLiteral) returns someLiteral. |
+ /// - _storeIfNecessary(someVariable) |
+ /// inserts: var tempX = someVariable |
+ /// returns: tempX |
+ /// where tempX is a fresh temporary variable. |
+ js.Expression _storeIfNecessary(js.Expression result) { |
+ // Note that RegExes, js.ArrayInitializer and js.ObjectInitializer are not |
+ // [js.Literal]s. |
+ if (result is js.Literal) return result; |
+ js.Expression tempVar = useTempVar(allocateTempVar()); |
+ addExpressionStatement(new js.Assignment(tempVar, result)); |
+ return tempVar; |
+ } |
+ |
+ withExpression(js.Expression node, fn(js.Expression result), {bool store}) { |
+ int oldTempVarIndex = currentTempVarIndex; |
+ js.Expression visited = visitExpression(node); |
+ if (store) { |
+ visited = _storeIfNecessary(visited); |
+ } |
+ var result = fn(visited); |
+ currentTempVarIndex = oldTempVarIndex; |
+ return result; |
+ } |
+ |
+ /// Calls [fn] with the value of evaluating [node1] and [node2]. |
+ /// |
+ /// If `shouldTransform(node2)` the first expression is stored in a temporary |
+ /// variable. |
+ /// |
+ /// This is because node1 must be evaluated before visiting node2, |
+ /// because the evaluation of an await or yield cannot be expressed as |
+ /// an expression, visiting node2 it will output statements that |
+ /// might have an influence on the value of node1. |
+ withExpression2(js.Expression node1, js.Expression node2, |
+ fn(js.Expression result1, js.Expression result2)) { |
+ int oldTempVarIndex = currentTempVarIndex; |
+ js.Expression r1 = visitExpression(node1); |
+ if (shouldTransform(node2)) { |
+ r1 = _storeIfNecessary(r1); |
+ } |
+ js.Expression r2 = visitExpression(node2); |
+ var result = fn(r1, r2); |
+ currentTempVarIndex = oldTempVarIndex; |
+ return result; |
+ } |
+ |
+ /// Calls [fn] with the value of evaluating all [nodes]. |
+ /// |
+ /// All results before the last node where `shouldTransform(node)` are stored |
+ /// in temporary variables. |
+ /// |
+ /// See more explanation on [withExpression2]. |
+ /// |
+ /// If any of the nodes are null, they are ignored, and a null is passed to |
+ /// [fn] in that place. |
+ withExpressions(List<js.Expression> nodes, fn(List<js.Expression> results)) { |
+ int oldTempVarIndex = currentTempVarIndex; |
+ // Find last occurence of a 'transform' expression in [nodes]. |
+ // All expressions before that must be stored in temp-vars. |
+ int lastTransformIndex = 0; |
+ for (int i = nodes.length - 1; i >= 0; --i) { |
+ if (nodes[i] == null) continue; |
+ if (shouldTransform(nodes[i])) { |
+ lastTransformIndex = i; |
+ break; |
+ } |
+ } |
+ List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) { |
+ return node == null ? null : _storeIfNecessary(visitExpression(node)); |
+ }).toList(); |
+ visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) { |
+ return node == null ? null : visitExpression(node); |
+ })); |
+ var result = fn(visited); |
+ currentTempVarIndex = oldTempVarIndex; |
+ return result; |
+ } |
+ |
+ /// Emits the return block that all returns should jump to (after going |
+ /// 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. |
+ /// (the result might have been stored in [returnValueName] by some finally |
+ /// block). |
+ /// |
+ /// Returning from a sync* function returns an [endOfIteration] marker. |
+ /// |
+ /// Returning from an async* function calls the [streamHelper] with an |
+ /// [endOfIteration] marker. |
+ void addExit() { |
+ if (analysis.hasExplicitReturns || isAsyncStar) { |
+ beginLabel(exitLabel); |
+ } else { |
+ addStatement(new js.Comment("implicit return")); |
+ } |
+ switch (async) { |
+ case const js.AsyncModifier.async(): |
+ String returnValue = |
+ analysis.hasExplicitReturns ? returnValueName : "null"; |
+ addStatement(js.js.statement( |
+ "return #thenHelper($returnValue, null, $completerName, null)", { |
+ "thenHelper": thenHelper |
+ })); |
+ 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)", { |
+ "streamHelper": streamHelper |
+ })); |
+ break; |
+ default: |
+ diagnosticListener.internalError( |
+ spannable, "Internal error, unexpected asyncmodifier $async"); |
+ } |
+ } |
+ |
+ /// The initial call to [thenHelper]/[streamHelper]. |
+ /// |
+ /// There is no value to await/yield, so the first argument is `null` and |
+ /// also the errorCallback is `null`. |
+ /// |
+ /// Returns the [Future]/[Stream] coming from [completerName]/ |
+ /// [controllerName]. |
+ js.Statement generateInitializer() { |
+ if (isAsync) { |
+ return js.js.statement( |
+ "return #thenHelper(null, $helperName, $completerName, null);", { |
+ "thenHelper": thenHelper |
+ }); |
+ } else if (isAsyncStar) { |
+ return js.js.statement( |
+ "return #streamHelper(null, $helperName, $controllerName, null);", { |
+ "streamHelper": streamHelper |
+ }); |
+ } else { |
+ throw diagnosticListener.internalError( |
+ spannable, "Unexpected asyncModifier: $async"); |
+ } |
+ } |
+ |
+ /// 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]. |
+ /// |
+ /// 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 |
+ /// and returns a future which is immediately returned by the translated |
+ /// await. |
+ /// Yields in async* are translated to a call to the [streamHelper]. 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`). |
+ /// |
+ /// Yield/yield* in a sync* function is translated to a return of the value, |
+ /// wrapped into a "IterationMarker" that signals the type (yield or yield*). |
+ /// Sync* functions are executed on demand (when the user requests a value) by |
+ /// the Iterable that knows how to handle these values. |
+ /// |
+ /// Simplified examples (not the exact translation, but intended to show the |
+ /// ideas): |
+ /// |
+ /// function (x, y, z) async { |
+ /// var p = await foo(); |
+ /// return bar(p); |
+ /// } |
+ /// |
+ /// Becomes: |
+ /// |
+ /// function(x, y, z) { |
+ /// var goto = 0, returnValue, completer = new Completer(), p; |
+ /// function helper(result) { |
+ /// while (true) { |
+ /// switch (goto) { |
+ /// case 0: |
+ /// goto = 1 // Remember where to continue when the future succeeds. |
+ /// return thenHelper(foo(), helper, completer, null); |
+ /// case 1: |
+ /// p = result; |
+ /// returnValue = bar(p); |
+ /// goto = 2; |
+ /// break; |
+ /// case 2: |
+ /// return thenHelper(returnValue, null, completer, null) |
+ /// } |
+ /// } |
+ /// return thenHelper(null, helper, completer, null); |
+ /// } |
+ /// } |
+ /// |
+ /// Try/catch is implemented by maintaining [handlerName] to contain the label |
+ /// of the current handler. The switch is nested inside a try/catch that will |
+ /// redirect the flow to the current handler. |
+ /// |
+ /// A `finally` clause is compiled similar to normal code, with the additional |
+ /// complexity that `finally` clauses need to know where to jump to after the |
+ /// clause is done. In the translation, each flow-path that enters a `finally` |
+ /// sets up the variable [nextName] with a stack of finally-blocks and a final |
+ /// jump-target (exit, catch, ...). |
+ /// |
+ /// function (x, y, z) async { |
+ /// try { |
+ /// try { |
+ /// throw "error"; |
+ /// } finally { |
+ /// finalize1(); |
+ /// } |
+ /// } catch (e) { |
+ /// handle(e); |
+ /// } finally { |
+ /// finalize2(); |
+ /// } |
+ /// } |
+ /// |
+ /// Translates into (besides the fact that structures not containing |
+ /// await/yield/yield* are left intact): |
+ /// |
+ /// function(x, y, z) { |
+ /// var goto = 0; |
+ /// var returnValue; |
+ /// var completer = new Completer(); |
+ /// var handler = null; |
+ /// var p; |
+ /// // The result can be either the result of an awaited future, or an |
+ /// // error if the future completed with an error. |
+ /// function helper(result) { |
+ /// while (true) { |
+ /// try { |
+ /// switch (goto) { |
+ /// case 0: |
+ /// handler = 4; // The outer catch-handler |
+ /// handler = 1; // The inner (implicit) catch-handler |
+ /// throw "error"; |
+ /// next = [3]; |
+ /// // After the finally (2) continue normally after the try. |
+ /// goto = 2; |
+ /// break; |
+ /// case 1: // (implicit) catch handler for inner try. |
+ /// next = [3]; // destination after the finally. |
+ /// // fall-though to the finally handler. |
+ /// case 2: // finally for inner try |
+ /// handler = 4; // catch-handler for outer try. |
+ /// finalize1(); |
+ /// goto = next.pop(); |
+ /// break; |
+ /// case 3: // exiting inner try. |
+ /// next = [6]; |
+ /// goto = 5; // finally handler for outer try. |
+ /// break; |
+ /// case 4: // catch handler for outer try. |
+ /// e = result; |
+ /// handle(e); |
+ /// // Fall through to finally. |
+ /// case 5: // finally handler for outer try. |
+ /// handler = null; |
+ /// finalize2(); |
+ /// goto = next.pop(); |
+ /// break; |
+ /// case 6: // Exiting outer try. |
+ /// case 7: // return |
+ /// return thenHelper(returnValue, null, completer, null); |
+ /// } |
+ /// } catch (error) { |
+ /// result = error; |
+ /// goto = handler; |
+ /// } |
+ /// } |
+ /// return thenHelper(null, helper, completer, null); |
+ /// } |
+ /// } |
+ /// |
+ @override |
+ js.Expression visitFun(js.Fun node) { |
+ if (isSync) return node; |
+ |
+ beginLabel(newLabel("Function start")); |
+ // AsyncStar needs a returnlabel for its handling of cancelation. See |
+ // [visitDartYield]. |
+ exitLabel = |
+ analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; |
+ js.Statement body = node.body; |
+ targetsAndTries.add(node); |
+ visitStatement(body); |
+ targetsAndTries.removeLast(); |
+ addExit(); |
+ |
+ List<js.SwitchClause> clauses = labelledParts.keys.map((label) { |
+ return new js.Case(js.number(label), new js.Block(labelledParts[label])); |
+ }).toList(); |
+ js.Statement helperBody = |
+ new js.Switch(new js.VariableUse(gotoName), clauses); |
+ 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}); |
+ } |
+ List<js.VariableInitialization> inits = <js.VariableInitialization>[]; |
+ |
+ js.VariableInitialization makeInit(String name, js.Expression initValue) { |
+ return new js.VariableInitialization( |
+ new js.VariableDeclaration(name), initValue); |
+ } |
+ |
+ inits.add(makeInit(gotoName, js.number(0))); |
+ if (isAsync) { |
+ 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())); |
+ } |
+ if (hasJumpThroughFinally) { |
+ inits.add(makeInit(nextName, null)); |
+ } |
+ if (analysis.hasExplicitReturns && isAsync) { |
+ inits.add(makeInit(returnValueName, null)); |
+ } |
+ if (analysis.hasThis && !isSyncStar) { |
+ // Sync* functions must remember `this` on the level of the outer |
+ // function. |
+ inits.add(makeInit(selfName, new js.This())); |
+ } |
+ inits.addAll(localVariables.map((js.VariableDeclaration decl) { |
+ return new js.VariableInitialization(decl, null); |
+ })); |
+ inits.addAll(new Iterable.generate(tempVarHighWaterMark, |
+ (int i) => makeInit(useTempVar(i + 1).name, null))); |
+ js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); |
+ if (isSyncStar) { |
+ return js.js(""" |
+ function (#params) { |
+ if (#needsThis) |
+ var $selfName = this; |
+ return new #newIterable(function () { |
+ #varDecl; |
+ return function $helperName($resultName) { |
+ while (true) |
+ #helperBody; |
+ }; |
+ }); |
+ } |
+ """, { |
+ "params": node.params, |
+ "needsThis": analysis.hasThis, |
+ "helperBody": helperBody, |
+ "varDecl": varDecl, |
+ "newIterable": newIterable |
+ }); |
+ } |
+ return js.js(""" |
+ function (#params) { |
+ #varDecl; |
+ function $helperName($resultName) { |
+ while (true) |
+ #helperBody; |
+ } |
+ #init; |
+ }""", { |
+ "params": node.params, |
+ "helperBody": helperBody, |
+ "varDecl": varDecl, |
+ "init": generateInitializer() |
+ }); |
+ } |
+ |
+ @override |
+ js.Expression visitAccess(js.PropertyAccess node) { |
+ return withExpression2(node.receiver, node.selector, |
+ (receiver, selector) => new js.PropertyAccess(receiver, selector)); |
+ } |
+ |
+ @override |
+ js.Expression visitArrayHole(js.ArrayHole node) { |
+ return node; |
+ } |
+ |
+ @override |
+ js.Expression visitArrayInitializer(js.ArrayInitializer node) { |
+ return withExpressions(node.elements, (elements) { |
+ return new js.ArrayInitializer(elements); |
+ }); |
+ } |
+ |
+ @override |
+ js.Expression visitAssignment(js.Assignment node) { |
+ if (!shouldTransform(node)) { |
+ return new js.Assignment.compound(visitExpression(node.leftHandSide), |
+ node.op, visitExpression(node.value)); |
+ } |
+ js.Expression leftHandSide = node.leftHandSide; |
+ if (leftHandSide is js.VariableUse) { |
+ return withExpression(node.value, (js.Expression value) { |
+ return new js.Assignment(leftHandSide, value); |
+ }, store: false); |
+ } else if (leftHandSide is js.PropertyAccess) { |
+ return withExpressions([ |
+ leftHandSide.receiver, |
+ leftHandSide.selector, |
+ node.value |
+ ], (evaluated) { |
+ return new js.Assignment.compound( |
+ new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, |
+ evaluated[2]); |
+ }); |
+ } else { |
+ throw "Unexpected assignment left hand side $leftHandSide"; |
+ } |
+ } |
+ |
+ /// An await is translated to a call to [thenHelper]/[streamHelper]. |
+ /// |
+ /// See the comments of [visitFun] for an example. |
+ @override |
+ js.Expression visitAwait(js.Await node) { |
+ assert(isAsync || isAsyncStar); |
+ 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); |
+ """, { |
+ "thenHelper": isAsync ? thenHelper : streamHelper, |
+ "value": value, |
+ "errorCallback": errorCallback |
+ })); |
+ }, store: false); |
+ beginLabel(afterAwait); |
+ return new js.VariableUse(resultName); |
+ } |
+ |
+ /// Checks if [node] is the variable named [resultName]. |
+ /// |
+ /// [resultName] is used to hold the result of a transformed computation |
+ /// for example the result of awaiting, or the result of a conditional or |
+ /// short-circuiting expression. |
+ /// If the subexpression of some transformed node already is transformed and |
+ /// visiting it returns [resultName], it is not redundantly assigned to itself |
+ /// again. |
+ bool isResult(js.Expression node) { |
+ return node is js.VariableUse && node.name == resultName; |
+ } |
+ |
+ @override |
+ js.Expression visitBinary(js.Binary node) { |
+ if (shouldTransform(node.right) && (node.op == "||" || node.op == "&&")) { |
+ int thenLabel = newLabel("then"); |
+ int joinLabel = newLabel("join"); |
+ withExpression(node.left, (js.Expression left) { |
+ js.Statement assignLeft = isResult(left) |
+ ? new js.Block.empty() |
+ : new js.ExpressionStatement( |
+ new js.Assignment(new js.VariableUse(resultName), left)); |
+ if (node.op == "||") { |
+ addStatement(new js.If(left, gotoAndBreak(thenLabel), assignLeft)); |
+ } else { |
+ assert(node.op == "&&"); |
+ addStatement(new js.If(left, assignLeft, gotoAndBreak(thenLabel))); |
+ } |
+ }, store: true); |
+ addGoto(joinLabel); |
+ beginLabel(thenLabel); |
+ withExpression(node.right, (js.Expression value) { |
+ if (!isResult(value)) { |
+ addExpressionStatement( |
+ new js.Assignment(new js.VariableUse(resultName), value)); |
+ } |
+ }, store: false); |
+ beginLabel(joinLabel); |
+ return new js.VariableUse(resultName); |
+ } |
+ |
+ return withExpression2(node.left, node.right, |
+ (left, right) => new js.Binary(node.op, left, right)); |
+ } |
+ |
+ @override |
+ js.Expression visitBlob(js.Blob node) { |
+ return node; |
+ } |
+ |
+ @override |
+ void visitBlock(js.Block node) { |
+ for (js.Statement statement in node.statements) { |
+ visitStatement(statement); |
+ } |
+ } |
+ |
+ @override |
+ void visitBreak(js.Break node) { |
+ js.Node target = analysis.targets[node]; |
+ if (!shouldTransform(target)) { |
+ addStatement(node); |
+ return; |
+ } |
+ translateJump(target, breakLabels[target]); |
+ } |
+ |
+ @override |
+ js.Expression visitCall(js.Call node) { |
+ bool storeTarget = node.arguments.any(shouldTransform); |
+ return withExpression(node.target, (target) { |
+ return withExpressions(node.arguments, (List<js.Expression> arguments) { |
+ return new js.Call(target, arguments); |
+ }); |
+ }, store: storeTarget); |
+ } |
+ |
+ @override |
+ void visitCase(js.Case node) { |
+ return unreachable(node); |
+ } |
+ |
+ @override |
+ void visitCatch(js.Catch node) { |
+ return unreachable(node); |
+ } |
+ |
+ @override |
+ void visitComment(js.Comment node) { |
+ addStatement(node); |
+ } |
+ |
+ @override |
+ js.Expression visitConditional(js.Conditional node) { |
+ if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { |
+ return withExpression(node.condition, (js.Expression condition) { |
+ return new js.Conditional(condition, node.then, node.otherwise); |
+ }); |
+ } |
+ int thenLabel = newLabel("then"); |
+ int joinLabel = newLabel("join"); |
+ int elseLabel = newLabel("else"); |
+ withExpression(node.condition, (js.Expression condition) { |
+ addExpressionStatement(new js.Assignment(new js.VariableUse(gotoName), |
+ new js.Conditional( |
+ condition, js.number(thenLabel), js.number(elseLabel)))); |
+ }, store: false); |
+ addBreak(); |
+ beginLabel(thenLabel); |
+ withExpression(node.then, (js.Expression value) { |
+ if (!isResult(value)) { |
+ addExpressionStatement( |
+ new js.Assignment(new js.VariableUse(resultName), value)); |
+ } |
+ }, store: false); |
+ addGoto(joinLabel); |
+ beginLabel(elseLabel); |
+ withExpression(node.otherwise, (js.Expression value) { |
+ if (!isResult(value)) { |
+ addExpressionStatement( |
+ new js.Assignment(new js.VariableUse(resultName), value)); |
+ } |
+ }, store: false); |
+ beginLabel(joinLabel); |
+ return new js.VariableUse(resultName); |
+ } |
+ |
+ @override |
+ void visitContinue(js.Continue node) { |
+ js.Node target = analysis.targets[node]; |
+ if (!shouldTransform(target)) { |
+ addStatement(node); |
+ return; |
+ } |
+ translateJump(target, continueLabels[target]); |
+ } |
+ |
+ /// Emits a break statement that exits the big switch statement. |
+ void addBreak() { |
+ if (insideUntranslatedBreakable) { |
+ hasJumpThoughOuterLabel = true; |
+ addStatement(new js.Break(outerLabelName)); |
+ } else { |
+ addStatement(new js.Break(null)); |
+ } |
+ } |
+ |
+ /// Common code for handling break, continue, return. |
+ /// |
+ /// It is necessary to run all nesting finally-handlers between the jump and |
+ /// the target. For that [nextName] is used as a stack of places to go. |
+ /// |
+ /// See also [visitFun]. |
+ void translateJump(js.Node target, int targetLabel) { |
+ // Compute a stack of all the 'finally' nodes that must be visited before |
+ // 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); |
+ jumpStack.add(finallyLabels[node]); |
+ } else if (node == target) { |
+ jumpStack.add(targetLabel); |
+ break; |
+ } |
+ // Ignore other nodes. |
+ } |
+ jumpStack = jumpStack.reversed.toList(); |
+ // As the program jumps directly to the top of the stack, it is taken off |
+ // now. |
+ int firstTarget = jumpStack.removeLast(); |
+ if (jumpStack.isNotEmpty) { |
+ hasJumpThroughFinally = true; |
+ js.Expression jsJumpStack = new js.ArrayInitializer( |
+ jumpStack.map((int label) => js.number(label)).toList()); |
+ addStatement(js.js.statement("$nextName = #", [jsJumpStack])); |
+ } |
+ addGoto(firstTarget); |
+ } |
+ |
+ @override |
+ void visitDefault(js.Default node) => unreachable(node); |
+ |
+ @override |
+ void visitDo(js.Do node) { |
+ if (!shouldTransform(node)) { |
+ bool oldInsideUntranslatedBreakable = insideUntranslatedBreakable; |
+ insideUntranslatedBreakable = true; |
+ withExpression(node.condition, (js.Expression condition) { |
+ addStatement(new js.Do(translateInBlock(node.body), condition)); |
+ }, store: false); |
+ insideUntranslatedBreakable = oldInsideUntranslatedBreakable; |
+ return; |
+ } |
+ int startLabel = newLabel("do body"); |
+ |
+ int continueLabel = newLabel("do condition"); |
+ continueLabels[node] = continueLabel; |
+ |
+ int afterLabel = newLabel("after do"); |
+ breakLabels[node] = afterLabel; |
+ |
+ beginLabel(startLabel); |
+ |
+ targetsAndTries.add(node); |
+ visitStatement(node.body); |
+ targetsAndTries.removeLast(); |
+ |
+ beginLabel(continueLabel); |
+ withExpression(node.condition, (js.Expression condition) { |
+ addStatement(new js.If.noElse(condition, gotoAndBreak(startLabel))); |
+ }, store: false); |
+ beginLabel(afterLabel); |
+ } |
+ |
+ @override |
+ void visitEmptyStatement(js.EmptyStatement node) { |
+ addStatement(node); |
+ } |
+ |
+ void visitExpressionInStatementContext(js.Expression node) { |
+ if (node is js.VariableDeclarationList) { |
+ // Treat js.VariableDeclarationList as a statement. |
+ visitVariableDeclarationList(node); |
+ } else { |
+ visitExpressionIgnoreResult(node); |
+ } |
+ } |
+ |
+ @override |
+ void visitExpressionStatement(js.ExpressionStatement node) { |
+ visitExpressionInStatementContext(node.expression); |
+ } |
+ |
+ @override |
+ void visitFor(js.For node) { |
+ if (!shouldTransform(node)) { |
+ bool oldInsideUntranslated = insideUntranslatedBreakable; |
+ insideUntranslatedBreakable = true; |
+ // Note that node.init, node.condition, node.update all can be null, but |
+ // withExpressions handles that. |
+ withExpressions([ |
+ node.init, |
+ node.condition, |
+ node.update |
+ ], (List<js.Expression> transformed) { |
+ addStatement(new js.For(transformed[0], transformed[1], transformed[2], |
+ translateInBlock(node.body))); |
+ }); |
+ insideUntranslatedBreakable = oldInsideUntranslated; |
+ return; |
+ } |
+ |
+ if (node.init != null) { |
+ visitExpressionInStatementContext(node.init); |
+ } |
+ int startLabel = newLabel("for condition"); |
+ // If there is no update, continuing the loop is the same as going to the |
+ // start. |
+ int continueLabel = |
+ (node.update == null) ? startLabel : newLabel("for update"); |
+ continueLabels[node] = continueLabel; |
+ int afterLabel = newLabel("after for"); |
+ breakLabels[node] = afterLabel; |
+ beginLabel(startLabel); |
+ js.Expression condition = node.condition; |
+ if (condition == null || |
+ (condition is js.LiteralBool && condition.value == true)) { |
+ addStatement(new js.Comment("trivial condition")); |
+ } else { |
+ withExpression(condition, (js.Expression condition) { |
+ addStatement(new js.If.noElse( |
+ new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
+ }, store: false); |
+ } |
+ targetsAndTries.add(node); |
+ visitStatement(node.body); |
+ targetsAndTries.removeLast(); |
+ if (node.update != null) { |
+ beginLabel(continueLabel); |
+ visitExpressionIgnoreResult(node.update); |
+ } |
+ addGoto(startLabel); |
+ beginLabel(afterLabel); |
+ } |
+ |
+ @override |
+ void visitForIn(js.ForIn node) { |
+ // The dart output currently never uses for-in loops. |
+ throw "Javascript for-in not implemented yet in the await transformation"; |
+ } |
+ |
+ @override |
+ void visitFunctionDeclaration(js.FunctionDeclaration node) { |
+ unsupported(node); |
+ } |
+ |
+ // Only used for code where `!shouldTransform(node)`. |
+ js.Block translateInBlock(js.Statement node) { |
+ assert(!shouldTransform(node)); |
+ List<js.Statement> oldBuffer = currentStatementBuffer; |
+ currentStatementBuffer = new List(); |
+ List<js.Statement> resultBuffer = currentStatementBuffer; |
+ visitStatement(node); |
+ currentStatementBuffer = oldBuffer; |
+ return new js.Block(resultBuffer); |
+ } |
+ |
+ @override |
+ void visitIf(js.If node) { |
+ if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { |
+ withExpression(node.condition, (js.Expression condition) { |
+ addStatement(new js.If(condition, translateInBlock(node.then), |
+ translateInBlock(node.otherwise))); |
+ }, store: false); |
+ return; |
+ } |
+ int thenLabel = newLabel("then"); |
+ int joinLabel = newLabel("join"); |
+ int elseLabel = |
+ node.otherwise is js.EmptyStatement ? joinLabel : newLabel("else"); |
+ |
+ withExpression(node.condition, (js.Expression condition) { |
+ addExpressionStatement( |
+ new js.Assignment( |
+ new js.VariableUse(gotoName), |
+ new js.Conditional( |
+ condition, |
+ js.number(thenLabel), |
+ js.number(elseLabel)))); |
+ }, store: false); |
+ addBreak(); |
+ beginLabel(thenLabel); |
+ visitStatement(node.then); |
+ if (node.otherwise is! js.EmptyStatement) { |
+ addGoto(joinLabel); |
+ beginLabel(elseLabel); |
+ visitStatement(node.otherwise); |
+ } |
+ beginLabel(joinLabel); |
+ } |
+ |
+ @override |
+ visitInterpolatedExpression(js.InterpolatedExpression node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ visitInterpolatedLiteral(js.InterpolatedLiteral node) => unsupported(node); |
+ |
+ @override |
+ visitInterpolatedParameter(js.InterpolatedParameter node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ visitInterpolatedSelector(js.InterpolatedSelector node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ visitInterpolatedStatement(js.InterpolatedStatement node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ void visitLabeledStatement(js.LabeledStatement node) { |
+ if (!shouldTransform(node)) { |
+ addStatement( |
+ new js.LabeledStatement(node.label, translateInBlock(node.body))); |
+ return; |
+ } |
+ int breakLabel = newLabel("break ${node.label}"); |
+ int continueLabel = newLabel("continue ${node.label}"); |
+ breakLabels[node] = breakLabel; |
+ continueLabels[node] = continueLabel; |
+ |
+ beginLabel(continueLabel); |
+ targetsAndTries.add(node); |
+ visitStatement(node.body); |
+ targetsAndTries.removeLast(); |
+ beginLabel(breakLabel); |
+ } |
+ |
+ @override |
+ js.Expression visitLiteralBool(js.LiteralBool node) => node; |
+ |
+ @override |
+ visitLiteralExpression(js.LiteralExpression node) => unsupported(node); |
+ |
+ @override |
+ js.Expression visitLiteralNull(js.LiteralNull node) => node; |
+ |
+ @override |
+ js.Expression visitLiteralNumber(js.LiteralNumber node) => node; |
+ |
+ @override |
+ visitLiteralStatement(js.LiteralStatement node) => unsupported(node); |
+ |
+ @override |
+ js.Expression visitLiteralString(js.LiteralString node) => node; |
+ |
+ @override |
+ visitNamedFunction(js.NamedFunction node) { |
+ unsupported(node); |
+ } |
+ |
+ @override |
+ js.Expression visitNew(js.New node) { |
+ bool storeTarget = node.arguments.any(shouldTransform); |
+ return withExpression(node.target, (target) { |
+ return withExpressions(node.arguments, (List<js.Expression> arguments) { |
+ return new js.New(target, arguments); |
+ }); |
+ }, store: storeTarget); |
+ } |
+ |
+ @override |
+ js.Expression visitObjectInitializer(js.ObjectInitializer node) { |
+ return withExpressions( |
+ node.properties.map((js.Property property) => property.value).toList(), |
+ (List<js.Node> values) { |
+ List<js.Property> properties = new List.generate(values.length, (int i) { |
+ return new js.Property(node.properties[i].name, values[i]); |
+ }); |
+ return new js.ObjectInitializer(properties); |
+ }); |
+ } |
+ |
+ @override |
+ visitParameter(js.Parameter node) => unreachable(node); |
+ |
+ @override |
+ js.Expression visitPostfix(js.Postfix node) { |
+ if (node.op == "++" || node.op == "--") { |
+ js.Expression argument = node.argument; |
+ if (argument is js.VariableUse) { |
+ return new js.Postfix(node.op, argument); |
+ } else if (argument is js.PropertyAccess) { |
+ return withExpression2(argument.receiver, argument.selector, |
+ (receiver, selector) { |
+ return new js.Postfix( |
+ node.op, new js.PropertyAccess(receiver, selector)); |
+ }); |
+ } else { |
+ throw "Unexpected postfix ${node.op} " |
+ "operator argument ${node.argument}"; |
+ } |
+ } |
+ return withExpression(node.argument, |
+ (js.Expression argument) => new js.Postfix(node.op, argument), |
+ store: false); |
+ } |
+ |
+ @override |
+ js.Expression visitPrefix(js.Prefix node) { |
+ if (node.op == "++" || node.op == "--") { |
+ js.Expression argument = node.argument; |
+ if (argument is js.VariableUse) { |
+ return new js.Prefix(node.op, argument); |
+ } else if (argument is js.PropertyAccess) { |
+ return withExpression2(argument.receiver, argument.selector, |
+ (receiver, selector) { |
+ return new js.Prefix( |
+ node.op, new js.PropertyAccess(receiver, selector)); |
+ }); |
+ } else { |
+ throw "Unexpected prefix ${node.op} operator " |
+ "argument ${node.argument}"; |
+ } |
+ } |
+ return withExpression(node.argument, |
+ (js.Expression argument) => new js.Prefix(node.op, argument), |
+ store: false); |
+ } |
+ |
+ @override |
+ visitProgram(js.Program node) => unsupported(node); |
+ |
+ @override |
+ js.Property visitProperty(js.Property node) { |
+ return withExpression( |
+ node.value, (js.Expression value) => new js.Property(node.name, value), |
+ store: false); |
+ } |
+ |
+ @override |
+ js.Expression visitRegExpLiteral(js.RegExpLiteral node) => node; |
+ |
+ @override |
+ void visitReturn(js.Return node) { |
+ assert(node.value == null || !isSyncStar && !isAsyncStar); |
+ js.Node target = analysis.targets[node]; |
+ if (node.value != null) { |
+ withExpression(node.value, (js.Expression value) { |
+ addStatement(js.js.statement("$returnValueName = #", [value])); |
+ }, store: false); |
+ } |
+ translateJump(target, exitLabel); |
+ } |
+ |
+ @override |
+ void visitSwitch(js.Switch node) { |
+ if (!node.cases.any(shouldTransform)) { |
+ // If only the key has an await, translation can be simplified. |
+ bool oldInsideUntranslated = insideUntranslatedBreakable; |
+ insideUntranslatedBreakable = true; |
+ withExpression(node.key, (js.Expression key) { |
+ List<js.SwitchClause> cases = node.cases.map((js.SwitchClause clause) { |
+ if (clause is js.Case) { |
+ return new js.Case( |
+ clause.expression, translateInBlock(clause.body)); |
+ } else if (clause is js.Default) { |
+ return new js.Default(translateInBlock(clause.body)); |
+ } |
+ }).toList(); |
+ addStatement(new js.Switch(key, cases)); |
+ }, store: false); |
+ insideUntranslatedBreakable = oldInsideUntranslated; |
+ return; |
+ } |
+ int before = newLabel("switch"); |
+ int after = newLabel("after switch"); |
+ breakLabels[node] = after; |
+ |
+ beginLabel(before); |
+ List<int> labels = new List<int>(node.cases.length); |
+ |
+ bool anyCaseExpressionTransformed = node.cases.any( |
+ (js.SwitchClause x) => x is js.Case && shouldTransform(x.expression)); |
+ if (anyCaseExpressionTransformed) { |
+ int defaultIndex = null; // Null means no default was found. |
+ // If there is an await in one of the keys, a chain of ifs has to be used. |
+ |
+ withExpression(node.key, (js.Expression key) { |
+ int i = 0; |
+ for (js.SwitchClause clause in node.cases) { |
+ if (clause is js.Default) { |
+ // The goto for the default case is added after all non-default |
+ // clauses have been handled. |
+ defaultIndex = i; |
+ labels[i] = newLabel("default"); |
+ continue; |
+ } else if (clause is js.Case) { |
+ labels[i] = newLabel("case"); |
+ withExpression(clause.expression, (expression) { |
+ addStatement(new js.If.noElse( |
+ new js.Binary("===", key, expression), |
+ gotoAndBreak(labels[i]))); |
+ }, store: false); |
+ } |
+ i++; |
+ } |
+ }, store: true); |
+ |
+ if (defaultIndex == null) { |
+ addGoto(after); |
+ } else { |
+ addGoto(labels[defaultIndex]); |
+ } |
+ } else { |
+ bool hasDefault = false; |
+ int i = 0; |
+ List<js.SwitchClause> clauses = new List<js.SwitchClause>(); |
+ for (js.SwitchClause clause in node.cases) { |
+ if (clause is js.Case) { |
+ labels[i] = newLabel("case"); |
+ clauses.add(new js.Case(clause.expression, gotoAndBreak(labels[i]))); |
+ } else if (i is js.Default) { |
+ labels[i] = newLabel("default"); |
+ clauses.add(new js.Default(gotoAndBreak(labels[i]))); |
+ hasDefault = true; |
+ } else { |
+ diagnosticListener.internalError( |
+ spannable, "Unknown clause type $clause"); |
+ } |
+ i++; |
+ } |
+ withExpression(node.key, (js.Expression key) { |
+ addStatement(new js.Switch(key, clauses)); |
+ }, store: false); |
+ if (!hasDefault) { |
+ addGoto(after); |
+ } |
+ } |
+ |
+ targetsAndTries.add(node); |
+ for (int i = 0; i < labels.length; i++) { |
+ beginLabel(labels[i]); |
+ visitStatement(node.cases[i].body); |
+ } |
+ beginLabel(after); |
+ targetsAndTries.removeLast(); |
+ } |
+ |
+ @override |
+ js.Expression visitThis(js.This node) { |
+ return new js.VariableUse(selfName); |
+ } |
+ |
+ @override |
+ void visitThrow(js.Throw node) { |
+ withExpression(node.expression, (js.Expression expression) { |
+ addStatement(new js.Throw(expression)); |
+ }, store: false); |
+ } |
+ |
+ setErrorHandler() { |
+ addExpressionStatement(new js.Assignment( |
+ new js.VariableUse(handlerName), currentErrorHandler)); |
+ } |
+ |
+ @override |
+ void visitTry(js.Try node) { |
+ if (!shouldTransform(node)) { |
+ js.Block body = translateInBlock(node.body); |
+ js.Catch catchPart = (node.catchPart == null) |
+ ? null |
+ : new js.Catch(node.catchPart.declaration, |
+ translateInBlock(node.catchPart.body)); |
+ js.Block finallyPart = (node.finallyPart == null) |
+ ? null |
+ : translateInBlock(node.finallyPart); |
+ addStatement(new js.Try(body, catchPart, finallyPart)); |
+ return; |
+ } |
+ hasTryBlocks = true; |
+ int handlerLabel = newLabel("catch"); |
+ int finallyLabel = newLabel("finally"); |
+ int afterFinallyLabel = newLabel("after finally"); |
+ errorHandlerLabels.add(handlerLabel); |
+ // 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)])); |
+ if (node.finallyPart == null) { |
+ setErrorHandler(); |
+ addGoto(afterFinallyLabel); |
+ } else { |
+ // The handler is set as the first thing in the finally block. |
+ addGoto(finallyLabel); |
+ } |
+ beginLabel(handlerLabel); |
+ if (node.catchPart != null) { |
+ setErrorHandler(); |
+ // 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. |
+ String errorRename = freshName(node.catchPart.declaration.name); |
+ localVariables.add(new js.VariableDeclaration(errorRename)); |
+ variableRenamings |
+ .add(new Pair(node.catchPart.declaration.name, errorRename)); |
+ addExpressionStatement(new js.Assignment( |
+ new js.VariableUse(errorRename), new js.VariableUse(resultName))); |
+ visitStatement(node.catchPart.body); |
+ variableRenamings.removeLast(); |
+ } |
+ 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)])); |
+ beginLabel(finallyLabel); |
+ setErrorHandler(); |
+ visitStatement(node.finallyPart); |
+ addStatement(new js.Comment("// goto the next finally handler")); |
+ addStatement(js.js.statement("$gotoName = $nextName.pop();")); |
+ addBreak(); |
+ } |
+ beginLabel(afterFinallyLabel); |
+ } |
+ |
+ @override |
+ visitVariableDeclaration(js.VariableDeclaration node) { |
+ unreachable(node); |
+ } |
+ |
+ @override |
+ void visitVariableDeclarationList(js.VariableDeclarationList node) { |
+ // Declaration of local variables is hoisted outside the helper but the |
+ // initialization is done here. |
+ for (js.VariableInitialization initialization in node.declarations) { |
+ js.VariableDeclaration declaration = initialization.declaration; |
+ localVariables.add(declaration); |
+ if (initialization.value != null) { |
+ withExpression(initialization.value, (js.Expression value) { |
+ addStatement(new js.ExpressionStatement( |
+ new js.Assignment(declaration, value))); |
+ }, store: false); |
+ } |
+ } |
+ } |
+ |
+ @override |
+ void visitVariableInitialization(js.VariableInitialization node) { |
+ unreachable(node); |
+ } |
+ |
+ @override |
+ js.Expression visitVariableUse(js.VariableUse node) { |
+ Pair<String, String> renaming = variableRenamings.lastWhere( |
+ (Pair renaming) => renaming.a == node.name, orElse: () => null); |
+ if (renaming == null) return node; |
+ return new js.VariableUse(renaming.b); |
+ } |
+ |
+ @override |
+ void visitWhile(js.While node) { |
+ if (!shouldTransform(node)) { |
+ bool oldInsideUntranslated = insideUntranslatedBreakable; |
+ insideUntranslatedBreakable = true; |
+ withExpression(node.condition, (js.Expression condition) { |
+ addStatement(new js.While(condition, translateInBlock(node.body))); |
+ }, store: false); |
+ insideUntranslatedBreakable = oldInsideUntranslated; |
+ return; |
+ } |
+ int continueLabel = newLabel("while condition"); |
+ continueLabels[node] = continueLabel; |
+ beginLabel(continueLabel); |
+ |
+ int afterLabel = newLabel("after while"); |
+ breakLabels[node] = afterLabel; |
+ js.Expression condition = node.condition; |
+ // If the condition is `true`, a test is not needed. |
+ if (!(condition is js.LiteralBool && condition.value == true)) { |
+ withExpression(node.condition, (js.Expression condition) { |
+ addStatement(new js.If.noElse( |
+ new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
+ }, store: false); |
+ } |
+ targetsAndTries.add(node); |
+ visitStatement(node.body); |
+ targetsAndTries.removeLast(); |
+ addGoto(continueLabel); |
+ beginLabel(afterLabel); |
+ } |
+ |
+ /// Translates a yield/yield* in an sync*. |
+ /// |
+ /// `yield` in a sync* function just returns [value]. |
+ /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. |
+ void addSyncYield(js.DartYield node, js.Expression expression) { |
+ assert(isSyncStar); |
+ if (node.hasStar) { |
+ addStatement( |
+ new js.Return(new js.Call(yieldStarExpression, [expression]))); |
+ } else { |
+ addStatement(new js.Return(expression)); |
+ } |
+ } |
+ |
+ /// Translates a yield/yield* in an async* function. |
+ /// |
+ /// 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. |
+ void addAsyncYield(js.DartYield node, js.Expression expression) { |
+ assert(isAsyncStar); |
+ // Find all the finally blocks that should be performed if the stream is |
+ // 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()); |
+ addStatement(js.js.statement(""" |
+ return #streamHelper(#yieldExpression(#expression), |
+ $helperName, $controllerName, function () { |
+ if (#notEmptyFinallyList) |
+ $nextName = #finallyList; |
+ $gotoName = #destinationOnCancel; |
+ $helperName(); |
+ });""", { |
+ "streamHelper": streamHelper, |
+ "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, |
+ "expression": expression, |
+ "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty, |
+ "finallyList": finallyListInitializer, |
+ "destinationOnCancel": js.number(destinationOnCancel) |
+ })); |
+ } |
+ |
+ @override |
+ void visitDartYield(js.DartYield node) { |
+ assert(isSyncStar || isAsyncStar); |
+ int label = newLabel("after yield"); |
+ // Don't do a break here for the goto, but instead a return in either |
+ // addSynYield or addAsyncYield. |
+ withExpression(node.expression, (js.Expression expression) { |
+ addStatement(setGotoVariable(label)); |
+ if (isSyncStar) { |
+ addSyncYield(node, expression); |
+ } else { |
+ addAsyncYield(node, expression); |
+ } |
+ }, store: false); |
+ beginLabel(label); |
+ } |
+} |
+ |
+/// Finds out |
+/// |
+/// - which expressions have yield or await nested in them. |
+/// - targets of jumps |
+/// - a set of used names. |
+/// - if any [This]-expressions are used. |
+class PreTranslationAnalysis extends js.NodeVisitor<bool> { |
+ Set<js.Node> hasAwaitOrYield = new Set<js.Node>(); |
+ |
+ Map<js.Node, js.Node> targets = new Map<js.Node, js.Node>(); |
+ List<js.Node> loopsAndSwitches = new List<js.Node>(); |
+ List<js.LabeledStatement> labelledStatements = |
+ new List<js.LabeledStatement>(); |
+ Set<String> usedNames = new Set<String>(); |
+ |
+ bool hasExplicitReturns = false; |
+ |
+ bool hasThis = false; |
+ |
+ // The function currently being analyzed. |
+ js.Fun currentFunction; |
+ |
+ // For error messages. |
+ final Function unsupported; |
+ |
+ PreTranslationAnalysis(void this.unsupported(js.Node node)); |
+ |
+ bool visit(js.Node node) { |
+ bool containsAwait = node.accept(this); |
+ if (containsAwait) { |
+ hasAwaitOrYield.add(node); |
+ } |
+ return containsAwait; |
+ } |
+ |
+ analyze(js.Fun node) { |
+ currentFunction = node; |
+ node.params.forEach(visit); |
+ visit(node.body); |
+ } |
+ |
+ @override |
+ bool visitAccess(js.PropertyAccess node) { |
+ bool receiver = visit(node.receiver); |
+ bool selector = visit(node.selector); |
+ return receiver || selector; |
+ } |
+ |
+ @override |
+ bool visitArrayHole(js.ArrayHole node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitArrayInitializer(js.ArrayInitializer node) { |
+ bool containsAwait = false; |
+ for (js.Expression element in node.elements) { |
+ if (visit(element)) containsAwait = true; |
+ } |
+ return containsAwait; |
+ } |
+ |
+ @override |
+ bool visitAssignment(js.Assignment node) { |
+ bool leftHandSide = visit(node.leftHandSide); |
+ bool value = (node.value == null) ? false : visit(node.value); |
+ return leftHandSide || value; |
+ } |
+ |
+ @override |
+ bool visitAwait(js.Await node) { |
+ visit(node.expression); |
+ return true; |
+ } |
+ |
+ @override |
+ bool visitBinary(js.Binary node) { |
+ bool left = visit(node.left); |
+ bool right = visit(node.right); |
+ return left || right; |
+ } |
+ |
+ @override |
+ bool visitBlob(js.Blob node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitBlock(js.Block node) { |
+ bool containsAwait = false; |
+ for (js.Statement statement in node.statements) { |
+ if (visit(statement)) containsAwait = true; |
+ } |
+ return containsAwait; |
+ } |
+ |
+ @override |
+ bool visitBreak(js.Break node) { |
+ if (node.targetLabel != null) { |
+ targets[node] = labelledStatements.lastWhere( |
+ (js.LabeledStatement statement) { |
+ return statement.label == node.targetLabel; |
+ }); |
+ } else { |
+ targets[node] = loopsAndSwitches.last; |
+ } |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitCall(js.Call node) { |
+ bool containsAwait = visit(node.target); |
+ for (js.Expression argument in node.arguments) { |
+ if (visit(argument)) containsAwait = true; |
+ } |
+ return containsAwait; |
+ } |
+ |
+ @override |
+ bool visitCase(js.Case node) { |
+ bool expression = visit(node.expression); |
+ bool body = visit(node.body); |
+ return expression || body; |
+ } |
+ |
+ @override |
+ bool visitCatch(js.Catch node) { |
+ bool declaration = visit(node.declaration); |
+ bool body = visit(node.body); |
+ return declaration || body; |
+ } |
+ |
+ @override |
+ bool visitComment(js.Comment node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitConditional(js.Conditional node) { |
+ bool condition = visit(node.condition); |
+ bool then = visit(node.then); |
+ bool otherwise = visit(node.otherwise); |
+ return condition || then || otherwise; |
+ } |
+ |
+ @override |
+ bool visitContinue(js.Continue node) { |
+ if (node.targetLabel != null) { |
+ targets[node] = labelledStatements.lastWhere( |
+ (js.LabeledStatement stm) => stm.label == node.targetLabel); |
+ } else { |
+ targets[node] = |
+ loopsAndSwitches.lastWhere((js.Node node) => node is! js.Switch); |
+ } |
+ assert(() { |
+ js.Node target = targets[node]; |
+ return target is js.Loop || |
+ (target is js.LabeledStatement && target.body is js.Loop); |
+ }); |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitDefault(js.Default node) { |
+ return visit(node.body); |
+ } |
+ |
+ @override |
+ bool visitDo(js.Do node) { |
+ loopsAndSwitches.add(node); |
+ bool body = visit(node.body); |
+ bool condition = visit(node.condition); |
+ loopsAndSwitches.removeLast(); |
+ return body || condition; |
+ } |
+ |
+ @override |
+ bool visitEmptyStatement(js.EmptyStatement node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitExpressionStatement(js.ExpressionStatement node) { |
+ return visit(node.expression); |
+ } |
+ |
+ @override |
+ bool visitFor(js.For node) { |
+ bool init = (node.init == null) ? false : visit(node.init); |
+ bool condition = (node.condition == null) ? false : visit(node.condition); |
+ bool update = (node.update == null) ? false : visit(node.update); |
+ loopsAndSwitches.add(node); |
+ bool body = visit(node.body); |
+ loopsAndSwitches.removeLast(); |
+ return init || condition || update || body; |
+ } |
+ |
+ @override |
+ bool visitForIn(js.ForIn node) { |
+ bool object = visit(node.object); |
+ loopsAndSwitches.add(node); |
+ bool body = visit(node.body); |
+ loopsAndSwitches.removeLast(); |
+ return object || body; |
+ } |
+ |
+ @override |
+ bool visitFun(js.Fun node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitFunctionDeclaration(js.FunctionDeclaration node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitIf(js.If node) { |
+ bool condition = visit(node.condition); |
+ bool then = visit(node.then); |
+ bool otherwise = visit(node.otherwise); |
+ return condition || then || otherwise; |
+ } |
+ |
+ @override |
+ bool visitInterpolatedExpression(js.InterpolatedExpression node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitInterpolatedLiteral(js.InterpolatedLiteral node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitInterpolatedParameter(js.InterpolatedParameter node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitInterpolatedSelector(js.InterpolatedSelector node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitInterpolatedStatement(js.InterpolatedStatement node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitLabeledStatement(js.LabeledStatement node) { |
+ usedNames.add(node.label); |
+ labelledStatements.add(node); |
+ bool containsAwait = visit(node.body); |
+ labelledStatements.removeLast(); |
+ return containsAwait; |
+ } |
+ |
+ @override |
+ bool visitLiteralBool(js.LiteralBool node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitLiteralExpression(js.LiteralExpression node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitLiteralNull(js.LiteralNull node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitLiteralNumber(js.LiteralNumber node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitLiteralStatement(js.LiteralStatement node) { |
+ return unsupported(node); |
+ } |
+ |
+ @override |
+ bool visitLiteralString(js.LiteralString node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitNamedFunction(js.NamedFunction node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitNew(js.New node) { |
+ return visitCall(node); |
+ } |
+ |
+ @override |
+ bool visitObjectInitializer(js.ObjectInitializer node) { |
+ bool containsAwait = false; |
+ for (js.Property property in node.properties) { |
+ if (visit(property)) containsAwait = true; |
+ } |
+ return containsAwait; |
+ } |
+ |
+ @override |
+ bool visitParameter(js.Parameter node) { |
+ usedNames.add(node.name); |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitPostfix(js.Postfix node) { |
+ return visit(node.argument); |
+ } |
+ |
+ @override |
+ bool visitPrefix(js.Prefix node) { |
+ return visit(node.argument); |
+ } |
+ |
+ @override |
+ bool visitProgram(js.Program node) { |
+ throw "Unexpected"; |
+ } |
+ |
+ @override |
+ bool visitProperty(js.Property node) { |
+ return visit(node.value); |
+ } |
+ |
+ @override |
+ bool visitRegExpLiteral(js.RegExpLiteral node) { |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitReturn(js.Return node) { |
+ hasExplicitReturns = true; |
+ targets[node] = currentFunction; |
+ if (node.value == null) return false; |
+ return visit(node.value); |
+ } |
+ |
+ @override |
+ bool visitSwitch(js.Switch node) { |
+ loopsAndSwitches.add(node); |
+ bool result = visit(node.key); |
+ for (js.SwitchClause clause in node.cases) { |
+ if (visit(clause)) result = true; |
+ } |
+ loopsAndSwitches.removeLast(); |
+ return result; |
+ } |
+ |
+ @override |
+ bool visitThis(js.This node) { |
+ hasThis = true; |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitThrow(js.Throw node) { |
+ return visit(node.expression); |
+ } |
+ |
+ @override |
+ bool visitTry(js.Try node) { |
+ bool body = visit(node.body); |
+ bool catchPart = (node.catchPart == null) ? false : visit(node.catchPart); |
+ bool finallyPart = |
+ (node.finallyPart == null) ? false : visit(node.finallyPart); |
+ return body || catchPart || finallyPart; |
+ } |
+ |
+ @override |
+ bool visitVariableDeclaration(js.VariableDeclaration node) { |
+ usedNames.add(node.name); |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitVariableDeclarationList(js.VariableDeclarationList node) { |
+ bool result = false; |
+ for (js.VariableInitialization init in node.declarations) { |
+ if (visit(init)) result = true; |
+ } |
+ return result; |
+ } |
+ |
+ @override |
+ bool visitVariableInitialization(js.VariableInitialization node) { |
+ return visitAssignment(node); |
+ } |
+ |
+ @override |
+ bool visitVariableUse(js.VariableUse node) { |
+ usedNames.add(node.name); |
+ return false; |
+ } |
+ |
+ @override |
+ bool visitWhile(js.While node) { |
+ loopsAndSwitches.add(node); |
+ bool condition = visit(node.condition); |
+ bool body = visit(node.body); |
+ loopsAndSwitches.removeLast(); |
+ return condition || body; |
+ } |
+ |
+ @override |
+ bool visitDartYield(js.DartYield node) { |
+ visit(node.expression); |
+ return true; |
+ } |
+} |