OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, 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 /** | |
6 * Desugarizes all await calls in a single function. This visitor assumes that | |
7 * the tree is already normalized with [AwaitNormalizer]. For this reason it | |
8 * only needs to recurse on statements and not on expressions. Like in | |
9 * [AwaitChecker], nested functions have to be processed separately. | |
10 */ | |
11 class AwaitProcessor implements TreeVisitor { | |
12 | |
13 /** | |
14 * Name of the variable introduced on asynchronous functions (of type | |
15 * [Completer] used to create a future of the function's result. | |
16 */ | |
17 // TODO(sigmund): fix frog to make it possible to switch to '_a:res'. The | |
18 // mangling code currently breaks across closure-boundaries. | |
19 static final _PREFIX = '_a_'; | |
20 static final _COMPLETER_NAME = _PREFIX + 'res'; | |
21 static final _THEN_PARAM = _PREFIX + 'v'; | |
22 static final _EXCEPTION_HANDLER_PARAM = _PREFIX + 'e'; | |
23 static final _IGNORED_THEN_PARAM = _PREFIX + 'ignored_param'; | |
24 static final _CONTINUATION_PREFIX = _PREFIX + 'after_'; | |
25 | |
26 static final _COMPLETE_METHOD = 'complete'; | |
27 static final _COMPLETE_EXCEPTION_METHOD = 'completeException'; | |
28 | |
29 | |
30 /** The continuation when visiting a particular statement. */ | |
31 Queue<Statement> continuation; | |
32 | |
33 /** Closures to call when a future throws an exception (in reverse order). */ | |
34 List<Identifier> exceptionHandlers; | |
35 | |
36 /** All try-catch blocks enclosing the current statement. */ | |
37 List<TryStatement> enclosingTrys; | |
38 | |
39 /** Counter to ensure created closure names are unique. */ | |
40 int continuationClosures = 0; | |
41 | |
42 /** Nodes containing await expressions (determined by [AwaitChecker]). */ | |
43 final NodeSet haveAwait; | |
44 | |
45 AwaitProcessor(this.haveAwait) | |
46 : continuation = new Queue<Statement>(), | |
47 exceptionHandlers = [], | |
48 enclosingTrys = []; | |
49 | |
50 visitVariableDefinition(VariableDefinition node) { | |
51 if (!haveAwait.contains(node)) return node; | |
52 final values = node.values; | |
53 // After normalization, variable declarations with await can only occur at | |
54 // the top-level and there is no other declaration. | |
55 assert(values != null && values.length == 1 | |
56 && values[0] is AwaitExpression); | |
57 final param = new Identifier(_THEN_PARAM, node.span); | |
58 // T x = await t; ... becomes: | |
59 // t.then((v) { T x = v; ... } | |
60 continuation.addFirst(new VariableDefinition( | |
61 node.modifiers, node.type, node.names, | |
62 [new VarExpression(param, node.span)], node.span)); | |
63 return new ExpressionStatement( | |
64 _desugarAwaitCall(values[0], param), node.span); | |
65 } | |
66 | |
67 visitFunctionDefinition(FunctionDefinition node) { | |
68 if (!haveAwait.contains(node)) return node; | |
69 // TODO(sigmund): consider making this part of the normalizer | |
70 // make implicit return explicit: | |
71 if (node.body is BlockStatement) { | |
72 BlockStatement block = node.body; | |
73 if (block.body.last() is! ReturnStatement) { | |
74 continuation.addFirst( | |
75 _callCompleter(_makeNull(node.span), node.span)); | |
76 } | |
77 } | |
78 | |
79 Statement newBody = node.body.visit(this); | |
80 // TODO(sigmund): extract type arg and put it in completer | |
81 // We update the body in-place to make it easier to update nested functions | |
82 // without having to rewrite the containing function's AST. | |
83 node.body = new BlockStatement([ | |
84 _declareCompleter(null, node.span), | |
85 _wrapInTryCatch(newBody, node == _mainMethod.definition), | |
86 _returnFuture(node.span)], newBody.span); | |
87 return node; | |
88 } | |
89 | |
90 visitReturnStatement(ReturnStatement node) { | |
91 continuation.clear(); | |
92 return _callCompleter(node.value, node.span); | |
93 } | |
94 | |
95 visitThrowStatement(ThrowStatement node) { | |
96 // instead of calling the exception handler here, we take a different | |
97 // approach and use throw directly. This helps make the try-catch | |
98 // transformation simpler. | |
99 continuation.clear(); | |
100 return node; | |
101 } | |
102 | |
103 visitAssertStatement(AssertStatement node) { | |
104 return node; | |
105 } | |
106 | |
107 visitBreakStatement(BreakStatement node) { | |
108 // TODO(sigmund): implement | |
109 return node; | |
110 } | |
111 | |
112 visitContinueStatement(ContinueStatement node) { | |
113 // TODO(sigmund): implement | |
114 return node; | |
115 } | |
116 | |
117 visitIfStatement(IfStatement node) { | |
118 if (!haveAwait.contains(node)) return node; | |
119 // TODO(sigmund): consider whether we should create this continuation | |
120 // closure when there are few statements following (e.g a simple expression | |
121 // statement, no loops, etc). | |
122 String afterIf = _newClosureName(_CONTINUATION_PREFIX + "_if"); | |
123 Statement def = _makeContinuation(afterIf, node.span); | |
124 | |
125 final trueContinuation = new Queue(); | |
126 trueContinuation.addFirst(_callNoArg(afterIf, node.span)); | |
127 continuation = trueContinuation; | |
128 Statement tRes = node.trueBranch.visit(this); | |
129 | |
130 Statement fRes = null; | |
131 if (node.falseBranch != null) { | |
132 final falseContinuation = new Queue(); | |
133 falseContinuation.addFirst(_callNoArg(afterIf, node.span)); | |
134 continuation = falseContinuation; | |
135 fRes = node.falseBranch.visit(this); | |
136 continuation = new Queue(); | |
137 } else { | |
138 continuation = new Queue(); | |
139 continuation.addFirst(_callNoArg(afterIf, node.span)); | |
140 } | |
141 | |
142 continuation.addFirst(new IfStatement(node.test, tRes, fRes, node.span)); | |
143 return def; | |
144 } | |
145 | |
146 visitWhileStatement(WhileStatement node) { | |
147 if (!haveAwait.contains(node)) return node; | |
148 | |
149 String afterWhile = _newClosureName(_CONTINUATION_PREFIX + "_while"); | |
150 Statement def = _makeContinuation(afterWhile, node.span); | |
151 String repeatWhile = _newClosureName(_PREFIX + "_while"); | |
152 | |
153 final bodyContinuation = new Queue(); | |
154 bodyContinuation.addFirst(_callNoArg(repeatWhile, node.span)); | |
155 continuation = bodyContinuation; | |
156 Statement body = node.body.visit(this); | |
157 | |
158 continuation = new Queue(); | |
159 continuation.addFirst(_callNoArg(afterWhile, node.span)); | |
160 continuation.addFirst(new IfStatement(node.test, body, null, node.span)); | |
161 Statement defLoop = _makeContinuation(repeatWhile, node.span); | |
162 | |
163 continuation = new Queue(); | |
164 continuation.addFirst(_callNoArg(repeatWhile, node.span)); | |
165 continuation.addFirst(defLoop); | |
166 return def; | |
167 } | |
168 | |
169 visitDoStatement(DoStatement node) { | |
170 if (!haveAwait.contains(node)) return node; | |
171 // TODO(sigmund): implement | |
172 return node; | |
173 } | |
174 | |
175 visitForStatement(ForStatement node) { | |
176 if (!haveAwait.contains(node)) return node; | |
177 // TODO(sigmund): implement | |
178 // Note: this is harder than while loops because of dart's special semantics | |
179 // capturing the loop variable. | |
180 return node; | |
181 } | |
182 | |
183 visitForInStatement(ForInStatement node) { | |
184 if (!haveAwait.contains(node)) return node; | |
185 // TODO(sigmund): implement | |
186 // Note: this is harder than while loops because of dart's special semantics | |
187 // capturing the loop variable. | |
188 return node; | |
189 } | |
190 | |
191 visitTryStatement(TryStatement node) { | |
192 if (!haveAwait.contains(node)) return node; | |
193 // TODO(sigmund): pending to do on try-catch blocks | |
194 // - consider when await shows in catch blocks, but not in the try block | |
195 // - support exceptions after the await | |
196 // - handle nested try blocks | |
197 // - support finally | |
198 // - consider throws within catch blocks, e.g: | |
199 // try { a } catch (E e1) { throw new E2(); } catch (E2 e2) { -- } | |
200 | |
201 String afterTry = _newClosureName(_CONTINUATION_PREFIX + "_try"); | |
202 Statement afterTryDef = _makeContinuation(afterTry, node.span); | |
203 | |
204 final defs = []; // closures for each catch block (avoid duplicating code). | |
205 final catches = []; // catch clauses of the transformed try-catch block | |
206 | |
207 // Catch blocks are passed as an exception handler on de-sugared awaits: | |
208 final handlerName = new Identifier( | |
209 _newClosureName(_PREFIX + "exception_handler"), node.span); | |
210 // TODO(sigmund): add trace argument (library change in Future<T>) | |
211 final handlerArg = new Identifier(_EXCEPTION_HANDLER_PARAM, node.span); | |
212 final handlerBody = []; | |
213 | |
214 // Transform each catch-block internally. | |
215 bool untypedCatch = false; // When true, the exception handler is smaller. | |
216 for (CatchNode n in node.catches) { | |
217 String fname = _newClosureName(_PREFIX + "catch"); | |
218 | |
219 // Code in transformed catch-block: | |
220 continuation = new Queue(); | |
221 continuation.addFirst(_callNoArg(afterTry, node.span)); | |
222 continuation.addFirst(n.body.visit(this)); | |
223 | |
224 defs.add(_makeCatchFunction(n, fname)); | |
225 catches.add(new CatchNode(n.exception, n.trace, | |
226 new BlockStatement( | |
227 [_callCatchFunction(n, fname), _returnFuture(n.span)], n.span), | |
228 n.span)); | |
229 | |
230 // Code in exception handler: | |
231 if (!untypedCatch) { | |
232 final exceptionHandlerCases = [ | |
233 _callCatchFunctionHelper(fname, handlerArg, null, n.span), | |
234 _returnBoolean(n.span, true)]; | |
235 if (n.exception.type == null) { | |
236 handlerBody.addAll(exceptionHandlerCases); | |
237 untypedCatch = true; | |
238 } else { | |
239 handlerBody.add(new IfStatement( | |
240 new IsExpression(true, | |
241 new VarExpression(handlerArg, n.span), | |
242 n.exception.type, n.span), | |
243 new BlockStatement(exceptionHandlerCases, n.span), | |
244 null, n.span)); | |
245 } | |
246 } | |
247 } | |
248 | |
249 if (!untypedCatch) { | |
250 handlerBody.add(_returnBoolean(node.span, false)); | |
251 } | |
252 | |
253 // Declare the exception handler | |
254 final handlerDef = new FunctionDefinition([], null, handlerName, | |
255 [new FormalNode(false, false, null, handlerArg, null, node.span)], | |
256 null, null, new BlockStatement(handlerBody, node.span), node.span); | |
257 | |
258 // Transform the try body, tracking the enclosing try blocks and | |
259 // exception handlers. | |
260 enclosingTrys.addLast(new TryStatement( | |
261 null /* this is replaced with code in [_desugarAwaitCall] */, | |
262 catches, node.finallyBlock, node.span)); | |
263 exceptionHandlers.addLast(handlerName); | |
264 continuation = new Queue(); | |
265 continuation.addFirst(_callNoArg(afterTry, node.span)); | |
266 Statement body = node.body.visit(this); | |
267 exceptionHandlers.removeLast(); | |
268 enclosingTrys.removeLast(); | |
269 | |
270 continuation = new Queue(); | |
271 continuation.addAll(defs); | |
272 continuation.add(handlerDef); | |
273 continuation.add(new TryStatement(body, | |
274 catches, node.finallyBlock, node.span)); | |
275 continuation.add(_callNoArg(afterTry, node.span)); | |
276 return afterTryDef; | |
277 } | |
278 | |
279 /** | |
280 * Create a closure representing the catch block. The closure takes either 1 | |
281 * or 2 args, depending on whether [n] has a `trace` declaration. | |
282 */ | |
283 Statement _makeCatchFunction(CatchNode n, String fname) { | |
284 if (n.trace == null) { | |
285 return _makeContinuation1(fname, | |
286 n.exception.type, n.exception.name, n.span); | |
287 } else { | |
288 return _makeContinuation2(fname, | |
289 n.exception.type, n.exception.name, | |
290 n.trace.type, n.trace.name, n.span); | |
291 } | |
292 } | |
293 | |
294 /** Calls the catch function declared for [n] using [_makeCatchFunction]. */ | |
295 Statement _callCatchFunction(CatchNode n, String fname) { | |
296 return _callCatchFunctionHelper(fname, n.exception.name, | |
297 n.trace == null ? null : n.trace.name, n.span); | |
298 } | |
299 | |
300 /** Helper to call a catch function declared using [_makeCatchFunction]. */ | |
301 Statement _callCatchFunctionHelper( | |
302 String fname, Identifier exception, Identifier trace, SourceSpan span) { | |
303 if (trace == null) { | |
304 return _call1Arg(fname, | |
305 new VarExpression(exception, exception.span), span); | |
306 } else { | |
307 return _call2Args(fname, new VarExpression(exception, exception.span), | |
308 new VarExpression(trace, trace.span), span); | |
309 } | |
310 } | |
311 | |
312 visitSwitchStatement(SwitchStatement node) { | |
313 if (!haveAwait.contains(node)) return node; | |
314 // TODO(sigmund): implement | |
315 return node; | |
316 } | |
317 | |
318 visitBlockStatement(BlockStatement node) { | |
319 if (!haveAwait.contains(node) && continuation.isEmpty()) return node; | |
320 // TODO(sigmund): test also when !continuation.isEmpty(); | |
321 for (int i = node.body.length - 1; i >= 0; i--) { | |
322 final res = node.body[i].visit(this); | |
323 // Note: we can't inline [res] in the line below because [continuation] | |
324 // might be redefined and we want to use the latest reference after we | |
325 // finish visiting [node.body]. | |
326 continuation.addFirst(res); | |
327 } | |
328 | |
329 List<Statement> newBody = []; | |
330 newBody.addAll(continuation); | |
331 continuation.clear(); | |
332 return new BlockStatement(newBody, node.span); | |
333 } | |
334 | |
335 visitLabeledStatement(LabeledStatement node) { | |
336 if (!haveAwait.contains(node)) return node; | |
337 // TODO(sigmund): implement | |
338 return node; | |
339 } | |
340 | |
341 visitExpressionStatement(ExpressionStatement node) { | |
342 if (!haveAwait.contains(node)) return node; | |
343 // After normalization, expression statements with await can only occur at | |
344 // the top-level or as the rhs of simple assignments (= but not +=). | |
345 if (node.body is AwaitExpression) { | |
346 return new ExpressionStatement( | |
347 _desugarAwaitCall(node.body, | |
348 new Identifier(_IGNORED_THEN_PARAM, node.span)), node.span); | |
349 } else { | |
350 assert(node.body is BinaryExpression); | |
351 BinaryExpression bin = node.body; | |
352 assert(bin.op.kind == TokenKind.ASSIGN); | |
353 assert(bin.y is AwaitExpression); | |
354 final param = new Identifier(_THEN_PARAM, node.span); | |
355 continuation.addFirst(new ExpressionStatement( | |
356 new BinaryExpression( | |
357 bin.op, bin.x, new VarExpression(param, node.span), node.span), | |
358 node.span)); | |
359 return new ExpressionStatement( | |
360 _desugarAwaitCall(bin.y, param), node.span); | |
361 } | |
362 } | |
363 | |
364 visitEmptyStatement(EmptyStatement node) { | |
365 if (!haveAwait.contains(node)) return node; | |
366 return node; | |
367 } | |
368 | |
369 visitArgumentNode(ArgumentNode node) { | |
370 if (!haveAwait.contains(node)) return node; | |
371 return node; | |
372 } | |
373 | |
374 visitCatchNode(CatchNode node) { | |
375 // shouldn't reach here. | |
376 world.fatal("'catch' should be handled with the enclosing try statement", | |
377 node.span); | |
378 } | |
379 | |
380 visitCaseNode(CaseNode node) { | |
381 if (!haveAwait.contains(node)) return node; | |
382 return node; | |
383 } | |
384 | |
385 /** | |
386 * Converts an await expression into several statements: calling | |
387 * [:Future.then:] and propatating errors. This implementation assumes that | |
388 * await calls are within blocks (after normalization). | |
389 */ | |
390 CallExpression _desugarAwaitCall(AwaitExpression node, Identifier param) { | |
391 List<Statement> afterAwait = []; | |
392 afterAwait.addAll(continuation); | |
393 if (afterAwait.last() is ReturnStatement) { | |
394 // The only reason there is a `return` is because there was another await | |
395 // and we introduced it. Such `return` is not needed in the callback. | |
396 afterAwait.removeLast(); | |
397 } | |
398 // Within try-blocks, we wrap the continuation in a try-catch block: | |
399 Statement thenBody = new BlockStatement(afterAwait, node.span); | |
400 for (int i = enclosingTrys.length - 1; i >= 0; i--) { | |
401 final templateTry = enclosingTrys[i]; | |
402 thenBody = new TryStatement(thenBody, | |
403 templateTry.catches, templateTry.finallyBlock, templateTry.span); | |
404 } | |
405 | |
406 // A lambda function that executes the continuation. | |
407 final thenArg = new LambdaExpression( | |
408 new FunctionDefinition([], null, null, | |
409 [new FormalNode( | |
410 false, false, null /* infer type from body? */, | |
411 param, null, param.span) | |
412 ], null, null, | |
413 thenBody, node.span), | |
414 node.span); | |
415 | |
416 continuation.clear(); | |
417 continuation.addFirst(_returnFuture(node.span)); | |
418 // Within try-blocks, we also add an exception handler to propagate errors. | |
419 for (final handlerName in exceptionHandlers) { | |
420 continuation.addFirst(new ExpressionStatement(new CallExpression( | |
421 new DotExpression(node.body, | |
422 new Identifier('handleException', node.span), node.span), | |
423 [new ArgumentNode(null, | |
424 new VarExpression(handlerName, node.span), | |
425 node.span)], | |
426 node.span), node.span)); | |
427 } | |
428 return _callThen(node.body, thenArg, node.span); | |
429 } | |
430 | |
431 /** Make the call expression: [: future.then(arg); :] */ | |
432 CallExpression _callThen(Expression future, Expression arg, SourceSpan span) { | |
433 return new CallExpression( | |
434 new DotExpression(future, new Identifier('then', span), span), | |
435 [new ArgumentNode(null, arg, span)], span); | |
436 } | |
437 | |
438 /** Make the statement: [: final Completer<T> v = new Completer<T>(); :]. */ | |
439 VariableDefinition _declareCompleter(Type argType, SourceSpan span) { | |
440 final name = new Identifier('Completer', span); | |
441 final ctorName = new Identifier('', span); | |
442 var typeRef = new NameTypeReference(false, name, [ctorName], span); | |
443 var type = world.corelib.types['Completer']; | |
444 if (argType != null) { | |
445 typeRef = new GenericTypeReference(typeRef, | |
446 new TypeReference(span, argType), 0, span); | |
447 typeRef.type = type.getOrMakeConcreteType([argType]); | |
448 } else { | |
449 typeRef.type = type; | |
450 } | |
451 final def = new VariableDefinition([new Token.fake(TokenKind.FINAL, span)], | |
452 typeRef, | |
453 [new Identifier(_COMPLETER_NAME, span)], | |
454 [new NewExpression(false, typeRef, null, [], span)], | |
455 span); | |
456 return def; | |
457 } | |
458 | |
459 /** | |
460 * Wrap [s] in a try-catch block that propagates errors through the future | |
461 * that is returned from the asynchronous function. If the function that we | |
462 * are generating is `main`, we add a noop listener on the resulting future. | |
463 * Without it, we would have to make it illegal to use `await` in main. This | |
464 * is because futures swallow exceptions if their values are never used. | |
465 */ | |
466 Statement _wrapInTryCatch(Statement s, bool isMain) { | |
467 final ex = new Identifier("ex", s.span); | |
468 Statement catchStatement = | |
469 _callCompleterException(new VarExpression(ex, ex.span), s.span); | |
470 if (isMain) { | |
471 final future = new DotExpression( | |
472 new VarExpression(new Identifier(_COMPLETER_NAME, s.span), s.span), | |
473 new Identifier("future", s.span), s.span); | |
474 final noopHandler = new LambdaExpression( | |
475 new FunctionDefinition([], null, null, | |
476 [new FormalNode(false, false, null, | |
477 new Identifier(_IGNORED_THEN_PARAM, s.span), null, s.span) | |
478 ], null, null, | |
479 new BlockStatement([], s.span), s.span), s.span); | |
480 catchStatement = new BlockStatement([ | |
481 // _a_res.future.then((_ignored_param) { }); | |
482 new ExpressionStatement( | |
483 _callThen(future, noopHandler, s.span), s.span), | |
484 catchStatement], s.span); | |
485 } | |
486 return new TryStatement(s, [ | |
487 new CatchNode(new DeclaredIdentifier(null, ex, ex.span), null, | |
488 catchStatement, s.span)], null, s.span); | |
489 } | |
490 | |
491 /** Make the statement: [: _a$res.complete(value); :]. */ | |
492 _callCompleter(Expression value, SourceSpan span) { | |
493 return _callTarget1Arg(_COMPLETER_NAME, _COMPLETE_METHOD, value, span); | |
494 } | |
495 | |
496 /** Make the statement: [: _a$res.completeException(value); :]. */ | |
497 _callCompleterException(Expression value, SourceSpan span) { | |
498 return _callTarget1Arg( | |
499 _COMPLETER_NAME, _COMPLETE_EXCEPTION_METHOD, value, span); | |
500 } | |
501 | |
502 /** Make the statement: [: return _a$res.future; :]. */ | |
503 _returnFuture(SourceSpan span) { | |
504 return new ReturnStatement( | |
505 new DotExpression( | |
506 new VarExpression(new Identifier(_COMPLETER_NAME, span), span), | |
507 new Identifier("future", span), span), span); | |
508 } | |
509 | |
510 /** Create a unique name for a continuation. */ | |
511 String _newClosureName(String name) { | |
512 continuationClosures++; | |
513 return '${name}_$continuationClosures'; | |
514 } | |
515 | |
516 /** Return the current continuation as a statement block for a method body. */ | |
517 Statement _continuationAsBody(SourceSpan span) { | |
518 if (continuation.length == 1 && continuation.first() is BlockStatement) { | |
519 // No need to wrap a single block statement within a block statement: | |
520 return continuation.first(); | |
521 } else { | |
522 List<Statement> continuationBlock = []; | |
523 continuationBlock.addAll(continuation); | |
524 return new BlockStatement(continuationBlock, span); | |
525 } | |
526 } | |
527 | |
528 /** Create a closure that contains the continuation statements. */ | |
529 FunctionDefinition _makeContinuation(String mName, SourceSpan span) { | |
530 return new FunctionDefinition([], null, | |
531 new Identifier(mName, span), [], null, null, | |
532 _continuationAsBody(span), span); | |
533 } | |
534 | |
535 /** Create a 1-arg closure that contains the continuation statements. */ | |
536 FunctionDefinition _makeContinuation1(String mName, | |
537 TypeReference arg1Type, Identifier arg1Name, SourceSpan span) { | |
538 return new FunctionDefinition([], null, | |
539 new Identifier(mName, span), | |
540 [new FormalNode(false, false, arg1Type, arg1Name, null, arg1Name.span)], | |
541 null, null, _continuationAsBody(span), span); | |
542 } | |
543 | |
544 /** Create a 2-arg closure that contains the continuation statements. */ | |
545 FunctionDefinition _makeContinuation2( | |
546 String mName, TypeReference arg1Type, Identifier arg1Name, | |
547 TypeReference arg2Type, Identifier arg2Name, SourceSpan span) { | |
548 return new FunctionDefinition([], null, | |
549 new Identifier(mName, span), | |
550 [new FormalNode(false, false, arg1Type, arg1Name, null, arg1Name.span), | |
551 new FormalNode(false, false, arg2Type, arg2Name, null, arg2Name.span)], | |
552 null, null, _continuationAsBody(span), span); | |
553 } | |
554 | |
555 /** Make a statement invoking a function in scope. */ | |
556 Statement _callNoArg(String mName, SourceSpan span) { | |
557 return new ExpressionStatement(new CallExpression( | |
558 new VarExpression(new Identifier(mName, span), span), [], span), span); | |
559 } | |
560 | |
561 /** Make the statement: [: target.method(value); :]. */ | |
562 Statement _callTarget1Arg( | |
563 String target, String method, Expression value, SourceSpan span) { | |
564 if (value == null) value = _makeNull(span); | |
565 return new ExpressionStatement(new CallExpression( | |
566 new DotExpression( | |
567 new VarExpression(new Identifier(target, span), span), | |
568 new Identifier(method, span), span), | |
569 [new ArgumentNode(null, value, value.span)], span), span); | |
570 } | |
571 | |
572 /** Make the statement: [: f(value); :]. */ | |
573 Statement _call1Arg(String f, Expression value, SourceSpan span) { | |
574 if (value == null) value = _makeNull(span); | |
575 return new ExpressionStatement(new CallExpression( | |
576 new VarExpression(new Identifier(f, span), span), | |
577 [new ArgumentNode(null, value, value.span)], span), span); | |
578 } | |
579 | |
580 /** Make the statement: [: f(a, b); :]. */ | |
581 Statement _call2Args(String f, Expression a, Expression b, | |
582 SourceSpan span) { | |
583 if (a == null) a = _makeNull(span); | |
584 if (b == null) b = _makeNull(span); | |
585 return new ExpressionStatement(new CallExpression( | |
586 new VarExpression(new Identifier(f, span), span), | |
587 [new ArgumentNode(null, a, a.span), | |
588 new ArgumentNode(null, b, b.span)], span), span); | |
589 } | |
590 | |
591 /** Make a return statement for a boolean value. */ | |
592 Statement _returnBoolean(SourceSpan span, bool value) { | |
593 return new ReturnStatement( | |
594 new LiteralExpression(Value.fromBool(value, span), span), span); | |
595 } | |
596 | |
597 Expression _makeNull(SourceSpan span) { | |
598 return new LiteralExpression(Value.fromNull(span), span); | |
599 } | |
600 } | |
601 | |
602 // TODO(sigmund): create the following tests: | |
603 // - await within the body of getter or setter properties | |
604 // - await in each valid AST construct | |
605 // - exceptions - make some of these fail and propagate errors. | |
606 // - methods with and without returns (are returns added implicitly) | |
607 // - await within assignmetns, but not declarations (see what happens with | |
608 // x = await t; | |
609 // x = await y; | |
610 // or | |
611 // x += await t; | |
612 // x += await y; | |
613 // (does it matter that ExpressionStatement will shadow the variable in the | |
614 // callback function to then?) | |
615 // - floating ifs/for loops, etc - make sure we don't need to normalize the ast | |
616 // to disallow 'floating' ifs or to insert a block statement when returning from | |
617 // ifs (rather than appending code to the existing continuation list). for | |
618 // instance: | |
619 // if (a) if (b) await t; 2; | |
620 // becomes: | |
621 // if (a) { if (b) { await t; } } 2; | |
622 // which becomes becomes: | |
623 // _2() { 2; } if (a) { if (b) { t.then((_) { _2(); } } } _2(); | |
624 // (seems that 'a' doesn't need { }) | |
625 // - 'if/while' with only one statement in the continuation (check the | |
626 // optimization that copies code rather than adding a continuation closure). | |
627 // - await not inside a block (could break error propagation if normalization is | |
628 // done incorrectly). | |
OLD | NEW |