OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, 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 kernel.transformations.closure.converter; |
| 6 |
| 7 import '../../ast.dart' |
| 8 show |
| 9 Arguments, |
| 10 Block, |
| 11 Catch, |
| 12 Class, |
| 13 Constructor, |
| 14 ConstructorInvocation, |
| 15 DartType, |
| 16 EmptyStatement, |
| 17 Expression, |
| 18 ExpressionStatement, |
| 19 Field, |
| 20 FieldInitializer, |
| 21 ForInStatement, |
| 22 ForStatement, |
| 23 FunctionDeclaration, |
| 24 FunctionExpression, |
| 25 FunctionNode, |
| 26 InferredValue, |
| 27 Initializer, |
| 28 InvalidExpression, |
| 29 InvocationExpression, |
| 30 Library, |
| 31 LocalInitializer, |
| 32 Member, |
| 33 MethodInvocation, |
| 34 Name, |
| 35 NamedExpression, |
| 36 NullLiteral, |
| 37 Procedure, |
| 38 ProcedureKind, |
| 39 PropertyGet, |
| 40 ReturnStatement, |
| 41 Statement, |
| 42 StaticGet, |
| 43 StaticInvocation, |
| 44 StringLiteral, |
| 45 Supertype, |
| 46 ThisExpression, |
| 47 Transformer, |
| 48 TreeNode, |
| 49 TypeParameter, |
| 50 TypeParameterType, |
| 51 VariableDeclaration, |
| 52 VariableGet, |
| 53 VariableSet, |
| 54 transformList; |
| 55 |
| 56 import '../../frontend/accessors.dart' show VariableAccessor; |
| 57 |
| 58 import '../../clone.dart' show CloneVisitor; |
| 59 |
| 60 import '../../core_types.dart' show CoreTypes; |
| 61 |
| 62 import '../../type_algebra.dart' show substitute; |
| 63 |
| 64 import 'clone_without_body.dart' show CloneWithoutBody; |
| 65 |
| 66 import 'context.dart' show Context, NoContext; |
| 67 |
| 68 import 'info.dart' show ClosureInfo; |
| 69 |
| 70 class ClosureConverter extends Transformer { |
| 71 final CoreTypes coreTypes; |
| 72 final Class contextClass; |
| 73 final Set<VariableDeclaration> capturedVariables; |
| 74 final Map<FunctionNode, Set<TypeParameter>> capturedTypeVariables; |
| 75 final Map<FunctionNode, VariableDeclaration> thisAccess; |
| 76 final Map<FunctionNode, String> localNames; |
| 77 |
| 78 /// Records place-holders for cloning contexts. See [visitForStatement]. |
| 79 final Set<InvalidExpression> contextClonePlaceHolders = |
| 80 new Set<InvalidExpression>(); |
| 81 |
| 82 /// Maps the names of all instance methods that may be torn off (aka |
| 83 /// implicitly closurized) to `${name.name}#get`. |
| 84 final Map<Name, Name> tearOffGetterNames; |
| 85 |
| 86 final CloneVisitor cloner = new CloneWithoutBody(); |
| 87 |
| 88 /// New members to add to [currentLibrary] after it has been |
| 89 /// transformed. These members will not be transformed themselves. |
| 90 final List<TreeNode> newLibraryMembers = <TreeNode>[]; |
| 91 |
| 92 /// New members to add to [currentClass] after it has been transformed. These |
| 93 /// members will not be transformed themselves. |
| 94 final List<Member> newClassMembers = <Member>[]; |
| 95 |
| 96 Library currentLibrary; |
| 97 |
| 98 Class currentClass; |
| 99 |
| 100 Member currentMember; |
| 101 |
| 102 FunctionNode currentMemberFunction; |
| 103 |
| 104 FunctionNode currentFunction; |
| 105 |
| 106 Block _currentBlock; |
| 107 |
| 108 int _insertionIndex = 0; |
| 109 |
| 110 Context context; |
| 111 |
| 112 /// Maps original type variable (aka type parameter) to a hoisted type |
| 113 /// variable type. |
| 114 /// |
| 115 /// For example, consider: |
| 116 /// |
| 117 /// class C<T> { |
| 118 /// f() => (x) => x is T; |
| 119 /// } |
| 120 /// |
| 121 /// This is currently converted to: |
| 122 /// |
| 123 /// class C<T> { |
| 124 /// f() => new Closure#0<T>(); |
| 125 /// } |
| 126 /// class Closure#0<T_> implements Function { |
| 127 /// call(x) => x is T_; |
| 128 /// } |
| 129 /// |
| 130 /// In this example, `typeSubstitution[T].parameter == T_` when transforming |
| 131 /// the closure in `f`. |
| 132 Map<TypeParameter, DartType> typeSubstitution = |
| 133 const <TypeParameter, DartType>{}; |
| 134 |
| 135 ClosureConverter(this.coreTypes, ClosureInfo info, this.contextClass) |
| 136 : this.capturedVariables = info.variables, |
| 137 this.capturedTypeVariables = info.typeVariables, |
| 138 this.thisAccess = info.thisAccess, |
| 139 this.localNames = info.localNames, |
| 140 this.tearOffGetterNames = info.tearOffGetterNames; |
| 141 |
| 142 bool get isOuterMostContext { |
| 143 return currentFunction == null || currentMemberFunction == currentFunction; |
| 144 } |
| 145 |
| 146 String get currentFileUri { |
| 147 if (currentMember is Constructor) return currentClass.fileUri; |
| 148 if (currentMember is Field) return (currentMember as Field).fileUri; |
| 149 if (currentMember is Procedure) return (currentMember as Procedure).fileUri; |
| 150 throw "No file uri"; |
| 151 } |
| 152 |
| 153 void insert(Statement statement) { |
| 154 _currentBlock.statements.insert(_insertionIndex++, statement); |
| 155 statement.parent = _currentBlock; |
| 156 } |
| 157 |
| 158 TreeNode saveContext(TreeNode f()) { |
| 159 Block savedBlock = _currentBlock; |
| 160 int savedIndex = _insertionIndex; |
| 161 Context savedContext = context; |
| 162 try { |
| 163 return f(); |
| 164 } finally { |
| 165 _currentBlock = savedBlock; |
| 166 _insertionIndex = savedIndex; |
| 167 context = savedContext; |
| 168 } |
| 169 } |
| 170 |
| 171 TreeNode visitLibrary(Library node) { |
| 172 assert(newLibraryMembers.isEmpty); |
| 173 if (node == contextClass.enclosingLibrary) return node; |
| 174 currentLibrary = node; |
| 175 node = super.visitLibrary(node); |
| 176 for (TreeNode member in newLibraryMembers) { |
| 177 if (member is Class) { |
| 178 node.addClass(member); |
| 179 } else { |
| 180 node.addMember(member); |
| 181 } |
| 182 } |
| 183 newLibraryMembers.clear(); |
| 184 currentLibrary = null; |
| 185 return node; |
| 186 } |
| 187 |
| 188 TreeNode visitClass(Class node) { |
| 189 assert(newClassMembers.isEmpty); |
| 190 currentClass = node; |
| 191 node = super.visitClass(node); |
| 192 newClassMembers.forEach(node.addMember); |
| 193 newClassMembers.clear(); |
| 194 currentClass = null; |
| 195 return node; |
| 196 } |
| 197 |
| 198 TreeNode visitConstructor(Constructor node) { |
| 199 // TODO(ahe): Convert closures in constructors as well. |
| 200 return node; |
| 201 } |
| 202 |
| 203 Expression handleLocalFunction(FunctionNode function) { |
| 204 FunctionNode enclosingFunction = currentFunction; |
| 205 Map<TypeParameter, DartType> enclosingTypeSubstitution = typeSubstitution; |
| 206 currentFunction = function; |
| 207 Statement body = function.body; |
| 208 assert(body != null); |
| 209 |
| 210 if (body is Block) { |
| 211 _currentBlock = body; |
| 212 } else { |
| 213 _currentBlock = new Block(<Statement>[body]); |
| 214 function.body = body.parent = _currentBlock; |
| 215 } |
| 216 _insertionIndex = 0; |
| 217 |
| 218 VariableDeclaration contextVariable = new VariableDeclaration( |
| 219 "#contextParameter", |
| 220 type: contextClass.rawType, |
| 221 isFinal: true); |
| 222 Context parent = context; |
| 223 context = context.toNestedContext(new VariableAccessor(contextVariable)); |
| 224 |
| 225 Set<TypeParameter> captured = capturedTypeVariables[currentFunction]; |
| 226 if (captured != null) { |
| 227 typeSubstitution = copyTypeVariables(captured); |
| 228 } else { |
| 229 typeSubstitution = const <TypeParameter, DartType>{}; |
| 230 } |
| 231 |
| 232 function.transformChildren(this); |
| 233 |
| 234 Expression result = addClosure(function, contextVariable, parent.expression, |
| 235 typeSubstitution, enclosingTypeSubstitution); |
| 236 currentFunction = enclosingFunction; |
| 237 typeSubstitution = enclosingTypeSubstitution; |
| 238 return result; |
| 239 } |
| 240 |
| 241 TreeNode visitFunctionDeclaration(FunctionDeclaration node) { |
| 242 /// Is this closure itself captured by a closure? |
| 243 bool isCaptured = capturedVariables.contains(node.variable); |
| 244 if (isCaptured) { |
| 245 context.extend(node.variable, new InvalidExpression()); |
| 246 } |
| 247 Context parent = context; |
| 248 return saveContext(() { |
| 249 Expression expression = handleLocalFunction(node.function); |
| 250 |
| 251 if (isCaptured) { |
| 252 parent.update(node.variable, expression); |
| 253 return null; |
| 254 } else { |
| 255 node.variable.initializer = expression; |
| 256 expression.parent = node.variable; |
| 257 return node.variable; |
| 258 } |
| 259 }); |
| 260 } |
| 261 |
| 262 TreeNode visitFunctionExpression(FunctionExpression node) => saveContext(() { |
| 263 return handleLocalFunction(node.function); |
| 264 }); |
| 265 |
| 266 /// Add a new class to the current library that looks like this: |
| 267 /// |
| 268 /// class Closure#0 extends core::Object implements core::Function { |
| 269 /// field _in::Context context; |
| 270 /// constructor •(final _in::Context #t1) → dynamic |
| 271 /// : self::Closure 0::context = #t1 |
| 272 /// ; |
| 273 /// method call(/* The parameters of [function] */) → dynamic { |
| 274 /// /// #t2 is [contextVariable]. |
| 275 /// final _in::Context #t2 = this.{self::Closure#0::context}; |
| 276 /// /* The body of [function]. */ |
| 277 /// } |
| 278 /// } |
| 279 /// |
| 280 /// Returns a constructor call to invoke the above constructor. |
| 281 /// |
| 282 /// TODO(ahe): We shouldn't create a class for each closure. Instead we turn |
| 283 /// [function] into a top-level function and use the Dart VM's mechnism for |
| 284 /// closures. |
| 285 Expression addClosure( |
| 286 FunctionNode function, |
| 287 VariableDeclaration contextVariable, |
| 288 Expression accessContext, |
| 289 Map<TypeParameter, DartType> substitution, |
| 290 Map<TypeParameter, DartType> enclosingTypeSubstitution) { |
| 291 Field contextField = new Field( |
| 292 // TODO(ahe): Rename to #context. |
| 293 new Name("context"), |
| 294 type: contextClass.rawType, |
| 295 fileUri: currentFileUri); |
| 296 Class closureClass = createClosureClass(function, |
| 297 fields: [contextField], substitution: substitution); |
| 298 closureClass.addMember(new Procedure( |
| 299 new Name("call"), ProcedureKind.Method, function, |
| 300 fileUri: currentFileUri)); |
| 301 newLibraryMembers.add(closureClass); |
| 302 Statement note = new ExpressionStatement( |
| 303 new StringLiteral("This is a temporary solution. " |
| 304 "In the VM, this will become an additional parameter.")); |
| 305 List<Statement> statements = <Statement>[note, contextVariable]; |
| 306 Statement body = function.body; |
| 307 if (body is Block) { |
| 308 statements.addAll(body.statements); |
| 309 } else { |
| 310 statements.add(body); |
| 311 } |
| 312 function.body = new Block(statements); |
| 313 function.body.parent = function; |
| 314 contextVariable.initializer = |
| 315 new PropertyGet(new ThisExpression(), contextField.name, contextField); |
| 316 contextVariable.initializer.parent = contextVariable; |
| 317 return new ConstructorInvocation( |
| 318 closureClass.constructors.single, |
| 319 new Arguments(<Expression>[accessContext], types: |
| 320 new List<DartType>.from(substitution.keys.map((TypeParameter t) { |
| 321 return substitute( |
| 322 new TypeParameterType(t), enclosingTypeSubstitution); |
| 323 })))); |
| 324 } |
| 325 |
| 326 TreeNode visitField(Field node) { |
| 327 currentMember = node; |
| 328 context = new NoContext(this); |
| 329 if (node.isInstanceMember) { |
| 330 Name tearOffName = tearOffGetterNames[node.name]; |
| 331 if (tearOffName != null) { |
| 332 // TODO(ahe): If we rewrite setters, we can rename the field to avoid |
| 333 // an indirection in most cases. |
| 334 addFieldForwarder(tearOffName, node); |
| 335 } |
| 336 } |
| 337 node = super.visitField(node); |
| 338 context = null; |
| 339 currentMember = null; |
| 340 return node; |
| 341 } |
| 342 |
| 343 TreeNode visitProcedure(Procedure node) { |
| 344 currentMember = node; |
| 345 assert(_currentBlock == null); |
| 346 assert(_insertionIndex == 0); |
| 347 assert(context == null); |
| 348 |
| 349 Statement body = node.function.body; |
| 350 |
| 351 if (node.isInstanceMember) { |
| 352 Name tearOffName = tearOffGetterNames[node.name]; |
| 353 if (tearOffName != null) { |
| 354 if (node.isGetter) { |
| 355 // We rename the getter to avoid an indirection in most cases. |
| 356 Name oldName = node.name; |
| 357 node.name = tearOffName; |
| 358 addGetterForwarder(oldName, node); |
| 359 } else if (node.kind == ProcedureKind.Method) { |
| 360 addTearOffGetter(tearOffName, node); |
| 361 } |
| 362 } |
| 363 } |
| 364 |
| 365 if (body == null) return node; |
| 366 |
| 367 currentMemberFunction = node.function; |
| 368 |
| 369 // Ensure that the body is a block which becomes the current block. |
| 370 if (body is Block) { |
| 371 _currentBlock = body; |
| 372 } else { |
| 373 _currentBlock = new Block(<Statement>[body]); |
| 374 node.function.body = body.parent = _currentBlock; |
| 375 } |
| 376 _insertionIndex = 0; |
| 377 |
| 378 // Start with no context. This happens after setting up _currentBlock |
| 379 // so statements can be emitted into _currentBlock if necessary. |
| 380 context = new NoContext(this); |
| 381 |
| 382 VariableDeclaration self = thisAccess[currentMemberFunction]; |
| 383 if (self != null) { |
| 384 context.extend(self, new ThisExpression()); |
| 385 } |
| 386 |
| 387 node.transformChildren(this); |
| 388 |
| 389 _currentBlock = null; |
| 390 _insertionIndex = 0; |
| 391 context = null; |
| 392 currentMemberFunction = null; |
| 393 currentMember = null; |
| 394 return node; |
| 395 } |
| 396 |
| 397 TreeNode visitLocalInitializer(LocalInitializer node) { |
| 398 assert(!capturedVariables.contains(node.variable)); |
| 399 node.transformChildren(this); |
| 400 return node; |
| 401 } |
| 402 |
| 403 TreeNode visitFunctionNode(FunctionNode node) { |
| 404 transformList(node.typeParameters, this, node); |
| 405 |
| 406 void extend(VariableDeclaration parameter) { |
| 407 context.extend(parameter, new VariableGet(parameter)); |
| 408 } |
| 409 |
| 410 // TODO: Can parameters contain initializers (e.g., for optional ones) that |
| 411 // need to be closure converted? |
| 412 node.positionalParameters.where(capturedVariables.contains).forEach(extend); |
| 413 node.namedParameters.where(capturedVariables.contains).forEach(extend); |
| 414 |
| 415 assert(node.body != null); |
| 416 node.body = node.body.accept(this); |
| 417 node.body.parent = node; |
| 418 return node; |
| 419 } |
| 420 |
| 421 TreeNode visitBlock(Block node) => saveContext(() { |
| 422 if (_currentBlock != node) { |
| 423 _currentBlock = node; |
| 424 _insertionIndex = 0; |
| 425 } |
| 426 |
| 427 while (_insertionIndex < _currentBlock.statements.length) { |
| 428 assert(_currentBlock == node); |
| 429 |
| 430 var original = _currentBlock.statements[_insertionIndex]; |
| 431 var transformed = original.accept(this); |
| 432 assert(_currentBlock.statements[_insertionIndex] == original); |
| 433 if (transformed == null) { |
| 434 _currentBlock.statements.removeAt(_insertionIndex); |
| 435 } else { |
| 436 _currentBlock.statements[_insertionIndex++] = transformed; |
| 437 transformed.parent = _currentBlock; |
| 438 } |
| 439 } |
| 440 |
| 441 return node; |
| 442 }); |
| 443 |
| 444 TreeNode visitVariableDeclaration(VariableDeclaration node) { |
| 445 node.transformChildren(this); |
| 446 |
| 447 if (!capturedVariables.contains(node)) return node; |
| 448 context.extend(node, node.initializer ?? new NullLiteral()); |
| 449 |
| 450 if (node.parent == currentFunction) return node; |
| 451 if (node.parent is Block) { |
| 452 // When returning null, the parent block will remove this node from its |
| 453 // list of statements. |
| 454 // TODO(ahe): I'd like to avoid testing on the parent pointer. |
| 455 return null; |
| 456 } |
| 457 throw "Unexpected parent for $node: ${node.parent.parent}"; |
| 458 } |
| 459 |
| 460 TreeNode visitVariableGet(VariableGet node) { |
| 461 return capturedVariables.contains(node.variable) |
| 462 ? context.lookup(node.variable) |
| 463 : node; |
| 464 } |
| 465 |
| 466 TreeNode visitVariableSet(VariableSet node) { |
| 467 node.transformChildren(this); |
| 468 |
| 469 return capturedVariables.contains(node.variable) |
| 470 ? context.assign(node.variable, node.value, |
| 471 voidContext: isInVoidContext(node)) |
| 472 : node; |
| 473 } |
| 474 |
| 475 bool isInVoidContext(Expression node) { |
| 476 TreeNode parent = node.parent; |
| 477 return parent is ExpressionStatement || |
| 478 parent is ForStatement && parent.condition != node; |
| 479 } |
| 480 |
| 481 DartType visitDartType(DartType node) { |
| 482 return substitute(node, typeSubstitution); |
| 483 } |
| 484 |
| 485 VariableDeclaration getReplacementLoopVariable(VariableDeclaration variable) { |
| 486 VariableDeclaration newVariable = new VariableDeclaration(variable.name, |
| 487 initializer: variable.initializer, |
| 488 type: variable.type)..flags = variable.flags; |
| 489 variable.initializer = new VariableGet(newVariable); |
| 490 variable.initializer.parent = variable; |
| 491 return newVariable; |
| 492 } |
| 493 |
| 494 Expression cloneContext() { |
| 495 InvalidExpression placeHolder = new InvalidExpression(); |
| 496 contextClonePlaceHolders.add(placeHolder); |
| 497 return placeHolder; |
| 498 } |
| 499 |
| 500 TreeNode visitInvalidExpression(InvalidExpression node) { |
| 501 return contextClonePlaceHolders.remove(node) ? context.clone() : node; |
| 502 } |
| 503 |
| 504 TreeNode visitForStatement(ForStatement node) { |
| 505 if (node.variables.any(capturedVariables.contains)) { |
| 506 // In Dart, loop variables are new variables on each iteration of the |
| 507 // loop. This is only observable when a loop variable is captured by a |
| 508 // closure, which is the situation we're in here. So we transform the |
| 509 // loop. |
| 510 // |
| 511 // Consider the following example, where `x` is `node.variables.first`, |
| 512 // and `#t1` is a temporary variable: |
| 513 // |
| 514 // for (var x = 0; x < 10; x++) body; |
| 515 // |
| 516 // This is transformed to: |
| 517 // |
| 518 // { |
| 519 // var x = 0; |
| 520 // for (; x < 10; clone-context, x++) body; |
| 521 // } |
| 522 // |
| 523 // `clone-context` is a place-holder that will later be replaced by an |
| 524 // expression that clones the current closure context (see |
| 525 // [visitInvalidExpression]). |
| 526 return saveContext(() { |
| 527 context = context.toNestedContext(); |
| 528 List<Statement> statements = <Statement>[]; |
| 529 statements.addAll(node.variables); |
| 530 statements.add(node); |
| 531 node.variables.clear(); |
| 532 node.updates.insert(0, cloneContext()); |
| 533 _currentBlock = new Block(statements); |
| 534 _insertionIndex = 0; |
| 535 return _currentBlock.accept(this); |
| 536 }); |
| 537 } |
| 538 return super.visitForStatement(node); |
| 539 } |
| 540 |
| 541 TreeNode visitForInStatement(ForInStatement node) { |
| 542 if (capturedVariables.contains(node.variable)) { |
| 543 // In Dart, loop variables are new variables on each iteration of the |
| 544 // loop. This is only observable when the loop variable is captured by a |
| 545 // closure, so we need to transform the for-in loop when `node.variable` |
| 546 // is captured. |
| 547 // |
| 548 // Consider the following example, where `x` is `node.variable`, and |
| 549 // `#t1` is a temporary variable: |
| 550 // |
| 551 // for (var x in expr) body; |
| 552 // |
| 553 // Notice that we can assume that `x` doesn't have an initializer based |
| 554 // on invariants in the Kernel AST. This is transformed to: |
| 555 // |
| 556 // for (var #t1 in expr) { var x = #t1; body; } |
| 557 // |
| 558 // After this, we call super to apply the normal closure conversion to |
| 559 // the transformed for-in loop. |
| 560 VariableDeclaration variable = node.variable; |
| 561 VariableDeclaration newVariable = getReplacementLoopVariable(variable); |
| 562 node.variable = newVariable; |
| 563 newVariable.parent = node; |
| 564 node.body = new Block(<Statement>[variable, node.body]); |
| 565 node.body.parent = node; |
| 566 } |
| 567 return super.visitForInStatement(node); |
| 568 } |
| 569 |
| 570 TreeNode visitThisExpression(ThisExpression node) { |
| 571 return isOuterMostContext |
| 572 ? node |
| 573 : context.lookup(thisAccess[currentMemberFunction]); |
| 574 } |
| 575 |
| 576 TreeNode visitStaticGet(StaticGet node) { |
| 577 Member target = node.target; |
| 578 if (target is Procedure && target.kind == ProcedureKind.Method) { |
| 579 Expression expression = getTearOffExpression(node.target); |
| 580 expression.transformChildren(this); |
| 581 return expression; |
| 582 } |
| 583 return super.visitStaticGet(node); |
| 584 } |
| 585 |
| 586 TreeNode visitPropertyGet(PropertyGet node) { |
| 587 Name tearOffName = tearOffGetterNames[node.name]; |
| 588 if (tearOffName != null) { |
| 589 node.name = tearOffName; |
| 590 } |
| 591 return super.visitPropertyGet(node); |
| 592 } |
| 593 |
| 594 TreeNode visitCatch(Catch node) { |
| 595 VariableDeclaration exception = node.exception; |
| 596 VariableDeclaration stackTrace = node.stackTrace; |
| 597 if (stackTrace != null && capturedVariables.contains(stackTrace)) { |
| 598 Block block = node.body = ensureBlock(node.body); |
| 599 block.parent = node; |
| 600 node.stackTrace = new VariableDeclaration(null); |
| 601 node.stackTrace.parent = node; |
| 602 stackTrace.initializer = new VariableGet(node.stackTrace); |
| 603 block.statements.insert(0, stackTrace); |
| 604 stackTrace.parent = block; |
| 605 } |
| 606 if (exception != null && capturedVariables.contains(exception)) { |
| 607 Block block = node.body = ensureBlock(node.body); |
| 608 block.parent = node; |
| 609 node.exception = new VariableDeclaration(null); |
| 610 node.exception.parent = node; |
| 611 exception.initializer = new VariableGet(node.exception); |
| 612 block.statements.insert(0, exception); |
| 613 exception.parent = block; |
| 614 } |
| 615 return super.visitCatch(node); |
| 616 } |
| 617 |
| 618 Block ensureBlock(Statement statement) { |
| 619 return statement is Block ? statement : new Block(<Statement>[statement]); |
| 620 } |
| 621 |
| 622 /// Creates a closure that will invoke [procedure] and return an expression |
| 623 /// that instantiates that closure. |
| 624 Expression getTearOffExpression(Procedure procedure) { |
| 625 Map<TypeParameter, DartType> substitution = procedure.isInstanceMember |
| 626 // Note: we do not attempt to avoid copying type variables that aren't |
| 627 // used in the signature of [procedure]. It might be more economical to |
| 628 // only copy type variables that are used. However, we assume that |
| 629 // passing type arguments that match the enclosing class' type |
| 630 // variables will be handled most efficiently. |
| 631 ? copyTypeVariables(procedure.enclosingClass.typeParameters) |
| 632 : const <TypeParameter, DartType>{}; |
| 633 Expression receiver = null; |
| 634 List<Field> fields = null; |
| 635 if (procedure.isInstanceMember) { |
| 636 // TODO(ahe): Rename to #self. |
| 637 Field self = new Field(new Name("self"), fileUri: currentFileUri); |
| 638 self.type = substitute(procedure.enclosingClass.thisType, substitution); |
| 639 fields = <Field>[self]; |
| 640 receiver = new PropertyGet(new ThisExpression(), self.name, self); |
| 641 } |
| 642 Class closureClass = createClosureClass(procedure.function, |
| 643 fields: fields, substitution: substitution); |
| 644 closureClass.addMember(new Procedure(new Name("call"), ProcedureKind.Method, |
| 645 forwardFunction(procedure, receiver, substitution), |
| 646 fileUri: currentFileUri)); |
| 647 newLibraryMembers.add(closureClass); |
| 648 Arguments constructorArguments = procedure.isInstanceMember |
| 649 ? new Arguments(<Expression>[new ThisExpression()]) |
| 650 : new Arguments.empty(); |
| 651 if (substitution.isNotEmpty) { |
| 652 constructorArguments.types |
| 653 .addAll(procedure.enclosingClass.thisType.typeArguments); |
| 654 } |
| 655 return new ConstructorInvocation( |
| 656 closureClass.constructors.single, constructorArguments); |
| 657 } |
| 658 |
| 659 /// Creates a function that has the same signature as `procedure.function` |
| 660 /// and which forwards all arguments to `procedure`. |
| 661 FunctionNode forwardFunction(Procedure procedure, Expression receiver, |
| 662 Map<TypeParameter, DartType> substitution) { |
| 663 CloneVisitor cloner = substitution.isEmpty |
| 664 ? this.cloner |
| 665 : new CloneWithoutBody(typeSubstitution: substitution); |
| 666 FunctionNode function = procedure.function; |
| 667 List<TypeParameter> typeParameters = |
| 668 function.typeParameters.map(cloner.clone).toList(); |
| 669 List<VariableDeclaration> positionalParameters = |
| 670 function.positionalParameters.map(cloner.clone).toList(); |
| 671 List<VariableDeclaration> namedParameters = |
| 672 function.namedParameters.map(cloner.clone).toList(); |
| 673 // TODO(ahe): Clone or copy inferredReturnValue? |
| 674 InferredValue inferredReturnValue = null; |
| 675 |
| 676 List<DartType> types = typeParameters |
| 677 .map((TypeParameter parameter) => new TypeParameterType(parameter)) |
| 678 .toList(); |
| 679 List<Expression> positional = positionalParameters |
| 680 .map((VariableDeclaration parameter) => new VariableGet(parameter)) |
| 681 .toList(); |
| 682 List<NamedExpression> named = |
| 683 namedParameters.map((VariableDeclaration parameter) { |
| 684 return new NamedExpression(parameter.name, new VariableGet(parameter)); |
| 685 }).toList(); |
| 686 |
| 687 Arguments arguments = new Arguments(positional, types: types, named: named); |
| 688 InvocationExpression invocation = procedure.isInstanceMember |
| 689 ? new MethodInvocation(receiver, procedure.name, arguments, procedure) |
| 690 : new StaticInvocation(procedure, arguments); |
| 691 return new FunctionNode(new ReturnStatement(invocation), |
| 692 typeParameters: typeParameters, |
| 693 positionalParameters: positionalParameters, |
| 694 namedParameters: namedParameters, |
| 695 requiredParameterCount: function.requiredParameterCount, |
| 696 returnType: substitute(function.returnType, substitution), |
| 697 inferredReturnValue: inferredReturnValue); |
| 698 } |
| 699 |
| 700 /// Creates copies of the type variables in [original] and returns a |
| 701 /// substitution that can be passed to [substitute] to substitute all uses of |
| 702 /// [original] with their copies. |
| 703 Map<TypeParameter, DartType> copyTypeVariables( |
| 704 Iterable<TypeParameter> original) { |
| 705 if (original.isEmpty) return const <TypeParameter, DartType>{}; |
| 706 Map<TypeParameter, DartType> substitution = <TypeParameter, DartType>{}; |
| 707 for (TypeParameter t in original) { |
| 708 substitution[t] = new TypeParameterType(new TypeParameter(t.name)); |
| 709 } |
| 710 substitution.forEach((TypeParameter t, DartType copy) { |
| 711 if (copy is TypeParameterType) { |
| 712 copy.parameter.bound = substitute(t.bound, substitution); |
| 713 } |
| 714 }); |
| 715 return substitution; |
| 716 } |
| 717 |
| 718 Class createClosureClass(FunctionNode function, |
| 719 {List<Field> fields, Map<TypeParameter, DartType> substitution}) { |
| 720 List<TypeParameter> typeParameters = new List<TypeParameter>.from( |
| 721 substitution.values |
| 722 .map((DartType t) => (t as TypeParameterType).parameter)); |
| 723 Class closureClass = new Class( |
| 724 name: 'Closure#${localNames[function]}', |
| 725 supertype: new Supertype(coreTypes.objectClass, const <DartType>[]), |
| 726 typeParameters: typeParameters, |
| 727 implementedTypes: <Supertype>[ |
| 728 new Supertype(coreTypes.functionClass, const <DartType>[]) |
| 729 ], |
| 730 fileUri: currentFileUri); |
| 731 addClosureClassNote(closureClass); |
| 732 |
| 733 List<VariableDeclaration> parameters = <VariableDeclaration>[]; |
| 734 List<Initializer> initializers = <Initializer>[]; |
| 735 for (Field field in fields ?? const <Field>[]) { |
| 736 closureClass.addMember(field); |
| 737 VariableDeclaration parameter = new VariableDeclaration(field.name.name, |
| 738 type: field.type, isFinal: true); |
| 739 parameters.add(parameter); |
| 740 initializers.add(new FieldInitializer(field, new VariableGet(parameter))); |
| 741 } |
| 742 |
| 743 closureClass.addMember(new Constructor( |
| 744 new FunctionNode(new EmptyStatement(), |
| 745 positionalParameters: parameters), |
| 746 name: new Name(""), |
| 747 initializers: initializers)); |
| 748 |
| 749 return closureClass; |
| 750 } |
| 751 |
| 752 Statement forwardToThisProperty(Member node) { |
| 753 assert(node is Field || (node is Procedure && node.isGetter)); |
| 754 return new ReturnStatement( |
| 755 new PropertyGet(new ThisExpression(), node.name, node)); |
| 756 } |
| 757 |
| 758 void addFieldForwarder(Name name, Field field) { |
| 759 newClassMembers.add(new Procedure(name, ProcedureKind.Getter, |
| 760 new FunctionNode(forwardToThisProperty(field)), |
| 761 fileUri: currentFileUri)); |
| 762 } |
| 763 |
| 764 Procedure copyWithBody(Procedure procedure, Statement body) { |
| 765 Procedure copy = cloner.clone(procedure); |
| 766 copy.function.body = body; |
| 767 copy.function.body.parent = copy.function; |
| 768 return copy; |
| 769 } |
| 770 |
| 771 void addGetterForwarder(Name name, Procedure getter) { |
| 772 assert(getter.isGetter); |
| 773 newClassMembers |
| 774 .add(copyWithBody(getter, forwardToThisProperty(getter))..name = name); |
| 775 } |
| 776 |
| 777 void addTearOffGetter(Name name, Procedure procedure) { |
| 778 newClassMembers.add(new Procedure(name, ProcedureKind.Getter, |
| 779 new FunctionNode(new ReturnStatement(getTearOffExpression(procedure))), |
| 780 fileUri: currentFileUri)); |
| 781 } |
| 782 |
| 783 // TODO(ahe): Remove this method when we don't generate closure classes |
| 784 // anymore. |
| 785 void addClosureClassNote(Class closureClass) { |
| 786 closureClass.addMember(new Field(new Name("note"), |
| 787 type: coreTypes.stringClass.rawType, |
| 788 initializer: new StringLiteral( |
| 789 "This is temporary. The VM doesn't need closure classes."), |
| 790 fileUri: currentFileUri)); |
| 791 } |
| 792 } |
OLD | NEW |