Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: pkg/compiler/lib/src/js/rewrite_async.dart

Issue 839323003: Implementation of async-await transformation on js ast. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments - add TODO for buggy error behaviour. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/compiler/lib/src/js/printer.dart ('k') | pkg/compiler/lib/src/js/template.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library rewrite_async;
6
7 // TODO(sigurdm): Throws in catch-handlers are handled wrong.
8 // TODO(sigurdm): Avoid using variables in templates. It could blow up memory
9 // use.
10
11 import "dart:math" show max;
12 import 'dart:collection';
13
14 import "js.dart" as js;
15
16 import '../util/util.dart';
17 import '../dart2jslib.dart' show DiagnosticListener;
18
19 import "../helpers/helpers.dart";
20
21 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield
22 /// (with dart-like semantics) to an equivalent function without these.
23 /// await-for is not handled and must be rewritten before. (Currently handled
24 /// in ssa/builder.dart).
25 ///
26 /// When generating the input to this, special care must be taken that
27 /// parameters to sync* functions that are mutated in the body must be boxed.
28 /// (Currently handled in closure.dart).
29 ///
30 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation.
31 class AsyncRewriter extends js.NodeVisitor {
32
33 // Local variables are hoisted to the top of the function, so they are
34 // collected here.
35 List<js.VariableDeclaration> localVariables =
36 new List<js.VariableDeclaration>();
37
38 Map<js.Node, int> continueLabels = new Map<js.Node, int>();
39 Map<js.Node, int> breakLabels = new Map<js.Node, int>();
40 Map<js.Node, int> finallyLabels = new Map<js.Node, int>();
41 int exitLabel;
42
43 // A stack of all enclosing jump targets (including the function for
44 // representing the target of a return, and all enclosing try-blocks that have
45 // finally part, this way ensuring all the finally blocks between a jump and
46 // its target are run before the jump.
47 List<js.Node> targetsAndTries = new List<js.Node>();
48
49 List<int> continueStack = new List<int>();
50 List<int> breakStack = new List<int>();
51 List<int> returnStack = new List<int>();
52
53 List<Pair<String, String>> variableRenamings =
54 new List<Pair<String, String>>();
55
56 PreTranslationAnalysis analysis;
57
58 List<int> errorHandlerLabels = new List<int>();
59
60 final Function safeVariableName;
61
62 // All the <x>Name variables are names of Javascript variables used in the
63 // transformed code.
64
65 /// Contains the result of an awaited expression, or a conditional or
66 /// lazy boolean operator.
67 ///
68 /// For example a conditional expression is roughly translated like:
69 /// [[cond ? a : b]]
70 ///
71 /// Becomes:
72 ///
73 /// while true { // outer while loop
74 /// switch (goto) { // Simulates goto
75 /// ...
76 /// goto = [[cond]] ? thenLabel : elseLabel
77 /// break;
78 /// case thenLabel:
79 /// result = [[a]];
80 /// goto = joinLabel;
81 /// case elseLabel:
82 /// result = [[b]];
83 /// case joinLabel:
84 /// // Now the result of computing the condition is in result.
85 /// ....
86 /// }
87 /// }
88 ///
89 /// It is a parameter to the [helperName] function, so that [thenHelper] and
90 /// [streamHelper] can call [helperName] with the result of an awaited Future.
91 String resultName;
92
93 /// The name of the inner function that is scheduled to do each await/yield,
94 /// and called to do a new iteration for sync*.
95 String helperName;
96
97 /// The Completer that will finish an async function.
98 ///
99 /// Not used for sync* or async* functions.
100 String completerName;
101
102 /// The StreamController that controls an async* function.
103 ///
104 /// Not used for async and sync* functions
105 String controllerName;
106
107 /// Used to simulate a goto.
108 ///
109 /// To "goto" a label, the label is assigned to this
110 /// variable, and break out of the switch to take another iteration in the
111 /// while loop. See [addGoto]
112 String gotoName;
113
114 /// The label of the current error handler.
115 String handlerName;
116
117 /// Current caught error.
118 String errorName;
119
120 /// A stack of labels of finally blocks to visit, and the label to go to after
121 /// the last.
122 String nextName;
123
124 /// The current returned value (a finally block may overwrite it).
125 String returnValueName;
126
127 /// The label of the outer loop.
128 ///
129 /// Used if there are untransformed loops containing break or continues to
130 /// targets outside the loop.
131 String outerLabelName;
132
133 /// If javascript `this` is used, it is accessed via this variable, in the
134 /// [helperName] function.
135 String selfName;
136
137 // These expressions are hooks for communicating with the runtime.
138
139 /// The function called by an async function to simulate an await or return.
140 ///
141 /// For an await it is called with:
142 ///
143 /// - The value to await
144 /// - The [helperName]
145 /// - The [completerName]
146 /// - A JavaScript function that is executed if the future completed with
147 /// an error. That function is responsible for executing the right error
148 /// handler and/or finally blocks).
149 ///
150 /// For a return it is called with:
151 ///
152 /// - The value to complete the completer with.
153 /// - null
154 /// - The [completerName]
155 /// - null.
156 final js.Expression thenHelper;
157
158 /// The function called by an async* function to simulate an await, yield or
159 /// yield*.
160 ///
161 /// For an await/yield/yield* it is called with:
162 ///
163 /// - The value to await/yieldExpression(value to yield)/
164 /// yieldStarExpression(stream to yield)
165 /// - The [helperName]
166 /// - The [controllerName]
167 /// - A JavaScript function that is executed if the future completed with
168 /// an error. That function is responsible for executing the right error
169 /// handler and/or finally blocks).
170 ///
171 /// For a return it is called with:
172 ///
173 /// - null
174 /// - null
175 /// - The [controllerName]
176 /// - null.
177 final js.Expression streamHelper;
178
179 /// Initializes the [completerName] variable.
floitsch 2015/02/06 14:58:43 I don't think that's correct. It's the constructor
sigurdm 2015/02/06 15:21:21 Done.
180 ///
181 /// Specific to async methods.
182 final js.Expression newCompleter;
183
184 /// Initializes the [controllerName] variable.
floitsch 2015/02/06 14:58:43 Not correct either. It's a JS Expression (in this
sigurdm 2015/02/06 15:21:21 Done.
185 ///
186 /// Specific to async* methods.
187 final js.Expression newController;
188
189 /// Creates an Iterable for a sync* method. Called with [helperName].
floitsch 2015/02/06 14:58:44 ditto. This seems to be a constructor.
sigurdm 2015/02/06 15:21:21 Done.
190 final js.Expression newIterable;
191
192 /// A JS Expression that creates a marker showing that iteration is over.
193 ///
194 /// Called without arguments.
195 final js.Expression endOfIteration;
196
197 /// A JS Expression that creates a marker indicating a 'yield' statement.
198 ///
199 /// Called with the value to yield.
200 final js.Expression yieldExpression;
201
202 /// A JS Expression that creates a marker indication a 'yield*' statement.
203 ///
204 /// Called with the stream to yield from.
205 final js.Expression yieldStarExpression;
206
207 final DiagnosticListener diagnosticListener;
208 // For error reporting only.
209 Spannable get spannable {
210 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable;
211 }
212
213 Spannable _spannable;
214
215 int _currentLabel = 0;
216
217 // The highest temporary variable index currently in use.
218 int currentTempVarIndex = 0;
219 // The highest temporary variable index ever in use in this function.
220 int tempVarHighWaterMark = 0;
221 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>();
222
223 js.AsyncModifier async;
224
225 bool get isSync => async == const js.AsyncModifier.sync();
226 bool get isAsync => async == const js.AsyncModifier.async();
227 bool get isSyncStar => async == const js.AsyncModifier.syncStar();
228 bool get isAsyncStar => async == const js.AsyncModifier.asyncStar();
229
230 AsyncRewriter(this.diagnosticListener,
231 spannable,
232 {this.thenHelper,
233 this.streamHelper,
234 this.newCompleter,
235 this.newController,
236 this.endOfIteration,
237 this.newIterable,
238 this.yieldExpression,
239 this.yieldStarExpression,
240 this.safeVariableName})
241 : _spannable = spannable;
242
243 /// Main entry point.
244 /// Rewrites a sync*/async/async* function to an equivalent normal function.
245 ///
246 /// [spannable] can be passed to have a location for error messages.
247 js.Fun rewrite(js.Fun node, [Spannable spannable]) {
248 _spannable = spannable;
249
250 async = node.asyncModifier;
251 assert(!isSync);
252
253 analysis = new PreTranslationAnalysis(unsupported);
254 analysis.analyze(node);
255
256 // To avoid name collisions with existing names, the fresh names are
257 // generated after the analysis.
258 resultName = freshName("result");
259 completerName = freshName("completer");
260 controllerName = freshName("controller");
261 helperName = freshName("helper");
262 gotoName = freshName("goto");
263 handlerName = freshName("handler");
264 errorName = freshName("error");
265 nextName = freshName("next");
266 returnValueName = freshName("returnValue");
267 outerLabelName = freshName("outer");
268 selfName = freshName("self");
269
270 return node.accept(this);
271 }
272
273 js.Expression get currentErrorHandler {
274 return errorHandlerLabels.isEmpty
275 ? new js.LiteralNull()
276 : js.number(errorHandlerLabels.last);
277 }
278
279 int allocateTempVar() {
280 assert(tempVarHighWaterMark >= currentTempVarIndex);
281 currentTempVarIndex++;
282 tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark);
283 return currentTempVarIndex;
284 }
285
286 js.VariableUse useTempVar(int i) {
287 return tempVarNames.putIfAbsent(
288 i, () => new js.VariableUse(freshName("temp$i")));
289 }
290
291 /// Generates a variable name with [safeVariableName] based on [originalName]
292 /// with a suffix to guarantee it does not collide with already used names.
293 String freshName(String originalName) {
294 String safeName = safeVariableName(originalName);
295 String result = safeName;
296 int counter = 1;
297 while (analysis.usedNames.contains(result)) {
298 result = "$safeName$counter";
299 ++counter;
300 }
301 analysis.usedNames.add(result);
302 return result;
303 }
304
305 /// All the pieces are collected in this map, to create a switch with a case
306 /// for each label.
307 ///
308 /// The order is important, therefore the type is explicitly LinkedHashMap.
309 LinkedHashMap<int, List<js.Statement>> labelledParts =
310 new LinkedHashMap<int, List<js.Statement>>();
311
312 /// Description of each label for readability of the non-minified output.
313 Map<int, String> labelComments = new Map<int, String>();
314
315 /// True if the function has any try blocks containing await.
316 bool hasTryBlocks = false;
317
318 /// True if any return, break or continue passes through a finally.
319 bool hasJumpThroughFinally = false;
320
321 /// True if the traversion currently is inside a loop or switch for which
322 /// [shouldTransform] is false.
323 bool insideUntranslatedBreakable = false;
324
325 /// True if a label is used to break to an outer switch-statement.
326 bool hasJumpThoughOuterLabel = false;
327
328 /// True if there is a catch-handler protected by a finally with no enclosing
329 /// catch-handlers.
330 bool needsRethrow = false;
331
332 /// Buffer for collecting translated statements belonging to the same switch
333 /// case.
334 List<js.Statement> currentStatementBuffer;
335
336 // Labels will become cases in the big switch expression, and `goto label`
337 // is expressed by assigning to the switch key [gotoName] and breaking out of
338 // the switch.
339
340 int newLabel([String comment]) {
341 int result = _currentLabel;
342 _currentLabel++;
343 if (comment != null) {
344 labelComments[result] = comment;
345 }
346 return result;
347 }
348
349 /// Begins outputting statements to a new buffer with label [label].
350 ///
351 /// Each buffer ends up as its own case part in the big state-switch.
352 void beginLabel(int label) {
353 assert(!labelledParts.containsKey(label));
354 currentStatementBuffer = new List<js.Statement>();
355 labelledParts[label] = currentStatementBuffer;
356 addStatement(new js.Comment(labelComments[label]));
357 }
358
359 /// Returns a statement assigning to the variable named [gotoName].
360 /// This should be followed by a break for the goto to be executed. Use
361 /// [gotoWithBreak] or [addGoto] for this.
362 js.Statement setGotoVariable(int label) {
363 return new js.ExpressionStatement(
364 new js.Assignment(new js.VariableUse(gotoName), js.number(label)));
365 }
366
367 /// Returns a block that has a goto to [label] including the break.
368 ///
369 /// Also inserts a comment describing the label if available.
370 js.Block gotoAndBreak(int label) {
371 List<js.Statement> statements = new List<js.Statement>();
372 if (labelComments.containsKey(label)) {
373 statements.add(new js.Comment("goto ${labelComments[label]}"));
374 }
375 statements.add(setGotoVariable(label));
376 if (insideUntranslatedBreakable) {
377 hasJumpThoughOuterLabel = true;
378 statements.add(new js.Break(outerLabelName));
379 } else {
380 statements.add(new js.Break(null));
381 }
382 return new js.Block(statements);
383 }
384
385 /// Adds a goto to [label] including the break.
386 ///
387 /// Also inserts a comment describing the label if available.
388 void addGoto(int label) {
389 if (labelComments.containsKey(label)) {
390 addStatement(new js.Comment("goto ${labelComments[label]}"));
391 }
392 addStatement(setGotoVariable(label));
393
394 addBreak();
395 }
396
397 void addStatement(js.Statement node) {
398 currentStatementBuffer.add(node);
399 }
400
401 void addExpressionStatement(js.Expression node) {
402 addStatement(new js.ExpressionStatement(node));
403 }
404
405 /// True if there is an await or yield in [node] or some subexpression.
406 bool shouldTransform(js.Node node) {
407 return analysis.hasAwaitOrYield.contains(node);
408 }
409
410 void unsupported(js.Node node) {
411 throw new UnsupportedError(
412 "Node $node cannot be transformed by the await-sync transformer");
413 }
414
415 void unreachable(js.Node node) {
416 diagnosticListener.internalError(
417 spannable, "Internal error, trying to visit $node");
418 }
419
420 visitStatement(js.Statement node) {
421 node.accept(this);
422 }
423
424 /// Visits [node] to ensure its sideeffects are performed, but throwing away
425 /// the result.
426 ///
427 /// If the return value of visiting [node] is an expression guaranteed to have
428 /// no side effect, it is dropped.
429 void visitExpressionIgnoreResult(js.Expression node) {
430 js.Expression result = node.accept(this);
431 if (!(result is js.Literal || result is js.VariableUse)) {
432 addExpressionStatement(result);
433 }
434 }
435
436 js.Expression visitExpression(js.Expression node) {
437 return node.accept(this);
438 }
439
440
441 /// Calls [fn] with the value of evaluating [node1] and [node2].
442 ///
443 /// Both nodes are evaluated in order.
444 ///
445 /// If node2 must be transformed (see [shouldTransform]), then the evaluation
446 /// of node1 is added to the current statement-list and the result is stored
447 /// in a temporary variable. The evaluation of node2 is then free to emit
448 /// statements without affecting the result of node1.
449 ///
450 /// This is necessary, because await or yield expressions have to emit
451 /// statements, and these statements could affect the value of node1.
452 ///
453 /// For example:
454 ///
455 /// - _storeIfNecessary(someLiteral) returns someLiteral.
456 /// - _storeIfNecessary(someVariable)
457 /// inserts: var tempX = someVariable
458 /// returns: tempX
459 /// where tempX is a fresh temporary variable.
460 js.Expression _storeIfNecessary(js.Expression result) {
461 // Note that RegExes, js.ArrayInitializer and js.ObjectInitializer are not
462 // [js.Literal]s.
463 if (result is js.Literal) return result;
464 js.Expression tempVar = useTempVar(allocateTempVar());
465 addExpressionStatement(new js.Assignment(tempVar, result));
466 return tempVar;
467 }
468
469 withExpression(js.Expression node, fn(js.Expression result), {bool store}) {
470 int oldTempVarIndex = currentTempVarIndex;
471 js.Expression visited = visitExpression(node);
472 if (store) {
473 visited = _storeIfNecessary(visited);
474 }
475 var result = fn(visited);
476 currentTempVarIndex = oldTempVarIndex;
477 return result;
478 }
479
480 /// Calls [fn] with the value of evaluating [node1] and [node2].
481 ///
482 /// If `shouldTransform(node2)` the first expression is stored in a temporary
483 /// variable.
484 ///
485 /// This is because node1 must be evaluated before visiting node2,
486 /// because the evaluation of an await or yield cannot be expressed as
487 /// an expression, visiting node2 it will output statements that
488 /// might have an influence on the value of node1.
489 withExpression2(js.Expression node1, js.Expression node2,
490 fn(js.Expression result1, js.Expression result2)) {
491 int oldTempVarIndex = currentTempVarIndex;
492 js.Expression r1 = visitExpression(node1);
493 if (shouldTransform(node2)) {
494 r1 = _storeIfNecessary(r1);
495 }
496 js.Expression r2 = visitExpression(node2);
497 var result = fn(r1, r2);
498 currentTempVarIndex = oldTempVarIndex;
499 return result;
500 }
501
502 /// Calls [fn] with the value of evaluating all [nodes].
503 ///
504 /// All results before the last node where `shouldTransform(node)` are stored
505 /// in temporary variables.
506 ///
507 /// See more explanation on [withExpression2].
508 ///
509 /// If any of the nodes are null, they are ignored, and a null is passed to
510 /// [fn] in that place.
511 withExpressions(List<js.Expression> nodes, fn(List<js.Expression> results)) {
512 int oldTempVarIndex = currentTempVarIndex;
513 // Find last occurence of a 'transform' expression in [nodes].
514 // All expressions before that must be stored in temp-vars.
515 int lastTransformIndex = 0;
516 for (int i = nodes.length - 1; i >= 0; --i) {
517 if (nodes[i] == null) continue;
518 if (shouldTransform(nodes[i])) {
519 lastTransformIndex = i;
520 break;
521 }
522 }
523 List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) {
524 return node == null ? null : _storeIfNecessary(visitExpression(node));
525 }).toList();
526 visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) {
527 return node == null ? null : visitExpression(node);
528 }));
529 var result = fn(visited);
530 currentTempVarIndex = oldTempVarIndex;
531 return result;
532 }
533
534 /// Emits the return block that all returns should jump to (after going
535 /// through all the enclosing finally blocks). The jump to here is made in
536 /// [visitReturn].
537 ///
538 /// Returning from an async method calls the [thenHelper] with the result.
539 /// (the result might have been stored in [returnValueName] by some finally
540 /// block).
541 ///
542 /// Returning from a sync* function returns an [endOfIteration] marker.
543 ///
544 /// Returning from an async* function calls the [streamHelper] with an
545 /// [endOfIteration] marker.
546 void addExit() {
547 if (analysis.hasExplicitReturns || isAsyncStar) {
548 beginLabel(exitLabel);
549 } else {
550 addStatement(new js.Comment("implicit return"));
551 }
552 switch (async) {
553 case const js.AsyncModifier.async():
554 String returnValue =
555 analysis.hasExplicitReturns ? returnValueName : "null";
556 addStatement(js.js.statement(
557 "return #thenHelper($returnValue, null, $completerName, null)", {
558 "thenHelper": thenHelper
559 }));
560 break;
561 case const js.AsyncModifier.syncStar():
562 addStatement(new js.Return(new js.Call(endOfIteration, [])));
563 break;
564 case const js.AsyncModifier.asyncStar():
565 addStatement(js.js.statement(
566 "return #streamHelper(null, null, $controllerName, null)", {
567 "streamHelper": streamHelper
568 }));
569 break;
570 default:
571 diagnosticListener.internalError(
572 spannable, "Internal error, unexpected asyncmodifier $async");
573 }
574 }
575
576 /// The initial call to [thenHelper]/[streamHelper].
577 ///
578 /// There is no value to await/yield, so the first argument is `null` and
579 /// also the errorCallback is `null`.
580 ///
581 /// Returns the [Future]/[Stream] coming from [completerName]/
582 /// [controllerName].
583 js.Statement generateInitializer() {
584 if (isAsync) {
585 return js.js.statement(
586 "return #thenHelper(null, $helperName, $completerName, null);", {
587 "thenHelper": thenHelper
588 });
589 } else if (isAsyncStar) {
590 return js.js.statement(
591 "return #streamHelper(null, $helperName, $controllerName, null);", {
592 "streamHelper": streamHelper
593 });
594 } else {
595 throw diagnosticListener.internalError(
596 spannable, "Unexpected asyncModifier: $async");
597 }
598 }
599
600 /// Rewrites an async/sync*/async* function to a normal Javascript function.
601 ///
602 /// The control flow is flattened by simulating 'goto' using a switch in a
603 /// loop and a state variable [gotoName] inside a helper-function that can be
604 /// called back by [thenHelper]/[streamHelper]/the [Iterator].
605 ///
606 /// Local variables are hoisted outside the helper.
607 ///
608 /// Awaits in async/async* are translated to code that remembers the current
609 /// location (so the function can resume from where it was) followed by a call
610 /// to the [thenHelper]. The helper sets up the waiting for the awaited value
611 /// and returns a future which is immediately returned by the translated
612 /// await.
613 /// Yields in async* are translated to a call to the [streamHelper]. They,
floitsch 2015/02/06 14:58:44 new line before. Actually currently they always i
sigurdm 2015/02/06 15:21:21 Done.
614 /// too, need to be prepared to be interrupted in case the stream is paused or
615 /// canceled.
616 ///
617 /// Yield/yield* in a sync* function is translated to a return of the value,
618 /// wrapped into a "IterationMarker" that signals the type (yield or yield*).
619 /// Sync* functions are executed on demand (when the user requests a value) by
620 /// the Iterable that knows how to handle these values.
621 ///
622 /// Simplified examples (not the exact translation, but intended to show the
623 /// ideas):
624 ///
625 /// function (x, y, z) async {
626 /// var p = await foo();
627 /// return bar(p);
628 /// }
629 ///
630 /// Becomes:
631 ///
632 /// function(x, y, z) {
633 /// var goto = 0, returnValue, completer = new Completer(), p;
634 /// function helper(result) {
635 /// while (true) {
636 /// switch (goto) {
637 /// case 0:
638 /// goto = 1 // Remember where to continue when the future succeeds.
639 /// return thenHelper(foo(), helper, completer, null);
640 /// case 1:
641 /// p = result;
642 /// returnValue = bar(p);
643 /// goto = 2;
644 /// break;
645 /// case 2:
646 /// return thenHelper(returnValue, null, completer, null)
647 /// }
648 /// }
649 /// return thenHelper(null, helper, completer, null);
650 /// }
651 /// }
652 ///
653 /// Try/catch is implemented by maintaining [handlerName] to contain the label
654 /// of the current handler. The switch is nested inside a try/catch that will
655 /// redirect the flow to the current handler.
656 ///
657 /// A `finally` clause is compiled similar to normal code, with the additional
658 /// complexity that `finally` clauses need to know where to jump to after the
659 /// clause is done. In the translation, each flow-path that enters a `finally`
660 /// sets up the variable [nextName] with a stack of finally-blocks and a final
661 /// jump-target (exit, catch, ...).
662 ///
663 /// function (x, y, z) async {
664 /// try {
665 /// try {
666 /// throw "error";
667 /// } finally {
668 /// finalize1();
669 /// }
670 /// } catch (e) {
671 /// handle(e);
672 /// } finally {
673 /// finalize2();
674 /// }
675 /// }
676 ///
677 /// Translates into (besides the fact that structures not containing
678 /// await/yield/yield* are left intact):
679 ///
680 /// function(x, y, z) {
681 /// var goto = 0;
682 /// var returnValue;
683 /// var completer = new Completer();
684 /// var handler = null;
685 /// var p;
686 /// // The result can be either the result of an awaited future, or an
687 /// // error if the future completed with an error.
688 /// function helper(result) {
689 /// while (true) {
690 /// try {
691 /// switch (goto) {
692 /// case 0:
693 /// handler = 4; // The outer catch-handler
694 /// handler = 1; // The inner (implicit) catch-handler
695 /// throw "error";
696 /// next = [3];
697 /// // After the finally (2) continue normally after the try.
698 /// goto = 2;
699 /// break;
700 /// case 1: // (implicit) catch handler for inner try.
701 /// next = [3]; // destination after the finally.
702 /// // fall-though to the finally handler.
703 /// case 2: // finally for inner try
704 /// handler = 4; // catch-handler for outer try.
705 /// finalize1();
706 /// goto = next.pop();
707 /// break;
708 /// case 3: // exiting inner try.
709 /// next = [6];
710 /// goto = 5; // finally handler for outer try.
711 /// break;
712 /// case 4: // catch handler for outer try.
713 /// e = result;
714 /// handle(e);
715 /// // Fall through to finally.
716 /// case 5: // finally handler for outer try.
717 /// handler = null;
718 /// finalize2();
719 /// goto = next.pop();
720 /// break;
721 /// case 6: // Exiting outer try.
722 /// case 7: // return
723 /// return thenHelper(returnValue, null, completer, null);
724 /// }
725 /// } catch (error) {
726 /// result = error;
727 /// goto = handler;
728 /// }
729 /// }
730 /// return thenHelper(null, helper, completer, null);
731 /// }
732 /// }
733 ///
734 @override
735 js.Expression visitFun(js.Fun node) {
736 if (isSync) return node;
737
738 beginLabel(newLabel("Function start"));
739 // AsyncStar needs a returnlabel for its handling of cancelation. See
740 // [visitDartYield].
741 exitLabel =
742 analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null;
743 js.Statement body = node.body;
744 targetsAndTries.add(node);
745 visitStatement(body);
746 targetsAndTries.removeLast();
747 addExit();
748
749 List<js.SwitchClause> clauses = labelledParts.keys.map((label) {
750 return new js.Case(js.number(label), new js.Block(labelledParts[label]));
751 }).toList();
752 js.Statement helperBody =
753 new js.Switch(new js.VariableUse(gotoName), clauses);
754 if (hasJumpThoughOuterLabel) {
755 helperBody = js.js.statement("$outerLabelName: #", [helperBody]);
756 }
757 if (hasTryBlocks) {
758 helperBody = js.js.statement("""
759 try {
760 #body
761 } catch ($errorName){
762 if ($handlerName === null)
763 throw $errorName;
764 $resultName = $errorName;
765 $gotoName = $handlerName;
766 }""", {"body": helperBody});
767 }
768 List<js.VariableInitialization> inits = <js.VariableInitialization>[];
769
770 js.VariableInitialization makeInit(String name, js.Expression initValue) {
771 return new js.VariableInitialization(
772 new js.VariableDeclaration(name), initValue);
773 }
774
775 inits.add(makeInit(gotoName, js.number(0)));
776 if (isAsync) {
777 inits.add(makeInit(completerName, new js.New(newCompleter, [])));
778 } else if (isAsyncStar) {
779 inits.add(makeInit(controllerName,
780 new js.Call(newController, [new js.VariableUse(helperName)])));
781 }
782 if (hasTryBlocks) {
783 inits.add(makeInit(handlerName, new js.LiteralNull()));
784 }
785 if (hasJumpThroughFinally) {
786 inits.add(makeInit(nextName, null));
787 }
788 if (analysis.hasExplicitReturns && isAsync) {
789 inits.add(makeInit(returnValueName, null));
790 }
791 if (analysis.hasThis) {
792 inits.add(makeInit(selfName, new js.This()));
793 }
794 inits.addAll(localVariables.map((js.VariableDeclaration decl) {
795 return new js.VariableInitialization(decl, null);
796 }));
797 inits.addAll(new Iterable.generate(tempVarHighWaterMark,
798 (int i) => makeInit(useTempVar(i + 1).name, null)));
799 js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits);
800 if (isSyncStar) {
801 return js.js("""
802 function (#params) {
803 return new #newIterable(function () {
804 #varDecl;
805 return function $helperName($resultName) {
806 while (true)
807 #helperBody;
808 };
809 });
810 }
811 """, {
812 "params": node.params,
813 "helperBody": helperBody,
814 "varDecl": varDecl,
815 "newIterable": newIterable
816 });
817 }
818 return js.js("""
819 function (#params) {
820 #varDecl;
821 function $helperName($resultName) {
822 while (true)
823 #helperBody;
824 }
825 #init;
826 }""", {
827 "params": node.params,
828 "helperBody": helperBody,
829 "varDecl": varDecl,
830 "init": generateInitializer()
831 });
832 }
833
834 @override
835 js.Expression visitAccess(js.PropertyAccess node) {
836 return withExpression2(node.receiver, node.selector,
837 (receiver, selector) => new js.PropertyAccess(receiver, selector));
838 }
839
840 @override
841 js.Expression visitArrayHole(js.ArrayHole node) {
842 return node;
843 }
844
845 @override
846 js.Expression visitArrayInitializer(js.ArrayInitializer node) {
847 return withExpressions(node.elements, (elements) {
848 return new js.ArrayInitializer(elements);
849 });
850 }
851
852 @override
853 js.Expression visitAssignment(js.Assignment node) {
854 if (!shouldTransform(node)) {
855 return new js.Assignment.compound(visitExpression(node.leftHandSide),
856 node.op, visitExpression(node.value));
857 }
858 js.Expression leftHandSide = node.leftHandSide;
859 if (leftHandSide is js.VariableUse) {
860 return withExpression(node.value, (js.Expression value) {
861 return new js.Assignment(leftHandSide, value);
862 }, store: false);
863 } else if (leftHandSide is js.PropertyAccess) {
864 return withExpressions([
865 leftHandSide.receiver,
866 leftHandSide.selector,
867 node.value
868 ], (evaluated) {
869 return new js.Assignment.compound(
870 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op,
871 evaluated[2]);
872 });
873 } else {
874 throw "Unexpected assignment left hand side $leftHandSide";
875 }
876 }
877
878 /// An await is translated to a call to [thenHelper]/[streamHelper].
879 ///
880 /// See the comments of [visitFun] for an example.
881 @override
882 js.Expression visitAwait(js.Await node) {
883 assert(isAsync || isAsyncStar);
884 int afterAwait = newLabel("returning from await.");
885 withExpression(node.expression, (js.Expression value) {
886 addStatement(setGotoVariable(afterAwait));
887 js.Expression errorCallback = errorHandlerLabels.isEmpty
888 ? new js.LiteralNull()
889 : js.js("""
890 function($errorName) {
891 $gotoName = #currentHandler;
892 $helperName($errorName);
893 }""", {"currentHandler": currentErrorHandler});
894
895 addStatement(js.js.statement("""
896 return #thenHelper(#value,
897 $helperName,
898 ${isAsync ? completerName : controllerName},
899 #errorCallback);
900 """, {
901 "thenHelper": isAsync ? thenHelper : streamHelper,
902 "value": value,
903 "errorCallback": errorCallback
904 }));
905 }, store: false);
906 beginLabel(afterAwait);
907 return new js.VariableUse(resultName);
908 }
909
910 /// Checks if [node] is the variable named [resultName].
911 ///
912 /// [resultName] is used to hold the result of a transformed computation
913 /// for example the result of awaiting, or the result of a conditional or
914 /// short-circuiting expression.
915 /// If the subexpression of some transformed node already is transformed and
916 /// visiting it returns [resultName], it is not redundantly assigned to itself
917 /// again.
918 bool isResult(js.Expression node) {
919 return node is js.VariableUse && node.name == resultName;
920 }
921
922 @override
923 js.Expression visitBinary(js.Binary node) {
924 if (shouldTransform(node.right) && (node.op == "||" || node.op == "&&")) {
925 int thenLabel = newLabel("then");
926 int joinLabel = newLabel("join");
927 withExpression(node.left, (js.Expression left) {
928 js.Statement assignLeft = isResult(left)
929 ? new js.Block.empty()
930 : new js.ExpressionStatement(
931 new js.Assignment(new js.VariableUse(resultName), left));
932 if (node.op == "||") {
933 addStatement(new js.If(left, gotoAndBreak(thenLabel), assignLeft));
934 } else {
935 assert(node.op == "&&");
936 addStatement(new js.If(left, assignLeft, gotoAndBreak(thenLabel)));
937 }
938 }, store: true);
939 addGoto(joinLabel);
940 beginLabel(thenLabel);
941 withExpression(node.right, (js.Expression value) {
942 if (!isResult(value)) {
943 addExpressionStatement(
944 new js.Assignment(new js.VariableUse(resultName), value));
945 }
946 }, store: false);
947 beginLabel(joinLabel);
948 return new js.VariableUse(resultName);
949 }
950
951 return withExpression2(node.left, node.right,
952 (left, right) => new js.Binary(node.op, left, right));
953 }
954
955 @override
956 js.Expression visitBlob(js.Blob node) {
957 return node;
958 }
959
960 @override
961 void visitBlock(js.Block node) {
962 for (js.Statement statement in node.statements) {
963 visitStatement(statement);
964 }
965 }
966
967 @override
968 void visitBreak(js.Break node) {
969 js.Node target = analysis.targets[node];
970 if (!shouldTransform(target)) {
971 addStatement(node);
972 return;
973 }
974 translateJump(target, breakLabels[target]);
975 }
976
977 @override
978 js.Expression visitCall(js.Call node) {
979 bool storeTarget = node.arguments.any(shouldTransform);
980 return withExpression(node.target, (target) {
981 return withExpressions(node.arguments, (List<js.Expression> arguments) {
982 return new js.Call(target, arguments);
983 });
984 }, store: storeTarget);
985 }
986
987 @override
988 void visitCase(js.Case node) {
989 return unreachable(node);
990 }
991
992 @override
993 void visitCatch(js.Catch node) {
994 return unreachable(node);
995 }
996
997 @override
998 void visitComment(js.Comment node) {
999 addStatement(node);
1000 }
1001
1002 @override
1003 js.Expression visitConditional(js.Conditional node) {
1004 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) {
1005 return withExpression(node.condition, (js.Expression condition) {
1006 return new js.Conditional(condition, node.then, node.otherwise);
1007 });
1008 }
1009 int thenLabel = newLabel("then");
1010 int joinLabel = newLabel("join");
1011 int elseLabel = newLabel("else");
1012 withExpression(node.condition, (js.Expression condition) {
1013 addExpressionStatement(new js.Assignment(new js.VariableUse(gotoName),
1014 new js.Conditional(
1015 condition, js.number(thenLabel), js.number(elseLabel))));
1016 }, store: false);
1017 addBreak();
1018 beginLabel(thenLabel);
1019 withExpression(node.then, (js.Expression value) {
1020 if (!isResult(value)) {
1021 addExpressionStatement(
1022 new js.Assignment(new js.VariableUse(resultName), value));
1023 }
1024 }, store: false);
1025 addGoto(joinLabel);
1026 beginLabel(elseLabel);
1027 withExpression(node.otherwise, (js.Expression value) {
1028 if (!isResult(value)) {
1029 addExpressionStatement(
1030 new js.Assignment(new js.VariableUse(resultName), value));
1031 }
1032 }, store: false);
1033 beginLabel(joinLabel);
1034 return new js.VariableUse(resultName);
1035 }
1036
1037 @override
1038 void visitContinue(js.Continue node) {
1039 js.Node target = analysis.targets[node];
1040 if (!shouldTransform(target)) {
1041 addStatement(node);
1042 return;
1043 }
1044 translateJump(target, continueLabels[target]);
1045 }
1046
1047 /// Emits a break statement that exits the big switch statement.
1048 void addBreak() {
1049 if (insideUntranslatedBreakable) {
1050 hasJumpThoughOuterLabel = true;
1051 addStatement(new js.Break(outerLabelName));
1052 } else {
1053 addStatement(new js.Break(null));
1054 }
1055 }
1056
1057 /// Common code for handling break, continue, return.
1058 ///
1059 /// It is necessary to run all nesting finally-handlers between the jump and
1060 /// the target. For that [nextName] is used as a stack of places to go.
1061 /// See also [visitFun].
floitsch 2015/02/06 14:58:43 New line before.
sigurdm 2015/02/06 15:21:21 Done.
1062 void translateJump(js.Node target, int targetLabel) {
1063 // Compute a stack of all the 'finally' nodes that must be visited before
1064 // the jump.
1065 // The bottom of the stack is the label where the jump goes to.
1066 List<int> jumpStack = new List<int>();
1067 for (js.Node node in targetsAndTries.reversed) {
1068 if (node is js.Try) {
1069 assert(node.finallyPart != null);
1070 jumpStack.add(finallyLabels[node]);
1071 } else if (node == target) {
1072 jumpStack.add(targetLabel);
1073 break;
1074 }
1075 // Ignore other nodes.
1076 }
1077 jumpStack = jumpStack.reversed.toList();
1078 // As the program jumps directly to the top of the stack, it is taken off
1079 // now.
1080 int firstTarget = jumpStack.removeLast();
1081 if (jumpStack.isNotEmpty) {
1082 hasJumpThroughFinally = true;
1083 js.Expression jsJumpStack = new js.ArrayInitializer(
1084 jumpStack.map((int label) => js.number(label)).toList());
1085 addStatement(js.js.statement("$nextName = #", [jsJumpStack]));
1086 }
1087 addGoto(firstTarget);
1088 }
1089
1090 @override
1091 void visitDefault(js.Default node) => unreachable(node);
1092
1093 @override
1094 void visitDo(js.Do node) {
1095 if (!shouldTransform(node)) {
1096 bool oldInsideUntranslatedBreakable = insideUntranslatedBreakable;
1097 insideUntranslatedBreakable = true;
1098 withExpression(node.condition, (js.Expression condition) {
1099 addStatement(new js.Do(translateInBlock(node.body), condition));
1100 }, store: false);
1101 insideUntranslatedBreakable = oldInsideUntranslatedBreakable;
1102 return;
1103 }
1104 int startLabel = newLabel("do body");
1105
1106 int continueLabel = newLabel("do condition");
1107 continueLabels[node] = continueLabel;
1108
1109 int afterLabel = newLabel("after do");
1110 breakLabels[node] = afterLabel;
1111
1112 beginLabel(startLabel);
1113
1114 targetsAndTries.add(node);
1115 visitStatement(node.body);
1116 targetsAndTries.removeLast();
1117
1118 beginLabel(continueLabel);
1119 withExpression(node.condition, (js.Expression condition) {
1120 addStatement(new js.If.noElse(condition, gotoAndBreak(startLabel)));
1121 }, store: false);
1122 beginLabel(afterLabel);
1123 }
1124
1125 @override
1126 void visitEmptyStatement(js.EmptyStatement node) {
1127 addStatement(node);
1128 }
1129
1130 void visitExpressionInStatementContext(js.Expression node) {
1131 if (node is js.VariableDeclarationList) {
1132 // Treat js.VariableDeclarationList as a statement.
1133 visitVariableDeclarationList(node);
1134 } else {
1135 visitExpressionIgnoreResult(node);
1136 }
1137 }
1138
1139 @override
1140 void visitExpressionStatement(js.ExpressionStatement node) {
1141 visitExpressionInStatementContext(node.expression);
1142 }
1143
1144 @override
1145 void visitFor(js.For node) {
1146 if (!shouldTransform(node)) {
1147 bool oldInsideUntranslated = insideUntranslatedBreakable;
1148 insideUntranslatedBreakable = true;
1149 // Note that node.init, node.condition, node.update all can be null, but
1150 // withExpressions handles that.
1151 withExpressions([
1152 node.init,
1153 node.condition,
1154 node.update
1155 ], (List<js.Expression> transformed) {
1156 addStatement(new js.For(transformed[0], transformed[1], transformed[2],
1157 translateInBlock(node.body)));
1158 });
1159 insideUntranslatedBreakable = oldInsideUntranslated;
1160 return;
1161 }
1162
1163 if (node.init != null) {
1164 visitExpressionInStatementContext(node.init);
1165 }
1166 int startLabel = newLabel("for condition");
1167 // If there is no update, continuing the loop is the same as going to the
1168 // start.
1169 int continueLabel =
1170 (node.update == null) ? startLabel : newLabel("for update");
1171 continueLabels[node] = continueLabel;
1172 int afterLabel = newLabel("after for");
1173 breakLabels[node] = afterLabel;
1174 beginLabel(startLabel);
1175 js.Expression condition = node.condition;
1176 if (condition == null ||
1177 (condition is js.LiteralBool && condition.value == true)) {
1178 addStatement(new js.Comment("trivial condition"));
1179 } else {
1180 withExpression(condition, (js.Expression condition) {
1181 addStatement(new js.If.noElse(
1182 new js.Prefix("!", condition), gotoAndBreak(afterLabel)));
1183 }, store: false);
1184 }
1185 targetsAndTries.add(node);
1186 visitStatement(node.body);
1187 targetsAndTries.removeLast();
1188 if (node.update != null) {
1189 beginLabel(continueLabel);
1190 visitExpressionIgnoreResult(node.update);
1191 }
1192 addGoto(startLabel);
1193 beginLabel(afterLabel);
1194 }
1195
1196 @override
1197 void visitForIn(js.ForIn node) {
1198 // The dart output currently never uses for-in loops.
1199 throw "Javascript for-in not implemented yet in the await transformation";
1200 }
1201
1202 @override
1203 void visitFunctionDeclaration(js.FunctionDeclaration node) {
1204 unsupported(node);
1205 }
1206
1207 // Only used for code where `!shouldTransform(node)`.
1208 js.Block translateInBlock(js.Statement node) {
1209 assert(!shouldTransform(node));
1210 List<js.Statement> oldBuffer = currentStatementBuffer;
1211 currentStatementBuffer = new List();
1212 List<js.Statement> resultBuffer = currentStatementBuffer;
1213 visitStatement(node);
1214 currentStatementBuffer = oldBuffer;
1215 return new js.Block(resultBuffer);
1216 }
1217
1218 @override
1219 void visitIf(js.If node) {
1220 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) {
1221 withExpression(node.condition, (js.Expression condition) {
1222 addStatement(new js.If(condition, translateInBlock(node.then),
1223 translateInBlock(node.otherwise)));
1224 }, store: false);
1225 return;
1226 }
1227 int thenLabel = newLabel("then");
1228 int joinLabel = newLabel("join");
1229 int elseLabel =
1230 node.otherwise is js.EmptyStatement ? joinLabel : newLabel("else");
1231
1232 withExpression(node.condition, (js.Expression condition) {
1233 addExpressionStatement(
1234 new js.Assignment(
1235 new js.VariableUse(gotoName),
1236 new js.Conditional(
1237 condition,
1238 js.number(thenLabel),
1239 js.number(elseLabel))));
1240 }, store: false);
1241 addBreak();
1242 beginLabel(thenLabel);
1243 visitStatement(node.then);
1244 if (node.otherwise is! js.EmptyStatement) {
1245 addGoto(joinLabel);
1246 beginLabel(elseLabel);
1247 visitStatement(node.otherwise);
1248 }
1249 beginLabel(joinLabel);
1250 }
1251
1252 @override
1253 visitInterpolatedExpression(js.InterpolatedExpression node) {
1254 return unsupported(node);
1255 }
1256
1257 @override
1258 visitInterpolatedLiteral(js.InterpolatedLiteral node) => unsupported(node);
1259
1260 @override
1261 visitInterpolatedParameter(js.InterpolatedParameter node) {
1262 return unsupported(node);
1263 }
1264
1265 @override
1266 visitInterpolatedSelector(js.InterpolatedSelector node) {
1267 return unsupported(node);
1268 }
1269
1270 @override
1271 visitInterpolatedStatement(js.InterpolatedStatement node) {
1272 return unsupported(node);
1273 }
1274
1275 @override
1276 void visitLabeledStatement(js.LabeledStatement node) {
1277 if (!shouldTransform(node)) {
1278 addStatement(
1279 new js.LabeledStatement(node.label, translateInBlock(node.body)));
1280 return;
1281 }
1282 int breakLabel = newLabel("break ${node.label}");
1283 int continueLabel = newLabel("continue ${node.label}");
1284 breakLabels[node] = breakLabel;
1285 continueLabels[node] = continueLabel;
1286
1287 beginLabel(continueLabel);
1288 targetsAndTries.add(node);
1289 visitStatement(node.body);
1290 targetsAndTries.removeLast();
1291 beginLabel(breakLabel);
1292 }
1293
1294 @override
1295 js.Expression visitLiteralBool(js.LiteralBool node) => node;
1296
1297 @override
1298 visitLiteralExpression(js.LiteralExpression node) => unsupported(node);
1299
1300 @override
1301 js.Expression visitLiteralNull(js.LiteralNull node) => node;
1302
1303 @override
1304 js.Expression visitLiteralNumber(js.LiteralNumber node) => node;
1305
1306 @override
1307 visitLiteralStatement(js.LiteralStatement node) => unsupported(node);
1308
1309 @override
1310 js.Expression visitLiteralString(js.LiteralString node) => node;
1311
1312 @override
1313 visitNamedFunction(js.NamedFunction node) {
1314 unsupported(node);
1315 }
1316
1317 @override
1318 js.Expression visitNew(js.New node) {
1319 bool storeTarget = node.arguments.any(shouldTransform);
1320 return withExpression(node.target, (target) {
1321 return withExpressions(node.arguments, (List<js.Expression> arguments) {
1322 return new js.New(target, arguments);
1323 });
1324 }, store: storeTarget);
1325 }
1326
1327 @override
1328 js.Expression visitObjectInitializer(js.ObjectInitializer node) {
1329 return withExpressions(
1330 node.properties.map((js.Property property) => property.value).toList(),
1331 (List<js.Node> values) {
1332 List<js.Property> properties = new List.generate(values.length, (int i) {
1333 return new js.Property(node.properties[i].name, values[i]);
1334 });
1335 return new js.ObjectInitializer(properties);
1336 });
1337 }
1338
1339 @override
1340 visitParameter(js.Parameter node) => unreachable(node);
1341
1342 @override
1343 js.Expression visitPostfix(js.Postfix node) {
1344 if (node.op == "++" || node.op == "--") {
1345 js.Expression argument = node.argument;
1346 if (argument is js.VariableUse) {
1347 return new js.Postfix(node.op, argument);
1348 } else if (argument is js.PropertyAccess) {
1349 return withExpression2(argument.receiver, argument.selector,
1350 (receiver, selector) {
1351 return new js.Postfix(
1352 node.op, new js.PropertyAccess(receiver, selector));
1353 });
1354 } else {
1355 throw "Unexpected postfix ${node.op} "
1356 "operator argument ${node.argument}";
1357 }
1358 }
1359 return withExpression(node.argument,
1360 (js.Expression argument) => new js.Postfix(node.op, argument),
1361 store: false);
1362 }
1363
1364 @override
1365 js.Expression visitPrefix(js.Prefix node) {
1366 if (node.op == "++" || node.op == "--") {
1367 js.Expression argument = node.argument;
1368 if (argument is js.VariableUse) {
1369 return new js.Prefix(node.op, argument);
1370 } else if (argument is js.PropertyAccess) {
1371 return withExpression2(argument.receiver, argument.selector,
1372 (receiver, selector) {
1373 return new js.Prefix(
1374 node.op, new js.PropertyAccess(receiver, selector));
1375 });
1376 } else {
1377 throw "Unexpected prefix ${node.op} operator "
1378 "argument ${node.argument}";
1379 }
1380 }
1381 return withExpression(node.argument,
1382 (js.Expression argument) => new js.Prefix(node.op, argument),
1383 store: false);
1384 }
1385
1386 @override
1387 visitProgram(js.Program node) => unsupported(node);
1388
1389 @override
1390 js.Property visitProperty(js.Property node) {
1391 return withExpression(
1392 node.value, (js.Expression value) => new js.Property(node.name, value),
1393 store: false);
1394 }
1395
1396 @override
1397 js.Expression visitRegExpLiteral(js.RegExpLiteral node) => node;
1398
1399 @override
1400 void visitReturn(js.Return node) {
1401 assert(node.value == null || !isSyncStar && !isAsyncStar);
1402 js.Node target = analysis.targets[node];
1403 if (node.value != null) {
1404 withExpression(node.value, (js.Expression value) {
1405 addStatement(js.js.statement("$returnValueName = #", [value]));
1406 }, store: false);
1407 }
1408 translateJump(target, exitLabel);
1409 }
1410
1411 @override
1412 void visitSwitch(js.Switch node) {
1413 if (!node.cases.any(shouldTransform)) {
1414 // If only the key has an await, translation can be simplified.
1415 bool oldInsideUntranslated = insideUntranslatedBreakable;
1416 insideUntranslatedBreakable = true;
1417 withExpression(node.key, (js.Expression key) {
1418 List<js.SwitchClause> cases = node.cases.map((js.SwitchClause clause) {
1419 if (clause is js.Case) {
1420 return new js.Case(
1421 clause.expression, translateInBlock(clause.body));
1422 } else if (clause is js.Default) {
1423 return new js.Default(translateInBlock(clause.body));
1424 }
1425 }).toList();
1426 addStatement(new js.Switch(key, cases));
1427 }, store: false);
1428 insideUntranslatedBreakable = oldInsideUntranslated;
1429 return;
1430 }
1431 int before = newLabel("switch");
1432 int after = newLabel("after switch");
1433 breakLabels[node] = after;
1434
1435 beginLabel(before);
1436 List<int> labels = new List<int>(node.cases.length);
1437
1438 bool allCaseExpressionsUntransformed = !node.cases.every(
floitsch 2015/02/06 14:58:43 Remove the leading "!". Eventually there should b
floitsch 2015/02/06 14:58:43 alternatively: !node.cases.any((js.SwitchClause x)
floitsch 2015/02/06 14:58:44 put the "!node.cases.every(" into the next line.
sigurdm 2015/02/06 15:21:20 Done.
sigurdm 2015/02/06 15:21:20 The variable was named wrongly.
sigurdm 2015/02/06 15:21:21 Acknowledged.
1439 (js.SwitchClause x) {
1440 return !(x is js.Case && shouldTransform(x.expression));
1441 });
1442 if (allCaseExpressionsUntransformed) {
1443 int defaultIndex = null; // Null means no default was found.
1444 // If there is an await in one of the keys, a chain of ifs has to be used.
1445
1446 withExpression(node.key, (js.Expression key) {
1447 int i = 0;
1448 for (js.SwitchClause clause in node.cases) {
1449 if (clause is js.Default) {
1450 // The goto for the default case is added after all non-default
1451 // clauses have been handled.
1452 defaultIndex = i;
1453 labels[i] = newLabel("default");
1454 continue;
1455 } else if (clause is js.Case) {
1456 labels[i] = newLabel("case");
1457 withExpression(clause.expression, (expression) {
1458 addStatement(new js.If.noElse(
1459 new js.Binary("===", key, expression),
1460 gotoAndBreak(labels[i])));
1461 }, store: false);
1462 }
1463 i++;
1464 }
1465 }, store: true);
1466
1467 if (defaultIndex == null) {
1468 addGoto(after);
1469 } else {
1470 addGoto(labels[defaultIndex]);
1471 }
1472 } else {
1473 bool hasDefault = false;
1474 int i = 0;
1475 List<js.SwitchClause> clauses = new List<js.SwitchClause>();
1476 for (js.SwitchClause clause in node.cases) {
1477 if (clause is js.Case) {
1478 labels[i] = newLabel("case");
1479 clauses.add(new js.Case(clause.expression, gotoAndBreak(labels[i])));
1480 } else if (i is js.Default) {
1481 labels[i] = newLabel("default");
1482 clauses.add(new js.Default(gotoAndBreak(labels[i])));
1483 hasDefault = true;
1484 } else {
1485 diagnosticListener.internalError(
1486 spannable, "Unknown clause type $clause");
1487 }
1488 i++;
1489 }
1490 withExpression(node.key, (js.Expression key) {
1491 addStatement(new js.Switch(key, clauses));
1492 }, store: false);
1493 if (!hasDefault) {
1494 addGoto(after);
1495 }
1496 }
1497
1498 targetsAndTries.add(node);
1499 for (int i = 0; i < labels.length; i++) {
1500 beginLabel(labels[i]);
1501 visitStatement(node.cases[i].body);
1502 }
1503 beginLabel(after);
1504 targetsAndTries.removeLast();
1505 }
1506
1507 @override
1508 js.Expression visitThis(js.This node) {
1509 return new js.VariableUse(selfName);
1510 }
1511
1512 @override
1513 void visitThrow(js.Throw node) {
1514 withExpression(node.expression, (js.Expression expression) {
1515 addStatement(new js.Throw(expression));
1516 }, store: false);
1517 }
1518
1519 setErrorHandler() {
1520 addExpressionStatement(new js.Assignment(
1521 new js.VariableUse(handlerName), currentErrorHandler));
1522 }
1523
1524 @override
1525 void visitTry(js.Try node) {
1526 if (!shouldTransform(node)) {
1527 js.Block body = translateInBlock(node.body);
1528 js.Catch catchPart = (node.catchPart == null)
1529 ? null
1530 : new js.Catch(node.catchPart.declaration,
1531 translateInBlock(node.catchPart.body));
1532 js.Block finallyPart = (node.finallyPart == null)
1533 ? null
1534 : translateInBlock(node.finallyPart);
1535 addStatement(new js.Try(body, catchPart, finallyPart));
1536 return;
1537 }
1538 hasTryBlocks = true;
1539 int handlerLabel = newLabel("catch");
1540 int finallyLabel = newLabel("finally");
1541 int afterFinallyLabel = newLabel("after finally");
1542 errorHandlerLabels.add(handlerLabel);
1543 // Set the error handler here. It must be cleared on every path out;
1544 // normal and error exit.
1545 setErrorHandler();
1546 if (node.finallyPart != null) {
1547 finallyLabels[node] = finallyLabel;
1548 targetsAndTries.add(node);
1549 }
1550 visitStatement(node.body);
1551 errorHandlerLabels.removeLast();
1552 addStatement(
1553 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)]));
1554 if (node.finallyPart == null) {
1555 setErrorHandler();
1556 addGoto(afterFinallyLabel);
1557 } else {
1558 // The handler is set as the first thing in the finally block.
1559 addGoto(finallyLabel);
1560 }
1561 beginLabel(handlerLabel);
1562 if (node.catchPart != null) {
1563 setErrorHandler();
1564 // The catch declaration name can shadow outer variables, so a fresh name
1565 // is needed to avoid collisions. See Ecma 262, 3rd edition,
1566 // section 12.14.
1567 String errorRename = freshName(node.catchPart.declaration.name);
1568 localVariables.add(new js.VariableDeclaration(errorRename));
1569 variableRenamings
1570 .add(new Pair(node.catchPart.declaration.name, errorRename));
1571 addExpressionStatement(new js.Assignment(
1572 new js.VariableUse(errorRename), new js.VariableUse(resultName)));
1573 visitStatement(node.catchPart.body);
1574 variableRenamings.removeLast();
1575 }
1576 if (node.finallyPart != null) {
1577 targetsAndTries.removeLast();
1578 setErrorHandler();
1579 // This belongs to the catch-part, but is only needed if there is a
1580 // `finally`. Therefore it is in this branch.
1581 // This is needed even if there is no explicit catch-branch, because
1582 // if an exception is raised the finally part has to be run.
1583 addStatement(
1584 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)]));
1585 beginLabel(finallyLabel);
1586 setErrorHandler();
1587 visitStatement(node.finallyPart);
1588 addStatement(new js.Comment("// goto the next finally handler"));
1589 addStatement(js.js.statement("$gotoName = $nextName.pop();"));
1590 addBreak();
1591 }
1592 beginLabel(afterFinallyLabel);
1593 }
1594
1595 @override
1596 visitVariableDeclaration(js.VariableDeclaration node) {
1597 unreachable(node);
1598 }
1599
1600 @override
1601 void visitVariableDeclarationList(js.VariableDeclarationList node) {
1602 // Declaration of local variables is hoisted outside the helper but the
1603 // initialization is done here.
1604 for (js.VariableInitialization initialization in node.declarations) {
1605 js.VariableDeclaration declaration = initialization.declaration;
1606 localVariables.add(declaration);
1607 if (initialization.value != null) {
1608 withExpression(initialization.value, (js.Expression value) {
1609 addStatement(new js.ExpressionStatement(
1610 new js.Assignment(declaration, value)));
1611 }, store: false);
1612 }
1613 }
1614 }
1615
1616 @override
1617 void visitVariableInitialization(js.VariableInitialization node) {
1618 unreachable(node);
1619 }
1620
1621 @override
1622 js.Expression visitVariableUse(js.VariableUse node) {
1623 Pair<String, String> renaming = variableRenamings.lastWhere(
1624 (Pair renaming) => renaming.a == node.name, orElse: () => null);
1625 if (renaming == null) return node;
1626 return new js.VariableUse(renaming.b);
1627 }
1628
1629 @override
1630 void visitWhile(js.While node) {
1631 if (!shouldTransform(node)) {
1632 bool oldInsideUntranslated = insideUntranslatedBreakable;
1633 insideUntranslatedBreakable = true;
1634 withExpression(node.condition, (js.Expression condition) {
1635 addStatement(new js.While(condition, translateInBlock(node.body)));
1636 }, store: false);
1637 insideUntranslatedBreakable = oldInsideUntranslated;
1638 return;
1639 }
1640 int continueLabel = newLabel("while condition");
1641 continueLabels[node] = continueLabel;
1642 beginLabel(continueLabel);
1643
1644 int afterLabel = newLabel("after while");
1645 breakLabels[node] = afterLabel;
1646 js.Expression condition = node.condition;
1647 // If the condition is `true`, a test is not needed.
1648 if (!(condition is js.LiteralBool && condition.value == true)) {
1649 withExpression(node.condition, (js.Expression condition) {
1650 addStatement(new js.If.noElse(
1651 new js.Prefix("!", condition), gotoAndBreak(afterLabel)));
1652 }, store: false);
1653 }
1654 targetsAndTries.add(node);
1655 visitStatement(node.body);
1656 targetsAndTries.removeLast();
1657 addGoto(continueLabel);
1658 beginLabel(afterLabel);
1659 }
1660
1661 /// Translates a yield/yield* in an sync*.
1662 ///
1663 /// `yield` in a sync* function just returns [value].
1664 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it.
1665 void addSyncYield(js.DartYield node, js.Expression expression) {
1666 assert(isSyncStar);
1667 if (node.hasStar) {
1668 addStatement(
1669 new js.Return(new js.Call(yieldStarExpression, [expression])));
1670 } else {
1671 addStatement(new js.Return(expression));
1672 }
1673 }
1674
1675 /// Translates a yield/yield* in an async* function.
1676 ///
1677 /// yield/yield* in an async* function is translated much like the `await` is
1678 /// translated in [visitAwait], only the object is wrapped in a
1679 /// [yieldExpression]/[yieldStarExpression] to let [streamHelper] distinguish.
1680 ///
1681 /// Because there is no Future that can fail (as there is in await) null is
1682 /// passed as the errorCallback.
1683 void addAsyncYield(js.DartYield node, js.Expression expression) {
1684 assert(isAsyncStar);
1685 // Find all the finally blocks that should be performed if the stream is
1686 // canceled during the yield.
1687 // At the bottom of the stack is the return label.
1688 List<int> enclosingFinallyLabels = <int>[exitLabel];
1689 enclosingFinallyLabels.addAll(targetsAndTries
1690 .where((js.Node node) => node is js.Try)
1691 .map((js.Try node) => finallyLabels[node]));
1692 int destinationOnCancel = enclosingFinallyLabels.removeLast();
1693 js.ArrayInitializer finallyListInitializer = new js.ArrayInitializer(
1694 enclosingFinallyLabels.map(js.number).toList());
1695 addStatement(js.js.statement("""
1696 return #streamHelper(#yieldExpression(#expression),
1697 $helperName, $controllerName, function () {
1698 if (#notEmptyFinallyList)
1699 $nextName = #finallyList;
1700 $gotoName = #destinationOnCancel;
1701 $helperName();
1702 });""", {
1703 "streamHelper": streamHelper,
1704 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression,
1705 "expression": expression,
1706 "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty,
1707 "finallyList": finallyListInitializer,
1708 "destinationOnCancel": js.number(destinationOnCancel)
1709 }));
1710 }
1711
1712 @override
1713 void visitDartYield(js.DartYield node) {
1714 assert(isSyncStar || isAsyncStar);
1715 int label = newLabel("after yield");
1716 // Don't do a break here for the goto, but instead a return in either
1717 // addSynYield or addAsyncYield.
1718 withExpression(node.expression, (js.Expression expression) {
1719 addStatement(setGotoVariable(label));
1720 if (isSyncStar) {
1721 addSyncYield(node, expression);
1722 } else {
1723 addAsyncYield(node, expression);
1724 }
1725 }, store: false);
1726 beginLabel(label);
1727 }
1728 }
1729
1730 /// Finds out
1731 ///
1732 /// - which expressions have yield or await nested in them.
1733 /// - targets of jumps
1734 /// - a set of used names.
1735 /// - if any [This]-expressions are used.
1736 class PreTranslationAnalysis extends js.NodeVisitor<bool> {
1737 Set<js.Node> hasAwaitOrYield = new Set<js.Node>();
1738
1739 Map<js.Node, js.Node> targets = new Map<js.Node, js.Node>();
1740 List<js.Node> loopsAndSwitches = new List<js.Node>();
1741 List<js.LabeledStatement> labelledStatements =
1742 new List<js.LabeledStatement>();
1743 Set<String> usedNames = new Set<String>();
1744
1745 bool hasExplicitReturns = false;
1746
1747 bool hasThis = false;
1748
1749 // The function currently being analyzed.
1750 js.Fun currentFunction;
1751
1752 // For error messages.
1753 final Function unsupported;
1754
1755 PreTranslationAnalysis(void this.unsupported(js.Node node));
1756
1757 bool visit(js.Node node) {
1758 bool containsAwait = node.accept(this);
1759 if (containsAwait) {
1760 hasAwaitOrYield.add(node);
1761 }
1762 return containsAwait;
1763 }
1764
1765 analyze(js.Fun node) {
1766 currentFunction = node;
1767 node.params.forEach(visit);
1768 visit(node.body);
1769 }
1770
1771 @override
1772 bool visitAccess(js.PropertyAccess node) {
1773 bool receiver = visit(node.receiver);
1774 bool selector = visit(node.selector);
1775 return receiver || selector;
1776 }
1777
1778 @override
1779 bool visitArrayHole(js.ArrayHole node) {
1780 return false;
1781 }
1782
1783 @override
1784 bool visitArrayInitializer(js.ArrayInitializer node) {
1785 bool containsAwait = false;
1786 for (js.Expression element in node.elements) {
1787 if (visit(element)) containsAwait = true;
1788 }
1789 return containsAwait;
1790 }
1791
1792 @override
1793 bool visitAssignment(js.Assignment node) {
1794 bool leftHandSide = visit(node.leftHandSide);
1795 bool value = (node.value == null) ? false : visit(node.value);
1796 return leftHandSide || value;
1797 }
1798
1799 @override
1800 bool visitAwait(js.Await node) {
1801 visit(node.expression);
1802 return true;
1803 }
1804
1805 @override
1806 bool visitBinary(js.Binary node) {
1807 bool left = visit(node.left);
1808 bool right = visit(node.right);
1809 return left || right;
1810 }
1811
1812 @override
1813 bool visitBlob(js.Blob node) {
1814 return false;
1815 }
1816
1817 @override
1818 bool visitBlock(js.Block node) {
1819 bool containsAwait = false;
1820 for (js.Statement statement in node.statements) {
1821 if (visit(statement)) containsAwait = true;
1822 }
1823 return containsAwait;
1824 }
1825
1826 @override
1827 bool visitBreak(js.Break node) {
1828 if (node.targetLabel != null) {
1829 targets[node] = labelledStatements.lastWhere(
1830 (js.LabeledStatement statement) {
1831 return statement.label == node.targetLabel;
1832 });
1833 } else {
1834 targets[node] = loopsAndSwitches.last;
1835 }
1836 return false;
1837 }
1838
1839 @override
1840 bool visitCall(js.Call node) {
1841 bool containsAwait = visit(node.target);
1842 for (js.Expression argument in node.arguments) {
1843 if (visit(argument)) containsAwait = true;
1844 }
1845 return containsAwait;
1846 }
1847
1848 @override
1849 bool visitCase(js.Case node) {
1850 bool expression = visit(node.expression);
1851 bool body = visit(node.body);
1852 return expression || body;
1853 }
1854
1855 @override
1856 bool visitCatch(js.Catch node) {
1857 bool declaration = visit(node.declaration);
1858 bool body = visit(node.body);
1859 return declaration || body;
1860 }
1861
1862 @override
1863 bool visitComment(js.Comment node) {
1864 return false;
1865 }
1866
1867 @override
1868 bool visitConditional(js.Conditional node) {
1869 bool condition = visit(node.condition);
1870 bool then = visit(node.then);
1871 bool otherwise = visit(node.otherwise);
1872 return condition || then || otherwise;
1873 }
1874
1875 @override
1876 bool visitContinue(js.Continue node) {
1877 if (node.targetLabel != null) {
1878 targets[node] = labelledStatements.lastWhere(
1879 (js.LabeledStatement stm) => stm.label == node.targetLabel);
1880 } else {
1881 targets[node] =
1882 loopsAndSwitches.lastWhere((js.Node node) => node is! js.Switch);
1883 }
1884 assert(() {
1885 js.Node target = targets[node];
1886 return target is js.Loop ||
1887 (target is js.LabeledStatement && target.body is js.Loop);
1888 });
1889 return false;
1890 }
1891
1892 @override
1893 bool visitDefault(js.Default node) {
1894 return visit(node.body);
1895 }
1896
1897 @override
1898 bool visitDo(js.Do node) {
1899 loopsAndSwitches.add(node);
1900 bool body = visit(node.body);
1901 bool condition = visit(node.condition);
1902 loopsAndSwitches.removeLast();
1903 return body || condition;
1904 }
1905
1906 @override
1907 bool visitEmptyStatement(js.EmptyStatement node) {
1908 return false;
1909 }
1910
1911 @override
1912 bool visitExpressionStatement(js.ExpressionStatement node) {
1913 return visit(node.expression);
1914 }
1915
1916 @override
1917 bool visitFor(js.For node) {
1918 bool init = (node.init == null) ? false : visit(node.init);
1919 bool condition = (node.condition == null) ? false : visit(node.condition);
1920 bool update = (node.update == null) ? false : visit(node.update);
1921 loopsAndSwitches.add(node);
1922 bool body = visit(node.body);
1923 loopsAndSwitches.removeLast();
1924 return init || condition || update || body;
1925 }
1926
1927 @override
1928 bool visitForIn(js.ForIn node) {
1929 bool object = visit(node.object);
1930 loopsAndSwitches.add(node);
1931 bool body = visit(node.body);
1932 loopsAndSwitches.removeLast();
1933 return object || body;
1934 }
1935
1936 @override
1937 bool visitFun(js.Fun node) {
1938 return false;
1939 }
1940
1941 @override
1942 bool visitFunctionDeclaration(js.FunctionDeclaration node) {
1943 return false;
1944 }
1945
1946 @override
1947 bool visitIf(js.If node) {
1948 bool condition = visit(node.condition);
1949 bool then = visit(node.then);
1950 bool otherwise = visit(node.otherwise);
1951 return condition || then || otherwise;
1952 }
1953
1954 @override
1955 bool visitInterpolatedExpression(js.InterpolatedExpression node) {
1956 return unsupported(node);
1957 }
1958
1959 @override
1960 bool visitInterpolatedLiteral(js.InterpolatedLiteral node) {
1961 return unsupported(node);
1962 }
1963
1964 @override
1965 bool visitInterpolatedParameter(js.InterpolatedParameter node) {
1966 return unsupported(node);
1967 }
1968
1969 @override
1970 bool visitInterpolatedSelector(js.InterpolatedSelector node) {
1971 return unsupported(node);
1972 }
1973
1974 @override
1975 bool visitInterpolatedStatement(js.InterpolatedStatement node) {
1976 return unsupported(node);
1977 }
1978
1979 @override
1980 bool visitLabeledStatement(js.LabeledStatement node) {
1981 usedNames.add(node.label);
1982 labelledStatements.add(node);
1983 bool containsAwait = visit(node.body);
1984 labelledStatements.removeLast();
1985 return containsAwait;
1986 }
1987
1988 @override
1989 bool visitLiteralBool(js.LiteralBool node) {
1990 return false;
1991 }
1992
1993 @override
1994 bool visitLiteralExpression(js.LiteralExpression node) {
1995 return unsupported(node);
1996 }
1997
1998 @override
1999 bool visitLiteralNull(js.LiteralNull node) {
2000 return false;
2001 }
2002
2003 @override
2004 bool visitLiteralNumber(js.LiteralNumber node) {
2005 return false;
2006 }
2007
2008 @override
2009 bool visitLiteralStatement(js.LiteralStatement node) {
2010 return unsupported(node);
2011 }
2012
2013 @override
2014 bool visitLiteralString(js.LiteralString node) {
2015 return false;
2016 }
2017
2018 @override
2019 bool visitNamedFunction(js.NamedFunction node) {
2020 return false;
2021 }
2022
2023 @override
2024 bool visitNew(js.New node) {
2025 return visitCall(node);
2026 }
2027
2028 @override
2029 bool visitObjectInitializer(js.ObjectInitializer node) {
2030 bool containsAwait = false;
2031 for (js.Property property in node.properties) {
2032 if (visit(property)) containsAwait = true;
2033 }
2034 return containsAwait;
2035 }
2036
2037 @override
2038 bool visitParameter(js.Parameter node) {
2039 usedNames.add(node.name);
2040 return false;
2041 }
2042
2043 @override
2044 bool visitPostfix(js.Postfix node) {
2045 return visit(node.argument);
2046 }
2047
2048 @override
2049 bool visitPrefix(js.Prefix node) {
2050 return visit(node.argument);
2051 }
2052
2053 @override
2054 bool visitProgram(js.Program node) {
2055 throw "Unexpected";
2056 }
2057
2058 @override
2059 bool visitProperty(js.Property node) {
2060 return visit(node.value);
2061 }
2062
2063 @override
2064 bool visitRegExpLiteral(js.RegExpLiteral node) {
2065 return false;
2066 }
2067
2068 @override
2069 bool visitReturn(js.Return node) {
2070 hasExplicitReturns = true;
2071 targets[node] = currentFunction;
2072 if (node.value == null) return false;
2073 return visit(node.value);
2074 }
2075
2076 @override
2077 bool visitSwitch(js.Switch node) {
2078 loopsAndSwitches.add(node);
2079 bool result = visit(node.key);
2080 for (js.SwitchClause clause in node.cases) {
2081 if (visit(clause)) result = true;
2082 }
2083 loopsAndSwitches.removeLast();
2084 return result;
2085 }
2086
2087 @override
2088 bool visitThis(js.This node) {
2089 hasThis = true;
2090 return false;
2091 }
2092
2093 @override
2094 bool visitThrow(js.Throw node) {
2095 return visit(node.expression);
2096 }
2097
2098 @override
2099 bool visitTry(js.Try node) {
2100 bool body = visit(node.body);
2101 bool catchPart = (node.catchPart == null) ? false : visit(node.catchPart);
2102 bool finallyPart =
2103 (node.finallyPart == null) ? false : visit(node.finallyPart);
2104 return body || catchPart || finallyPart;
2105 }
2106
2107 @override
2108 bool visitVariableDeclaration(js.VariableDeclaration node) {
2109 usedNames.add(node.name);
2110 return false;
2111 }
2112
2113 @override
2114 bool visitVariableDeclarationList(js.VariableDeclarationList node) {
2115 bool result = false;
2116 for (js.VariableInitialization init in node.declarations) {
2117 if (visit(init)) result = true;
2118 }
2119 return result;
2120 }
2121
2122 @override
2123 bool visitVariableInitialization(js.VariableInitialization node) {
2124 return visitAssignment(node);
2125 }
2126
2127 @override
2128 bool visitVariableUse(js.VariableUse node) {
2129 usedNames.add(node.name);
2130 return false;
2131 }
2132
2133 @override
2134 bool visitWhile(js.While node) {
2135 loopsAndSwitches.add(node);
2136 bool condition = visit(node.condition);
2137 bool body = visit(node.body);
2138 loopsAndSwitches.removeLast();
2139 return condition || body;
2140 }
2141
2142 @override
2143 bool visitDartYield(js.DartYield node) {
2144 visit(node.expression);
2145 return true;
2146 }
2147 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/js/printer.dart ('k') | pkg/compiler/lib/src/js/template.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698