| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Desugarizes all await calls in a single function. This visitor assumes that | 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 | 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 | 8 * only needs to recurse on statements and not on expressions. Like in |
| 9 * [AwaitChecker], nested functions have to be processed separately. | 9 * [AwaitChecker], nested functions have to be processed separately. |
| 10 */ | 10 */ |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 static final _IGNORED_THEN_PARAM = _PREFIX + 'ignored_param'; | 23 static final _IGNORED_THEN_PARAM = _PREFIX + 'ignored_param'; |
| 24 static final _CONTINUATION_PREFIX = _PREFIX + 'after_'; | 24 static final _CONTINUATION_PREFIX = _PREFIX + 'after_'; |
| 25 | 25 |
| 26 static final _COMPLETE_METHOD = 'complete'; | 26 static final _COMPLETE_METHOD = 'complete'; |
| 27 static final _COMPLETE_EXCEPTION_METHOD = 'completeException'; | 27 static final _COMPLETE_EXCEPTION_METHOD = 'completeException'; |
| 28 | 28 |
| 29 | 29 |
| 30 /** The continuation when visiting a particular statement. */ | 30 /** The continuation when visiting a particular statement. */ |
| 31 Queue<Statement> continuation; | 31 Queue<Statement> continuation; |
| 32 | 32 |
| 33 /** If not null, a closure to call when a future ends with an exception. */ | 33 /** Closures to call when a future throws an exception (in reverse order). */ |
| 34 Identifier currentExceptionHandler; | 34 List<Identifier> exceptionHandlers; |
| 35 |
| 36 /** All try-catch blocks enclosing the current statement. */ |
| 37 List<TryStatement> enclosingTrys; |
| 35 | 38 |
| 36 /** Counter to ensure created closure names are unique. */ | 39 /** Counter to ensure created closure names are unique. */ |
| 37 int continuationClosures = 0; | 40 int continuationClosures = 0; |
| 38 | 41 |
| 39 /** Nodes containing await expressions (determined by [AwaitChecker]). */ | 42 /** Nodes containing await expressions (determined by [AwaitChecker]). */ |
| 40 final NodeSet haveAwait; | 43 final NodeSet haveAwait; |
| 41 | 44 |
| 42 AwaitProcessor(this.haveAwait) : continuation = new Queue<Statement>(); | 45 AwaitProcessor(this.haveAwait) |
| 46 : continuation = new Queue<Statement>(), |
| 47 exceptionHandlers = [], |
| 48 enclosingTrys = []; |
| 43 | 49 |
| 44 visitVariableDefinition(VariableDefinition node) { | 50 visitVariableDefinition(VariableDefinition node) { |
| 45 if (!haveAwait.contains(node)) return node; | 51 if (!haveAwait.contains(node)) return node; |
| 46 final values = node.values; | 52 final values = node.values; |
| 47 // After normalization, variable declarations with await can only occur at | 53 // After normalization, variable declarations with await can only occur at |
| 48 // the top-level and there is no other declaration. | 54 // the top-level and there is no other declaration. |
| 49 assert(values != null && values.length == 1 | 55 assert(values != null && values.length == 1 |
| 50 && values[0] is AwaitExpression); | 56 && values[0] is AwaitExpression); |
| 51 final param = new Identifier(_THEN_PARAM, node.span); | 57 final param = new Identifier(_THEN_PARAM, node.span); |
| 52 // T x = await t; ... becomes: | 58 // T x = await t; ... becomes: |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 // - consider when await shows in catch blocks, but not in the try block | 196 // - consider when await shows in catch blocks, but not in the try block |
| 191 // - support exceptions after the await | 197 // - support exceptions after the await |
| 192 // - handle nested try blocks | 198 // - handle nested try blocks |
| 193 // - support finally | 199 // - support finally |
| 194 // - consider throws within catch blocks, e.g: | 200 // - consider throws within catch blocks, e.g: |
| 195 // try { a } catch (E e1) { throw new E2(); } catch (E2 e2) { -- } | 201 // try { a } catch (E e1) { throw new E2(); } catch (E2 e2) { -- } |
| 196 | 202 |
| 197 String afterTry = _newClosureName(_CONTINUATION_PREFIX + "_try"); | 203 String afterTry = _newClosureName(_CONTINUATION_PREFIX + "_try"); |
| 198 Statement afterTryDef = _makeContinuation(afterTry, node.span); | 204 Statement afterTryDef = _makeContinuation(afterTry, node.span); |
| 199 | 205 |
| 200 // Transform the body first: | |
| 201 continuation = new Queue(); | |
| 202 final exceptionHandlerName = new Identifier( | |
| 203 _newClosureName(_PREFIX + "exception_handler"), node.span); | |
| 204 currentExceptionHandler = exceptionHandlerName; | |
| 205 continuation.addFirst(_callNoArg(afterTry, node.span)); | |
| 206 Statement body = node.body.visit(this); | |
| 207 currentExceptionHandler = null; | |
| 208 | |
| 209 final defs = []; // closures for each catch block (avoid duplicating code). | 206 final defs = []; // closures for each catch block (avoid duplicating code). |
| 210 final catches = []; // catch clauses of the transformed try-catch block | 207 final catches = []; // catch clauses of the transformed try-catch block |
| 211 | 208 |
| 212 // Catch blocks are passed as an exception handler on de-sugared awaits: | 209 // Catch blocks are passed as an exception handler on de-sugared awaits: |
| 210 final handlerName = new Identifier( |
| 211 _newClosureName(_PREFIX + "exception_handler"), node.span); |
| 213 // TODO(sigmund): add trace argument (library change in Future<T>) | 212 // TODO(sigmund): add trace argument (library change in Future<T>) |
| 214 final handlerArg = new Identifier(_EXCEPTION_HANDLER_PARAM, node.span); | 213 final handlerArg = new Identifier(_EXCEPTION_HANDLER_PARAM, node.span); |
| 215 final handlerBody = []; | 214 final handlerBody = []; |
| 216 | 215 |
| 217 // The exception handler is smaller when we encounter an untyped catch. | 216 // Transform each catch-block internally. |
| 218 bool untypedCatch = false; | 217 bool untypedCatch = false; // When true, the exception handler is smaller. |
| 219 | |
| 220 for (CatchNode n in node.catches) { | 218 for (CatchNode n in node.catches) { |
| 221 String fname = _newClosureName(_PREFIX + "catch"); | 219 String fname = _newClosureName(_PREFIX + "catch"); |
| 222 | 220 |
| 223 // Code in transformed catch-block: | 221 // Code in transformed catch-block: |
| 224 continuation = new Queue(); | 222 continuation = new Queue(); |
| 225 continuation.addFirst(_callNoArg(afterTry, node.span)); | 223 continuation.addFirst(_callNoArg(afterTry, node.span)); |
| 226 continuation.addFirst(n.body.visit(this)); | 224 continuation.addFirst(n.body.visit(this)); |
| 227 | 225 |
| 228 defs.add(_makeCatchFunction(n, fname)); | 226 defs.add(_makeCatchFunction(n, fname)); |
| 229 catches.add(new CatchNode(n.exception, n.trace, | 227 catches.add(new CatchNode(n.exception, n.trace, |
| 230 new BlockStatement( | 228 new BlockStatement( |
| 231 [_callCatchFunction(n, fname), _returnFuture(n.span)], n.span), | 229 [_callCatchFunction(n, fname), _returnFuture(n.span)], n.span), |
| 232 n.span)); | 230 n.span)); |
| 233 | 231 |
| 234 // Code in exceptionHandler: | 232 // Code in exception handler: |
| 235 if (!untypedCatch) { | 233 if (!untypedCatch) { |
| 236 final exceptionHandlerCases = [ | 234 final exceptionHandlerCases = [ |
| 237 _callCatchFunctionHelper(fname, handlerArg, null, n.span), | 235 _callCatchFunctionHelper(fname, handlerArg, null, n.span), |
| 238 _returnBoolean(n.span, true)]; | 236 _returnBoolean(n.span, true)]; |
| 239 if (n.exception.type == null) { | 237 if (n.exception.type == null) { |
| 240 handlerBody.addAll(exceptionHandlerCases); | 238 handlerBody.addAll(exceptionHandlerCases); |
| 241 untypedCatch = true; | 239 untypedCatch = true; |
| 242 } else { | 240 } else { |
| 243 handlerBody.add(new IfStatement( | 241 handlerBody.add(new IfStatement( |
| 244 new IsExpression(true, handlerArg, n.exception.type, n.span), | 242 new IsExpression(true, |
| 243 new VarExpression(handlerArg, n.span), |
| 244 n.exception.type, n.span), |
| 245 new BlockStatement(exceptionHandlerCases, n.span), | 245 new BlockStatement(exceptionHandlerCases, n.span), |
| 246 null, n.span)); | 246 null, n.span)); |
| 247 } | 247 } |
| 248 } | 248 } |
| 249 } | 249 } |
| 250 | 250 |
| 251 if (!untypedCatch) { | 251 if (!untypedCatch) { |
| 252 handlerBody.add(_returnBoolean(node.span, false)); | 252 handlerBody.add(_returnBoolean(node.span, false)); |
| 253 } | 253 } |
| 254 | 254 |
| 255 final handlerDef = new FunctionDefinition([], null, | 255 // Declare the exception handler |
| 256 exceptionHandlerName, [new FormalNode( | 256 final handlerDef = new FunctionDefinition([], null, handlerName, |
| 257 false, false, null, handlerArg, null, node.span)], | 257 [new FormalNode(false, false, null, handlerArg, null, node.span)], |
| 258 null, null, new BlockStatement(handlerBody, node.span), node.span); | 258 null, null, new BlockStatement(handlerBody, node.span), node.span); |
| 259 | 259 |
| 260 // Transform the try body, tracking the enclosing try blocks and |
| 261 // exception handlers. |
| 262 enclosingTrys.addLast(new TryStatement( |
| 263 null /* this is replaced with code in [_desugarAwaitCall] */, |
| 264 catches, node.finallyBlock, node.span)); |
| 265 exceptionHandlers.addLast(handlerName); |
| 266 continuation = new Queue(); |
| 267 continuation.addFirst(_callNoArg(afterTry, node.span)); |
| 268 Statement body = node.body.visit(this); |
| 269 exceptionHandlers.removeLast(); |
| 270 enclosingTrys.removeLast(); |
| 271 |
| 260 continuation = new Queue(); | 272 continuation = new Queue(); |
| 261 continuation.addAll(defs); | 273 continuation.addAll(defs); |
| 262 continuation.add(handlerDef); | 274 continuation.add(handlerDef); |
| 263 continuation.add(new TryStatement(body, | 275 continuation.add(new TryStatement(body, |
| 264 catches, node.finallyBlock, node.span)); | 276 catches, node.finallyBlock, node.span)); |
| 265 continuation.add(_callNoArg(afterTry, node.span)); | 277 continuation.add(_callNoArg(afterTry, node.span)); |
| 266 return afterTryDef; | 278 return afterTryDef; |
| 267 } | 279 } |
| 268 | 280 |
| 269 /** | 281 /** |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 371 } | 383 } |
| 372 | 384 |
| 373 /** | 385 /** |
| 374 * Converts an await expression into several statements: calling | 386 * Converts an await expression into several statements: calling |
| 375 * [:Future.then:] and propatating errors. This implementation assumes that | 387 * [:Future.then:] and propatating errors. This implementation assumes that |
| 376 * await calls are within blocks (after normalization). | 388 * await calls are within blocks (after normalization). |
| 377 */ | 389 */ |
| 378 _desugarAwaitCall(AwaitExpression node, Identifier param) { | 390 _desugarAwaitCall(AwaitExpression node, Identifier param) { |
| 379 List<Statement> afterAwait = []; | 391 List<Statement> afterAwait = []; |
| 380 afterAwait.addAll(continuation); | 392 afterAwait.addAll(continuation); |
| 381 if (afterAwait[afterAwait.length - 1] is ReturnStatement) { | 393 if (afterAwait.last() is ReturnStatement) { |
| 382 // The only reason there is a `return` is because there was another await | 394 // The only reason there is a `return` is because there was another await |
| 383 // and we introduced it. Such `return` is not needed in the callback. | 395 // and we introduced it. Such `return` is not needed in the callback. |
| 384 afterAwait.length = afterAwait.length - 1; | 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); |
| 385 } | 404 } |
| 386 | 405 |
| 387 // A lambda function that executes the continuation. | 406 // A lambda function that executes the continuation. |
| 388 final thenArg = new LambdaExpression( | 407 final thenArg = new LambdaExpression( |
| 389 new FunctionDefinition([], null, null, | 408 new FunctionDefinition([], null, null, |
| 390 [new FormalNode( | 409 [new FormalNode( |
| 391 false, false, null /* infer type from body? */, | 410 false, false, null /* infer type from body? */, |
| 392 param, null, param.span) | 411 param, null, param.span) |
| 393 ], null, null, | 412 ], null, null, |
| 394 new BlockStatement(afterAwait, node.span), node.span), | 413 thenBody, node.span), |
| 395 node.span); | 414 node.span); |
| 396 | 415 |
| 397 continuation.clear(); | 416 continuation.clear(); |
| 398 continuation.addFirst(_returnFuture(node.span)); | 417 continuation.addFirst(_returnFuture(node.span)); |
| 399 // Within try-blocks, we also add an exception handler to propagate errors. | 418 // Within try-blocks, we also add an exception handler to propagate errors. |
| 400 if (currentExceptionHandler != null) { | 419 for (final handlerName in exceptionHandlers) { |
| 401 continuation.addFirst(new ExpressionStatement(new CallExpression( | 420 continuation.addFirst(new ExpressionStatement(new CallExpression( |
| 402 new DotExpression(node.body, | 421 new DotExpression(node.body, |
| 403 new Identifier('handleException', node.span), node.span), | 422 new Identifier('handleException', node.span), node.span), |
| 404 [new ArgumentNode(null, | 423 [new ArgumentNode(null, |
| 405 new VarExpression(currentExceptionHandler, node.span), | 424 new VarExpression(handlerName, node.span), |
| 406 node.span)], | 425 node.span)], |
| 407 node.span), node.span)); | 426 node.span), node.span)); |
| 408 } | 427 } |
| 409 return _callThen(node.body, thenArg, node.span); | 428 return _callThen(node.body, thenArg, node.span); |
| 410 } | 429 } |
| 411 | 430 |
| 412 /** Make the statement: [: future.then(arg); :] */ | 431 /** Make the statement: [: future.then(arg); :] */ |
| 413 _callThen(Expression future, Expression arg, SourceSpan span) { | 432 _callThen(Expression future, Expression arg, SourceSpan span) { |
| 414 return new CallExpression( | 433 return new CallExpression( |
| 415 new DotExpression(future, new Identifier('then', span), span), | 434 new DotExpression(future, new Identifier('then', span), span), |
| (...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 596 // if (a) if (b) await t; 2; | 615 // if (a) if (b) await t; 2; |
| 597 // becomes: | 616 // becomes: |
| 598 // if (a) { if (b) { await t; } } 2; | 617 // if (a) { if (b) { await t; } } 2; |
| 599 // which becomes becomes: | 618 // which becomes becomes: |
| 600 // _2() { 2; } if (a) { if (b) { t.then((_) { _2(); } } } _2(); | 619 // _2() { 2; } if (a) { if (b) { t.then((_) { _2(); } } } _2(); |
| 601 // (seems that 'a' doesn't need { }) | 620 // (seems that 'a' doesn't need { }) |
| 602 // - 'if/while' with only one statement in the continuation (check the | 621 // - 'if/while' with only one statement in the continuation (check the |
| 603 // optimization that copies code rather than adding a continuation closure). | 622 // optimization that copies code rather than adding a continuation closure). |
| 604 // - await not inside a block (could break error propagation if normalization is | 623 // - await not inside a block (could break error propagation if normalization is |
| 605 // done incorrectly). | 624 // done incorrectly). |
| OLD | NEW |