| 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 |