| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2017, 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.method_call; |
| 6 |
| 7 import 'dart:math' as math; |
| 8 |
| 9 import '../ast.dart'; |
| 10 import '../class_hierarchy.dart'; |
| 11 import '../core_types.dart'; |
| 12 import '../kernel.dart'; |
| 13 import '../visitor.dart'; |
| 14 |
| 15 /// Problems with the method rewrite transformation: |
| 16 /// |
| 17 /// * Cannot rewrite invocations to things called "call" because of tear-offs |
| 18 /// and whatnot that when invoked turns into variableName.call(...). |
| 19 /// |
| 20 /// * Cannot rewrite invocations to things sharing a name with a field because |
| 21 /// one could have called clazz.fieldName(...). |
| 22 /// |
| 23 /// * Rewrites will make stacktraces look weird. |
| 24 /// |
| 25 /// * Rewrites will make noSuchMethod look weird --- e.g. calling a non-existing |
| 26 /// function foo(a: 42) turns into foo%0%a(42), i.e. the method name has |
| 27 /// changed ("foo" vs "foo%0%a") and the arguments has changed (named "a" vs |
| 28 /// positional). |
| 29 /// NOTE: At least for now this can be fixed by changing the |
| 30 /// invocation_mirror_patch file. Doing this I can make all dill, language |
| 31 /// and co19 tests pass! |
| 32 /// |
| 33 /// Somewhat weird: |
| 34 /// |
| 35 /// * Inserts methods that redirect to the correct noSuchMethod invocation |
| 36 /// so that program #1 example below will work. |
| 37 /// The reason it otherwise wouldn't is that b.foo(499, named1: 88) is |
| 38 /// rewritten to b.foo%1%named1(499, 88) which is not legal for class B |
| 39 /// (thus the method would not be create there) but IS legal for Bs parent |
| 40 /// class (A), so it would be created there. The call will thus go to the |
| 41 /// super (A) but shouldn't as foo was overwritten in B. |
| 42 /// |
| 43 /// Program #1 example: |
| 44 /// class A { |
| 45 /// foo(required1, { named1: 499}) => print("Hello from class A"); |
| 46 /// } |
| 47 /// |
| 48 /// class B extends A { |
| 49 /// foo(required1) => print("Hello from class B"); |
| 50 /// } |
| 51 /// |
| 52 /// main() { |
| 53 /// var b = new B(); |
| 54 /// b.foo(499, named1: 88); |
| 55 /// } |
| 56 Program transformProgram(Program program, [debug = false]) { |
| 57 new MethodCallTransformer(debug).visitProgram(program); |
| 58 return program; |
| 59 } |
| 60 |
| 61 class MethodCallTransformer extends Transformer { |
| 62 /// Keep track of "visited" procedures and constructors to not visit already |
| 63 /// visited stuff, nor visit newly created stubs. |
| 64 Set<Member> _visited = new Set<Member>(); |
| 65 |
| 66 /// Some things currently cannot be rewritten. Calls to methods called "call" |
| 67 /// (because invoking tear-offs and closures and whatnot becomes .call) |
| 68 /// as well as clashes with field names as a field can contain a function |
| 69 /// and one can then do a clazz.fieldName(...). |
| 70 Set<String> blacklistedSelectors = new Set<String>.from(["call"]); |
| 71 |
| 72 /// Map from a "originally named" procedure to the "%original" procedure |
| 73 /// for procedures that was moved. |
| 74 Map<Procedure, Procedure> _movedBodies = {}; |
| 75 |
| 76 /// Map from a "originally named" constructor to the "%original" |
| 77 /// constructor for constructors that was moved. |
| 78 Map<Constructor, Constructor> _movedConstructors = {}; |
| 79 |
| 80 /// For static method transformations: |
| 81 /// Maps a procedure to the mapping of argument signature to procedure stub. |
| 82 Map<Procedure, Map<String, Procedure>> _staticProcedureCalls = {}; |
| 83 |
| 84 /// For constructor transformations: |
| 85 /// Maps a constructor to a mapping of argument signature to constructor stub. |
| 86 Map<Constructor, Map<String, Constructor>> _constructorCalls = {}; |
| 87 |
| 88 /// For non-static method transformations: |
| 89 /// Maps from method name to the set of legal number of positional arguments. |
| 90 Map<String, Set<int>> _methodToLegalPositionalArgumentCount = {}; |
| 91 |
| 92 /// For non-static method transformations: |
| 93 /// Maps from name of method to the set of new target names seen for at least |
| 94 /// one instance (i.e. rewriting has been performed from key to all values in |
| 95 /// the mapped to set at least once). |
| 96 Map<Name, Set<String>> _rewrittenMethods = {}; |
| 97 |
| 98 /// For non-static method transformations: |
| 99 /// Maps a procedure to the mapping of argument signature to procedure stub. |
| 100 Map<Procedure, Map<String, Procedure>> _superProcedureCalls = {}; |
| 101 |
| 102 /// Whether in debug mode, i.e. if we can insert extra print statements for |
| 103 /// debugging purposes. |
| 104 bool _debug; |
| 105 |
| 106 /// For noSuchMethod calls. |
| 107 ClassHierarchy hierarchy; |
| 108 CoreTypes coreTypes; |
| 109 Constructor _invocationMirrorConstructor; // cached |
| 110 Procedure _listFrom; // cached |
| 111 |
| 112 MethodCallTransformer(this._debug); |
| 113 |
| 114 @override |
| 115 TreeNode visitProgram(Program node) { |
| 116 hierarchy = new ClassHierarchy(node); |
| 117 coreTypes = new CoreTypes(node); |
| 118 |
| 119 // First move body of all procedures that takes optional positional or named |
| 120 // parameters and record which non-static procedure names have optional |
| 121 // positional arguments. |
| 122 // Do the same for constructors. Then also rewrite constructor initializers |
| 123 // using LocalInitializer and sort named arguments in those initializers |
| 124 for (final library in node.libraries) { |
| 125 for (final procedure in new List<Procedure>.from(library.procedures)) { |
| 126 _moveAndTransformProcedure(procedure); |
| 127 } |
| 128 |
| 129 for (final clazz in library.classes) { |
| 130 for (final field in clazz.fields) { |
| 131 blacklistedSelectors.add(field.name.name); |
| 132 } |
| 133 |
| 134 for (final procedure in new List<Procedure>.from(clazz.procedures)) { |
| 135 // This call creates new procedures |
| 136 _moveAndTransformProcedure(procedure); |
| 137 _recordNonStaticProcedureAndVariableArguments(procedure); |
| 138 } |
| 139 |
| 140 for (final constructor |
| 141 in new List<Constructor>.from(clazz.constructors)) { |
| 142 // This call creates new constructors |
| 143 _moveAndTransformConstructor(constructor); |
| 144 } |
| 145 |
| 146 for (final constructor in clazz.constructors) { |
| 147 _rewriteConstructorInitializations(constructor); |
| 148 } |
| 149 } |
| 150 } |
| 151 |
| 152 // Rewrite calls |
| 153 node.transformChildren(this); |
| 154 |
| 155 // Now for all method calls that was rewritten, make sure those call |
| 156 // destinations actually exist, i.e. for each method with a matching name |
| 157 // where the called-with-arguments is legal, create a stub |
| 158 for (final library in node.libraries) { |
| 159 for (final clazz in library.classes) { |
| 160 for (final procedure in new List<Procedure>.from(clazz.procedures)) { |
| 161 // This call creates new procedures |
| 162 _createNeededNonStaticStubs(procedure); |
| 163 } |
| 164 } |
| 165 } |
| 166 |
| 167 return node; |
| 168 } |
| 169 |
| 170 @override |
| 171 TreeNode visitProcedure(Procedure node) { |
| 172 if (!_visited.contains(node)) { |
| 173 _visited.add(node); |
| 174 node.transformChildren(this); |
| 175 } |
| 176 return node; |
| 177 } |
| 178 |
| 179 @override |
| 180 TreeNode visitConstructor(Constructor node) { |
| 181 if (!_visited.contains(node)) { |
| 182 _visited.add(node); |
| 183 node.transformChildren(this); |
| 184 } |
| 185 return node; |
| 186 } |
| 187 |
| 188 @override |
| 189 TreeNode visitStaticInvocation(StaticInvocation node) { |
| 190 node.transformChildren(this); |
| 191 if (!_isMethod(node.target)) return node; |
| 192 if (!_hasAnyOptionalParameters(node.target.function)) return node; |
| 193 if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| 194 |
| 195 // Rewrite with let if needed (without named arguments it won't do anything) |
| 196 Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| 197 |
| 198 // Create/lookup target and set it as the new target |
| 199 node.target = _getNewTargetForStaticLikeInvocation( |
| 200 node.target, node.arguments, _staticProcedureCalls); |
| 201 |
| 202 // Now turn any named parameters into positional parameters |
| 203 _turnNamedArgumentsIntoPositional(node.arguments); |
| 204 return rewrittenNode; |
| 205 } |
| 206 |
| 207 @override |
| 208 TreeNode visitDirectMethodInvocation(DirectMethodInvocation node) { |
| 209 node.transformChildren(this); |
| 210 if (!_isMethod(node.target)) return node; |
| 211 if (!_hasAnyOptionalParameters(node.target.function)) return node; |
| 212 if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| 213 |
| 214 // Rewrite with let if needed (without named arguments it won't do anything) |
| 215 Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| 216 |
| 217 // Create/lookup target and set it as the new target |
| 218 node.target = _getNewTargetForStaticLikeInvocation( |
| 219 node.target, node.arguments, _superProcedureCalls); |
| 220 |
| 221 // Now turn any named parameters into positional parameters instead |
| 222 _turnNamedArgumentsIntoPositional(node.arguments); |
| 223 return rewrittenNode; |
| 224 } |
| 225 |
| 226 @override |
| 227 TreeNode visitSuperMethodInvocation(SuperMethodInvocation node) { |
| 228 // SuperMethodInvocation was changed since I originally wrote this, |
| 229 // and now it seems to never be called anyway. |
| 230 throw "visitSuperMethodInvocation is not implemented!"; |
| 231 } |
| 232 |
| 233 @override |
| 234 TreeNode visitMethodInvocation(MethodInvocation node) { |
| 235 node.transformChildren(this); |
| 236 final name = node.name.name; |
| 237 |
| 238 // Don't renamed calls to methods that clashes in name with a field |
| 239 // or is called "call". |
| 240 if (blacklistedSelectors.contains(name)) return node; |
| 241 |
| 242 // Rewrite with let if needed (without named arguments it won't do anything) |
| 243 Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| 244 |
| 245 String argumentsSignature = _createArgumentsSignature(node.arguments); |
| 246 if (node.arguments.named.isEmpty) { |
| 247 // Positional: Don't rewrite if no procedure with that name can be called |
| 248 // with a variable number of arguments, or where the number of arguments |
| 249 // called with here isn't a legal number of arguments to any such |
| 250 // procedure. |
| 251 // Note for named arguments: Named arguments are always rewritten |
| 252 // (except for 'call' methods) so there's no such check |
| 253 final okCounts = _methodToLegalPositionalArgumentCount[name]; |
| 254 |
| 255 if (okCounts == null || |
| 256 !okCounts.contains(node.arguments.positional.length)) { |
| 257 return node; |
| 258 } |
| 259 } |
| 260 |
| 261 // Rewrite this call |
| 262 final originalName = node.name; |
| 263 node.name = _createName(node.name, argumentsSignature); |
| 264 |
| 265 // Remember that we rewrote this call |
| 266 _rewrittenMethods |
| 267 .putIfAbsent(originalName, () => new Set<String>()) |
| 268 .add(argumentsSignature); |
| 269 |
| 270 // Now turn any named parameters into positional parameters instead |
| 271 _turnNamedArgumentsIntoPositional(node.arguments); |
| 272 return rewrittenNode; |
| 273 } |
| 274 |
| 275 @override |
| 276 TreeNode visitConstructorInvocation(ConstructorInvocation node) { |
| 277 node.transformChildren(this); |
| 278 if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| 279 |
| 280 Expression rewrittenNode; |
| 281 if (node.isConst) { |
| 282 // Sort named arguments by name => it's const so there's no side-effects! |
| 283 // but DO NOT rewrite with let! |
| 284 node.arguments.named.sort((a, b) => a.name.compareTo(b.name)); |
| 285 rewrittenNode = node; |
| 286 } else { |
| 287 rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| 288 } |
| 289 node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| 290 |
| 291 // Now turn named parameters into positional parameters instead |
| 292 _turnNamedArgumentsIntoPositional(node.arguments); |
| 293 return rewrittenNode; |
| 294 } |
| 295 |
| 296 @override |
| 297 TreeNode visitSuperInitializer(SuperInitializer node) { |
| 298 // Note that sorting was done in _rewriteConstructorInitializations |
| 299 node.transformChildren(this); |
| 300 if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| 301 |
| 302 node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| 303 |
| 304 // Now turn named parameters into positional parameters instead |
| 305 _turnNamedArgumentsIntoPositional(node.arguments); |
| 306 return node; |
| 307 } |
| 308 |
| 309 @override |
| 310 TreeNode visitRedirectingInitializer(RedirectingInitializer node) { |
| 311 // Note that sorting was done in _rewriteConstructorInitializations |
| 312 node.transformChildren(this); |
| 313 if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| 314 |
| 315 node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| 316 |
| 317 // Now turn named parameters into positional parameters instead |
| 318 _turnNamedArgumentsIntoPositional(node.arguments); |
| 319 return node; |
| 320 } |
| 321 |
| 322 /// Gets the new target for an invocation, using cache or creating a new one. |
| 323 /// |
| 324 /// Assumes that any let-rewrite, named argument sorting etc has been done |
| 325 /// already. |
| 326 Procedure _getNewTargetForStaticLikeInvocation(Procedure target, |
| 327 Arguments arguments, Map<Procedure, Map<String, Procedure>> cache) { |
| 328 final createdProcedures = cache.putIfAbsent(target, () => {}); |
| 329 |
| 330 // Rewrite target |
| 331 final argumentsSignature = _createArgumentsSignature(arguments); |
| 332 return createdProcedures[argumentsSignature] ?? |
| 333 _createAndCacheInvocationProcedure( |
| 334 argumentsSignature, |
| 335 arguments.positional.length, |
| 336 arguments.named.map((e) => e.name).toList(), |
| 337 target, |
| 338 _movedBodies[target], |
| 339 createdProcedures, |
| 340 true); |
| 341 } |
| 342 |
| 343 /// Rewrite the [Argument]s turning named arguments into positional arguments. |
| 344 /// |
| 345 /// Note that if the [Argument]s does not take any named parameters this |
| 346 /// method does nothing. |
| 347 void _turnNamedArgumentsIntoPositional(Arguments arguments) { |
| 348 for (final named in arguments.named) { |
| 349 arguments.positional.add(named.value..parent = arguments); |
| 350 } |
| 351 arguments.named.clear(); |
| 352 } |
| 353 |
| 354 /// Gets the new target for an invocation, using cache or creating a new one. |
| 355 /// |
| 356 /// Assumes that any let-rewrite, named argument sorting etc has been done |
| 357 /// already. |
| 358 Constructor _getNewTargetForConstructor( |
| 359 Constructor target, Arguments arguments) { |
| 360 if (!_isNotExternal(target)) return target; |
| 361 if (!_hasAnyOptionalParameters(target.function)) return target; |
| 362 |
| 363 final argumentsSignature = _createArgumentsSignature(arguments); |
| 364 final createdConstructor = _constructorCalls.putIfAbsent(target, () => {}); |
| 365 return createdConstructor[argumentsSignature] ?? |
| 366 _createAndCacheInvocationConstructor( |
| 367 argumentsSignature, |
| 368 arguments.positional.length, |
| 369 arguments.named.map((e) => e.name).toList(), |
| 370 target, |
| 371 _movedConstructors[target], |
| 372 createdConstructor, |
| 373 true); |
| 374 } |
| 375 |
| 376 /// Create a signature for the [Arguments]. |
| 377 /// |
| 378 /// Assumes that any needed sorting etc has already been done. |
| 379 /// |
| 380 /// Looks like x%positionalCount%named --- but it shouldn't matter if always |
| 381 /// using these methods |
| 382 String _createArgumentsSignature(Arguments arguments) { |
| 383 String namedString = arguments.named.map((e) => e.name).join("%"); |
| 384 return "${arguments.positional.length}%$namedString"; |
| 385 } |
| 386 |
| 387 /// Parse the argument signature. |
| 388 /// |
| 389 /// First element will be the string representation of the number of |
| 390 /// positional arguments used. |
| 391 /// The rest will be the named arguments, except that with no named arguments |
| 392 /// there still is a 2nd entry: the empty string... |
| 393 List<String> _parseArgumentsSignature(String argumentsSignature) { |
| 394 return argumentsSignature.split("%"); |
| 395 } |
| 396 |
| 397 /// Rewrites an expression with let, replacing expressions in the [Arguments]. |
| 398 /// |
| 399 /// Sorts the named arguments after rewriting with let. |
| 400 /// |
| 401 /// Note that this method does nothing if there are no named arguments, or the |
| 402 /// named arguments list contain only a single named argument as any sorting |
| 403 /// would have no effect. As such, the let-rewrite will also have no effect. |
| 404 /// In such a case the return value is [original]. |
| 405 Expression _rewriteWithLetAndSort(Expression original, Arguments arguments) { |
| 406 final named = arguments.named; |
| 407 |
| 408 // Only bother if names can be unordered |
| 409 if (named.length < 2) return original; |
| 410 |
| 411 // Rewrite named with let in given order |
| 412 Let let; |
| 413 for (int i = named.length - 1; i >= 0; i--) { |
| 414 VariableDeclaration letDeclaration = |
| 415 new VariableDeclaration.forValue(named[i].value); |
| 416 named[i].value = new VariableGet(letDeclaration)..parent = arguments; |
| 417 let = new Let(letDeclaration, let ?? original); |
| 418 } |
| 419 |
| 420 // Sort named arguments by name |
| 421 named.sort((a, b) => a.name.compareTo(b.name)); |
| 422 |
| 423 // Now also add the given positional arguments into the let |
| 424 final expressions = arguments.positional; |
| 425 for (int i = expressions.length - 1; i >= 0; i--) { |
| 426 VariableDeclaration letDeclaration = |
| 427 new VariableDeclaration.forValue(expressions[i]); |
| 428 expressions[i] = new VariableGet(letDeclaration)..parent = arguments; |
| 429 let = new Let(letDeclaration, let ?? original); |
| 430 } |
| 431 |
| 432 return let; |
| 433 } |
| 434 |
| 435 /// Creates all needed stubs for non static procedures. |
| 436 /// |
| 437 /// More specifically: If calls have been made to a procedure with the same |
| 438 /// name as this procedure with both 1 and 2 arguments, where both of these |
| 439 /// are legal inputs to this procedure, create stubs for both of them, |
| 440 /// each of which calls with whatever default parameter values are defined for |
| 441 /// the non-given arguments. |
| 442 void _createNeededNonStaticStubs(Procedure procedure) { |
| 443 final incomingCalls = _rewrittenMethods[procedure.name]; |
| 444 if (incomingCalls != null && |
| 445 procedure.kind == ProcedureKind.Method && |
| 446 !procedure.isStatic) { |
| 447 final createdOnSuper = _superProcedureCalls[procedure]; |
| 448 final names = |
| 449 procedure.function.namedParameters.map((e) => e.name).toSet(); |
| 450 |
| 451 // A procedure with this name was called on at least one object with |
| 452 // an argument signature like any in [incomingCalls] |
| 453 nextArgumentSignature: |
| 454 for (final argumentsSignature in incomingCalls) { |
| 455 // Skip if it was created in a super call already |
| 456 if (createdOnSuper != null && |
| 457 createdOnSuper.containsKey(argumentsSignature)) { |
| 458 continue; |
| 459 } |
| 460 |
| 461 final elements = _parseArgumentsSignature(argumentsSignature); |
| 462 int positional = int.parse(elements[0]); |
| 463 |
| 464 if (positional < procedure.function.requiredParameterCount || |
| 465 positional > procedure.function.positionalParameters.length) { |
| 466 // We don't take that number of positional parameters! |
| 467 // Call noSuchMethod in case anyone called on object with wrong |
| 468 // parameters, but where superclass does take these parameters. |
| 469 _createNoSuchMethodStub( |
| 470 argumentsSignature, positional, elements.sublist(1), procedure); |
| 471 continue; |
| 472 } |
| 473 |
| 474 if (elements.length > 2 || elements[1] != "") { |
| 475 // Named: Could the call be for this method? |
| 476 for (int i = 1; i < elements.length; i++) { |
| 477 String name = elements[i]; |
| 478 // Using a name that we don't have? |
| 479 if (!names.contains(name)) { |
| 480 // Call noSuchMethod in case anyone called on object with wrong |
| 481 // parameters, but where superclass does take these parameters. |
| 482 _createNoSuchMethodStub(argumentsSignature, positional, |
| 483 elements.sublist(1), procedure); |
| 484 continue nextArgumentSignature; |
| 485 } |
| 486 } |
| 487 } |
| 488 |
| 489 // Potential legal call => make stub |
| 490 // Note the ?? here: E.g. contains on list doesn't take optionals so it |
| 491 // wasn't moved, but calls were rewritten because contains on string |
| 492 // takes either 1 or 2 arguments. |
| 493 final destination = _movedBodies[procedure] ?? procedure; |
| 494 _createAndCacheInvocationProcedure(argumentsSignature, positional, |
| 495 elements.sublist(1), procedure, destination, {}, false); |
| 496 } |
| 497 } |
| 498 } |
| 499 |
| 500 /// Records how this procedure can be called (if it is non-static). |
| 501 /// |
| 502 /// More specifically: Assuming that the procedure given is non-static taking |
| 503 /// a variable number of positional parameters, record all number of arguments |
| 504 /// that is legal, e.g. foo(int a, [int b]) is legal for 1 and 2 parameters. |
| 505 /// If it takes named parameters, remember how many positional there is so |
| 506 /// we also know to rewrite calls without the named arguments. |
| 507 void _recordNonStaticProcedureAndVariableArguments(Procedure procedure) { |
| 508 if (_isMethod(procedure) && |
| 509 !procedure.isStatic && |
| 510 _hasAnyOptionalParameters(procedure.function)) { |
| 511 final name = procedure.name.name; |
| 512 final okCounts = _methodToLegalPositionalArgumentCount.putIfAbsent( |
| 513 name, () => new Set<int>()); |
| 514 for (int i = procedure.function.requiredParameterCount; |
| 515 i <= procedure.function.positionalParameters.length; |
| 516 i++) { |
| 517 okCounts.add(i); |
| 518 } |
| 519 } |
| 520 } |
| 521 |
| 522 /// Move body of procedure to new procedure and call that from this procedure. |
| 523 /// |
| 524 /// More specifically: For all procedures with optional positional parameters, |
| 525 /// or named parameters, create a new procedure without optional positional |
| 526 /// parameters and named parameters and move the body of the original |
| 527 /// procedure into this new procedure. |
| 528 /// Then make the body of the original procedure call the new procedure. |
| 529 /// |
| 530 /// The idea is that all rewrites should call the moved procedure instead, |
| 531 /// bypassing the optional/named arguments entirely. |
| 532 void _moveAndTransformProcedure(Procedure procedure) { |
| 533 if (_isMethod(procedure) && _hasAnyOptionalParameters(procedure.function)) { |
| 534 final function = procedure.function; |
| 535 |
| 536 // Create variable lists |
| 537 final newParameterDeclarations = <VariableDeclaration>[]; |
| 538 final newNamedParameterDeclarations = <VariableDeclaration>[]; |
| 539 final newParameterVariableGets = <Expression>[]; |
| 540 final targetParameters = function.positionalParameters; |
| 541 final targetNamedParameters = function.namedParameters; |
| 542 _moveVariableInitialization( |
| 543 targetParameters, |
| 544 targetNamedParameters, |
| 545 newParameterDeclarations, |
| 546 newNamedParameterDeclarations, |
| 547 newParameterVariableGets, |
| 548 procedure.function); |
| 549 |
| 550 // Create new procedure looking like the old one |
| 551 // (with the old body and parameters) |
| 552 FunctionNode functionNode = _createShallowFunctionCopy(function); |
| 553 final newProcedure = new Procedure( |
| 554 _createOriginalName(procedure), ProcedureKind.Method, functionNode, |
| 555 isAbstract: procedure.isAbstract, |
| 556 isStatic: procedure.isStatic, |
| 557 isConst: procedure.isConst, |
| 558 fileUri: procedure.fileUri); |
| 559 |
| 560 // Add procedure to the code |
| 561 _addMember(procedure, newProcedure); |
| 562 |
| 563 // Map moved body |
| 564 _movedBodies[procedure] = newProcedure; |
| 565 |
| 566 // Transform original procedure |
| 567 if (procedure.isAbstract && procedure.function.body == null) { |
| 568 // do basically nothing then |
| 569 procedure.function.positionalParameters = newParameterDeclarations; |
| 570 procedure.function.namedParameters = newNamedParameterDeclarations; |
| 571 } else if (procedure.isStatic) { |
| 572 final expression = new StaticInvocation( |
| 573 newProcedure, new Arguments(newParameterVariableGets)); |
| 574 final statement = new ReturnStatement(expression) |
| 575 ..parent = procedure.function; |
| 576 procedure.function.body = statement; |
| 577 procedure.function.positionalParameters = newParameterDeclarations; |
| 578 procedure.function.namedParameters = newNamedParameterDeclarations; |
| 579 } else { |
| 580 final expression = new DirectMethodInvocation(new ThisExpression(), |
| 581 newProcedure, new Arguments(newParameterVariableGets)); |
| 582 final statement = new ReturnStatement(expression) |
| 583 ..parent = procedure.function; |
| 584 procedure.function.body = statement; |
| 585 procedure.function.positionalParameters = newParameterDeclarations; |
| 586 procedure.function.namedParameters = newNamedParameterDeclarations; |
| 587 } |
| 588 |
| 589 if (_debug) { |
| 590 // Debug flag set: Print something to the terminal before returning to |
| 591 // easily detect if rewrites are missing. |
| 592 Expression debugPrint = _getPrintExpression( |
| 593 "DEBUG! Procedure shouldn't have been called...", procedure); |
| 594 procedure.function.body = new Block( |
| 595 [new ExpressionStatement(debugPrint), procedure.function.body]) |
| 596 ..parent = procedure.function; |
| 597 } |
| 598 |
| 599 // Mark original procedure as seen (i.e. don't transform it further) |
| 600 _visited.add(procedure); |
| 601 } |
| 602 } |
| 603 |
| 604 /// Rewrite constructor initializers by introducing variables and sorting. |
| 605 /// |
| 606 /// For any* [SuperInitializer] or [RedirectingInitializer], extract the |
| 607 /// parameters, put them into variables, then sorting the named parameters. |
| 608 /// The idea is to sort the named parameters without changing any invocation |
| 609 /// order. |
| 610 /// |
| 611 /// * only with at least 2 named arguments, otherwise sorting would do nothing |
| 612 void _rewriteConstructorInitializations(Constructor constructor) { |
| 613 if (_isNotExternal(constructor)) { |
| 614 // Basically copied from "super_calls.dart" |
| 615 List<Initializer> initializers = constructor.initializers; |
| 616 int foundIndex = -1; |
| 617 Arguments arguments; |
| 618 for (int i = initializers.length - 1; i >= 0; --i) { |
| 619 Initializer initializer = initializers[i]; |
| 620 if (initializer is SuperInitializer) { |
| 621 foundIndex = i; |
| 622 arguments = initializer.arguments; |
| 623 break; |
| 624 } else if (initializer is RedirectingInitializer) { |
| 625 foundIndex = i; |
| 626 arguments = initializer.arguments; |
| 627 break; |
| 628 } |
| 629 } |
| 630 if (foundIndex == -1) return; |
| 631 |
| 632 // Rewrite using variables if using named parameters (so we can sort them) |
| 633 // (note that with 1 named it cannot be unsorted so we don't bother) |
| 634 if (arguments.named.length < 2) return; |
| 635 |
| 636 int argumentCount = arguments.positional.length + arguments.named.length; |
| 637 |
| 638 // Make room for [argumentCount] [LocalInitializer]s before the |
| 639 // super/redirector call. |
| 640 initializers.length += argumentCount; |
| 641 initializers.setRange( |
| 642 foundIndex + argumentCount, // destination start (inclusive) |
| 643 initializers.length, // destination end (exclusive) |
| 644 initializers, // source list |
| 645 foundIndex); // source start index |
| 646 |
| 647 // Fill in the [argumentCount] reserved slots with the evaluation |
| 648 // expressions of the arguments to the super/redirector constructor call |
| 649 int storeIndex = foundIndex; |
| 650 for (int i = 0; i < arguments.positional.length; ++i) { |
| 651 var variable = |
| 652 new VariableDeclaration.forValue(arguments.positional[i]); |
| 653 arguments.positional[i] = new VariableGet(variable)..parent = arguments; |
| 654 initializers[storeIndex++] = new LocalInitializer(variable) |
| 655 ..parent = constructor; |
| 656 } |
| 657 for (int i = 0; i < arguments.named.length; ++i) { |
| 658 NamedExpression argument = arguments.named[i]; |
| 659 var variable = new VariableDeclaration.forValue(argument.value); |
| 660 arguments.named[i].value = new VariableGet(variable)..parent = argument; |
| 661 initializers[storeIndex++] = new LocalInitializer(variable) |
| 662 ..parent = constructor; |
| 663 } |
| 664 |
| 665 // Sort the named arguments |
| 666 arguments.named.sort((a, b) => a.name.compareTo(b.name)); |
| 667 } |
| 668 } |
| 669 |
| 670 /// Move body of constructor to new one and call that from this one. |
| 671 /// |
| 672 /// More specifically: For all constructors with optional positional |
| 673 /// parameters, or named parameters, create a new constructor without optional |
| 674 /// positional parameters and named parameters, and move the body of the |
| 675 /// original constructor into this new constructor. |
| 676 /// Then make the original constructor redirect to the new constructor. |
| 677 /// |
| 678 /// The idea is that all rewrites should call the moved constructor instead, |
| 679 /// bypassing the optional/named arguments entirely. |
| 680 /// |
| 681 /// This method is very similar to _moveAndTransformProcedure |
| 682 void _moveAndTransformConstructor(Constructor constructor) { |
| 683 if (_isNotExternal(constructor) && |
| 684 _hasAnyOptionalParameters(constructor.function)) { |
| 685 final function = constructor.function; |
| 686 |
| 687 // Create variable lists |
| 688 final newParameterDeclarations = <VariableDeclaration>[]; |
| 689 final newNamedParameterDeclarations = <VariableDeclaration>[]; |
| 690 final newParameterVariableGets = <Expression>[]; |
| 691 final targetParameters = function.positionalParameters; |
| 692 final targetNamedParameters = function.namedParameters; |
| 693 _moveVariableInitialization( |
| 694 targetParameters, |
| 695 targetNamedParameters, |
| 696 newParameterDeclarations, |
| 697 newNamedParameterDeclarations, |
| 698 newParameterVariableGets, |
| 699 constructor.function); |
| 700 |
| 701 // Create new constructor looking like the old one |
| 702 // (with the old body, parameters and initializers) |
| 703 FunctionNode functionNode = _createShallowFunctionCopy(function); |
| 704 final newConstructor = new Constructor(functionNode, |
| 705 name: _createOriginalName(constructor), |
| 706 isConst: constructor.isConst, |
| 707 isExternal: constructor.isExternal, |
| 708 initializers: constructor.initializers); |
| 709 |
| 710 // Add constructor to the code |
| 711 _addMember(constructor, newConstructor); |
| 712 |
| 713 // Map moved body |
| 714 _movedConstructors[constructor] = newConstructor; |
| 715 |
| 716 // Transform original constructor |
| 717 constructor.function.body = null; |
| 718 constructor.function.positionalParameters = newParameterDeclarations; |
| 719 constructor.function.namedParameters = newNamedParameterDeclarations; |
| 720 constructor.initializers = [ |
| 721 new RedirectingInitializer( |
| 722 newConstructor, new Arguments(newParameterVariableGets)) |
| 723 ..parent = constructor |
| 724 ]; |
| 725 |
| 726 if (_debug) { |
| 727 // Debug flag set: Print something to the terminal before returning to |
| 728 // easily detect if rewrites are missing. |
| 729 Expression debugPrint = _getPrintExpression( |
| 730 "DEBUG! Constructor shouldn't have been called...", constructor); |
| 731 var variable = new VariableDeclaration.forValue(debugPrint); |
| 732 final debugInitializer = new LocalInitializer(variable) |
| 733 ..parent = constructor; |
| 734 final redirector = constructor.initializers[0]; |
| 735 constructor.initializers = [debugInitializer, redirector]; |
| 736 } |
| 737 |
| 738 // Mark original procedure as seen (i.e. don't transform it further) |
| 739 _visited.add(constructor); |
| 740 } |
| 741 } |
| 742 |
| 743 /// Creates a new [FunctionNode] based on the given one. |
| 744 /// |
| 745 /// Parameters are taken directly (i.e. after returning the parameters will |
| 746 /// have a new parent (the returned value), but still be referenced in the |
| 747 /// original [FunctionNode]. |
| 748 /// The same goes for the body of the function. |
| 749 /// The caller should take steps to remedy this after this call. |
| 750 /// |
| 751 /// The parameters are no longer optional and named parameters have been |
| 752 /// sorted and turned into regular parameters in the returned [FunctionNode]. |
| 753 FunctionNode _createShallowFunctionCopy(FunctionNode function) { |
| 754 final newParameters = |
| 755 new List<VariableDeclaration>.from(function.positionalParameters); |
| 756 final named = new List<VariableDeclaration>.from(function.namedParameters); |
| 757 named.sort((a, b) => a.name.compareTo(b.name)); |
| 758 newParameters.addAll(named); |
| 759 final functionNode = new FunctionNode(function.body, |
| 760 positionalParameters: newParameters, |
| 761 namedParameters: [], |
| 762 requiredParameterCount: newParameters.length, |
| 763 returnType: function.returnType, |
| 764 asyncMarker: function.asyncMarker); |
| 765 return functionNode; |
| 766 } |
| 767 |
| 768 /// Creates new variables, moving old initializers into them |
| 769 /// |
| 770 /// Specifically: Given lists for output, create new variables based on |
| 771 /// original parameters. Any new variable will receive the original variables |
| 772 /// initializer, and the original variable will have its initializer set to |
| 773 /// null. |
| 774 /// Named parameters have been sorted in [newParameterVariableGets]. |
| 775 void _moveVariableInitialization( |
| 776 List<VariableDeclaration> originalParameters, |
| 777 List<VariableDeclaration> originalNamedParameters, |
| 778 List<VariableDeclaration> newParameterDeclarations, |
| 779 List<VariableDeclaration> newNamedParameterDeclarations, |
| 780 List<Expression> newParameterVariableGets, |
| 781 TreeNode newStuffParent) { |
| 782 for (final orgVar in originalParameters) { |
| 783 final variableDeclaration = new VariableDeclaration(orgVar.name, |
| 784 initializer: orgVar.initializer, |
| 785 type: orgVar.type, |
| 786 isFinal: orgVar.isFinal, |
| 787 isConst: orgVar.isConst)..parent = newStuffParent; |
| 788 variableDeclaration.initializer?.parent = variableDeclaration; |
| 789 newParameterDeclarations.add(variableDeclaration); |
| 790 orgVar.initializer = null; |
| 791 newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| 792 } |
| 793 |
| 794 // Named expressions in newParameterVariableGets should be sorted |
| 795 final tmp = new List<_Pair<String, Expression>>(); |
| 796 for (final orgVar in originalNamedParameters) { |
| 797 final variableDeclaration = new VariableDeclaration(orgVar.name, |
| 798 initializer: orgVar.initializer, |
| 799 type: orgVar.type, |
| 800 isFinal: orgVar.isFinal, |
| 801 isConst: orgVar.isConst)..parent = newStuffParent; |
| 802 variableDeclaration.initializer?.parent = variableDeclaration; |
| 803 newNamedParameterDeclarations.add(variableDeclaration); |
| 804 orgVar.initializer = null; |
| 805 tmp.add(new _Pair(orgVar.name, new VariableGet(variableDeclaration))); |
| 806 } |
| 807 tmp.sort((a, b) => a.key.compareTo(b.key)); |
| 808 for (final item in tmp) { |
| 809 newParameterVariableGets.add(item.value); |
| 810 } |
| 811 } |
| 812 |
| 813 /// Creates a stub redirecting to noSuchMethod. |
| 814 /// |
| 815 /// Needed because if B extends A, both have a foo method, but taking |
| 816 /// different optional parameters, a call on an instance of B with parameters |
| 817 /// for A should actually result in a noSuchMethod call, but if only A has |
| 818 /// the rewritten method name, that method will be called... |
| 819 /// TODO: We only have to create these stubs for arguments that a procedures |
| 820 /// super allows, otherwise it will become a noSuchMethod automatically! |
| 821 Procedure _createNoSuchMethodStub( |
| 822 String argumentsSignature, |
| 823 int positionalCount, |
| 824 List<String> givenNamedParameters, |
| 825 Procedure existing) { |
| 826 // Build parameter lists |
| 827 final newParameterDeclarations = <VariableDeclaration>[]; |
| 828 final newParameterVariableGets = <Expression>[]; |
| 829 for (int i = 0; i < positionalCount + givenNamedParameters.length; i++) { |
| 830 final variableDeclaration = new VariableDeclaration("v%$i"); |
| 831 newParameterDeclarations.add(variableDeclaration); |
| 832 newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| 833 } |
| 834 |
| 835 var procedureName = _createName(existing.name, argumentsSignature); |
| 836 |
| 837 // Find noSuchMethod to call |
| 838 Member noSuchMethod = hierarchy.getDispatchTarget( |
| 839 existing.enclosingClass, new Name("noSuchMethod")); |
| 840 Arguments argumentsToNoSuchMethod; |
| 841 |
| 842 if (noSuchMethod.function.positionalParameters.length == 1 && |
| 843 noSuchMethod.function.namedParameters.isEmpty) { |
| 844 // We have a correct noSuchMethod method. |
| 845 ConstructorInvocation invocation = _createInvocation( |
| 846 procedureName.name, new Arguments(newParameterVariableGets)); |
| 847 argumentsToNoSuchMethod = new Arguments([invocation]); |
| 848 } else { |
| 849 // Get noSuchMethod on Object then... |
| 850 noSuchMethod = hierarchy.getDispatchTarget( |
| 851 hierarchy.rootClass, new Name("noSuchMethod")); |
| 852 ConstructorInvocation invocation = _createInvocation( |
| 853 procedureName.name, new Arguments(newParameterVariableGets)); |
| 854 ConstructorInvocation invocationPrime = |
| 855 _createInvocation("noSuchMethod", new Arguments([invocation])); |
| 856 argumentsToNoSuchMethod = new Arguments([invocationPrime]); |
| 857 } |
| 858 |
| 859 // Create return statement to call noSuchMethod |
| 860 ReturnStatement statement; |
| 861 final expression = new DirectMethodInvocation( |
| 862 new ThisExpression(), noSuchMethod, argumentsToNoSuchMethod); |
| 863 statement = new ReturnStatement(expression); |
| 864 |
| 865 // Build procedure |
| 866 final functionNode = new FunctionNode(statement, |
| 867 positionalParameters: newParameterDeclarations, |
| 868 namedParameters: [], |
| 869 requiredParameterCount: newParameterDeclarations.length, |
| 870 returnType: existing.function.returnType, |
| 871 asyncMarker: existing.function.asyncMarker); |
| 872 final procedure = new Procedure( |
| 873 procedureName, ProcedureKind.Method, functionNode, |
| 874 isStatic: existing.isStatic, fileUri: existing.fileUri); |
| 875 |
| 876 // Add procedure to the code |
| 877 _addMember(existing, procedure); |
| 878 |
| 879 // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| 880 _visited.add(procedure); |
| 881 |
| 882 return procedure; |
| 883 } |
| 884 |
| 885 /// Creates an "new _InvocationMirror(...)" invocation. |
| 886 ConstructorInvocation _createInvocation( |
| 887 String methodName, Arguments callArguments) { |
| 888 if (_invocationMirrorConstructor == null) { |
| 889 Class clazz = coreTypes.getCoreClass('dart:core', '_InvocationMirror'); |
| 890 _invocationMirrorConstructor = clazz.constructors[0]; |
| 891 } |
| 892 |
| 893 // The _InvocationMirror constructor takes the following arguments: |
| 894 // * Method name (a string). |
| 895 // * An arguments descriptor - a list consisting of: |
| 896 // - number of arguments (including receiver). |
| 897 // - number of positional arguments (including receiver). |
| 898 // - pairs (2 entries in the list) of |
| 899 // * named arguments name. |
| 900 // * index of named argument in arguments list. |
| 901 // * A list of arguments, where the first ones are the positional arguments. |
| 902 // * Whether it's a super invocation or not. |
| 903 |
| 904 int numPositionalArguments = callArguments.positional.length + 1; |
| 905 int numArguments = numPositionalArguments + callArguments.named.length; |
| 906 List<Expression> argumentsDescriptor = [ |
| 907 new IntLiteral(numArguments), |
| 908 new IntLiteral(numPositionalArguments) |
| 909 ]; |
| 910 List<Expression> arguments = []; |
| 911 arguments.add(new ThisExpression()); |
| 912 for (Expression pos in callArguments.positional) { |
| 913 arguments.add(pos); |
| 914 } |
| 915 for (NamedExpression named in callArguments.named) { |
| 916 argumentsDescriptor.add(new StringLiteral(named.name)); |
| 917 argumentsDescriptor.add(new IntLiteral(arguments.length)); |
| 918 arguments.add(named.value); |
| 919 } |
| 920 |
| 921 return new ConstructorInvocation( |
| 922 _invocationMirrorConstructor, |
| 923 new Arguments([ |
| 924 new StringLiteral(methodName), |
| 925 _fixedLengthList(argumentsDescriptor), |
| 926 _fixedLengthList(arguments), |
| 927 new BoolLiteral(false) |
| 928 ])); |
| 929 } |
| 930 |
| 931 /// Create a fixed length list containing given expressions. |
| 932 Expression _fixedLengthList(List<Expression> list) { |
| 933 if (_listFrom == null) { |
| 934 Class clazz = coreTypes.getCoreClass('dart:core', 'List'); |
| 935 _listFrom = clazz.procedures.firstWhere((c) => c.name.name == "from"); |
| 936 } |
| 937 return new StaticInvocation( |
| 938 _listFrom, |
| 939 new Arguments([new ListLiteral(list)], |
| 940 named: [new NamedExpression("growable", new BoolLiteral(false))], |
| 941 types: [const DynamicType()])); |
| 942 } |
| 943 |
| 944 /// Creates a new procedure taking given arguments, caching it. |
| 945 /// |
| 946 /// Copies any non-given default values for parameters into the new procedure |
| 947 /// to be able to call the [realTarget] without using optionals and named |
| 948 /// parameters. |
| 949 Procedure _createAndCacheInvocationProcedure( |
| 950 String argumentsSignature, |
| 951 int positionalCount, |
| 952 List<String> givenNamedParameters, |
| 953 Procedure target, |
| 954 Procedure realTarget, |
| 955 Map<String, Procedure> createdProcedures, |
| 956 bool doSpecialCaseForAllParameters) { |
| 957 // Special case: Calling with all parameters |
| 958 if (doSpecialCaseForAllParameters && |
| 959 positionalCount == target.function.positionalParameters.length && |
| 960 givenNamedParameters.length == target.function.namedParameters.length) { |
| 961 // We don't cache this procedure as this could make it look like |
| 962 // something with name argumentsSignature actually exists |
| 963 // while it doesn't (which is bad as we could then decide that we don't |
| 964 // need to create a stub even though we do!) |
| 965 return realTarget; |
| 966 } |
| 967 |
| 968 // Create and cache (save) constructor |
| 969 |
| 970 // Build parameter lists |
| 971 final newParameterDeclarations = <VariableDeclaration>[]; |
| 972 final newParameterVariableGets = <Expression>[]; |
| 973 _extractAndCreateParameters(positionalCount, newParameterDeclarations, |
| 974 newParameterVariableGets, target, givenNamedParameters); |
| 975 |
| 976 // Create return statement to call real target |
| 977 ReturnStatement statement; |
| 978 if (target.isAbstract && target.function?.body == null) { |
| 979 // statement should just be null then |
| 980 } else if (target.isStatic) { |
| 981 final expression = new StaticInvocation( |
| 982 realTarget, new Arguments(newParameterVariableGets)); |
| 983 statement = new ReturnStatement(expression); |
| 984 } else { |
| 985 final expression = new DirectMethodInvocation(new ThisExpression(), |
| 986 realTarget, new Arguments(newParameterVariableGets)); |
| 987 statement = new ReturnStatement(expression); |
| 988 } |
| 989 |
| 990 // Build procedure |
| 991 final functionNode = new FunctionNode(statement, |
| 992 positionalParameters: newParameterDeclarations, |
| 993 namedParameters: [], |
| 994 requiredParameterCount: newParameterDeclarations.length, |
| 995 returnType: target.function.returnType, |
| 996 asyncMarker: target.function.asyncMarker); |
| 997 final procedure = new Procedure( |
| 998 _createName(target.name, argumentsSignature), |
| 999 ProcedureKind.Method, |
| 1000 functionNode, |
| 1001 isAbstract: target.isAbstract, |
| 1002 isStatic: target.isStatic, |
| 1003 isConst: target.isConst, |
| 1004 fileUri: target.fileUri); |
| 1005 |
| 1006 // Add procedure to the code |
| 1007 _addMember(target, procedure); |
| 1008 |
| 1009 // Cache it for future reference |
| 1010 createdProcedures[argumentsSignature] = procedure; |
| 1011 |
| 1012 // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| 1013 _visited.add(procedure); |
| 1014 |
| 1015 return procedure; |
| 1016 } |
| 1017 |
| 1018 /// Creates a new constructor taking given arguments, caching it. |
| 1019 /// |
| 1020 /// Copies any non-given default values for parameters into the new |
| 1021 /// constructor to be able to call the [realTarget] without using optionals |
| 1022 /// and named parameters. |
| 1023 Constructor _createAndCacheInvocationConstructor( |
| 1024 String argumentsSignature, |
| 1025 int positionalCount, |
| 1026 List<String> givenNamedParameters, |
| 1027 Constructor target, |
| 1028 Constructor realTarget, |
| 1029 Map<String, Constructor> createdConstructor, |
| 1030 bool doSpecialCaseForAllParameters) { |
| 1031 // Special case: Calling with all parameters |
| 1032 if (doSpecialCaseForAllParameters && |
| 1033 positionalCount == target.function.positionalParameters.length && |
| 1034 givenNamedParameters.length == target.function.namedParameters.length) { |
| 1035 createdConstructor[argumentsSignature] = realTarget; |
| 1036 return realTarget; |
| 1037 } |
| 1038 |
| 1039 // Create and cache (save) constructor |
| 1040 |
| 1041 // Build parameter lists |
| 1042 final newParameterDeclarations = <VariableDeclaration>[]; |
| 1043 final newParameterVariableGets = <Expression>[]; |
| 1044 _extractAndCreateParameters(positionalCount, newParameterDeclarations, |
| 1045 newParameterVariableGets, target, givenNamedParameters); |
| 1046 |
| 1047 // Build constructor |
| 1048 final functionNode = new FunctionNode(null, |
| 1049 positionalParameters: newParameterDeclarations, |
| 1050 namedParameters: [], |
| 1051 requiredParameterCount: newParameterDeclarations.length, |
| 1052 returnType: target.function.returnType, |
| 1053 asyncMarker: target.function.asyncMarker); |
| 1054 final constructor = new Constructor(functionNode, |
| 1055 name: _createName(target.name, argumentsSignature), |
| 1056 isConst: target.isConst, |
| 1057 isExternal: target.isExternal, |
| 1058 initializers: [ |
| 1059 new RedirectingInitializer( |
| 1060 realTarget, new Arguments(newParameterVariableGets)) |
| 1061 ]); |
| 1062 |
| 1063 // Add procedure to the code |
| 1064 _addMember(target, constructor); |
| 1065 |
| 1066 // Cache it for future reference |
| 1067 createdConstructor[argumentsSignature] = constructor; |
| 1068 |
| 1069 // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| 1070 _visited.add(constructor); |
| 1071 |
| 1072 return constructor; |
| 1073 } |
| 1074 |
| 1075 /// Extracts and creates parameters into the first two given lists. |
| 1076 /// |
| 1077 /// What is done: |
| 1078 /// Step 1: Re-create the parameters given (i.e. the non-optional positional |
| 1079 /// ones) - i.e. create a new variable with the same name etc, put it in |
| 1080 /// [newParameterDeclarations]; create VariableGet for that and put it in |
| 1081 /// [newParameterVariableGets] |
| 1082 /// Step 2: Re-create the positional parameters NOT given, i.e. insert |
| 1083 /// defaults and add to [newParameterVariableGets] only. |
| 1084 /// Step 3: Re-create the named arguments (in sorted order). For actually |
| 1085 /// given named parameters, do as in step 1, for not-given named parameters |
| 1086 /// do as in step 2. |
| 1087 /// |
| 1088 /// NOTE: [newParameterDeclarations] and [newParameterVariableGets] are OUTPUT |
| 1089 /// lists. |
| 1090 void _extractAndCreateParameters( |
| 1091 int positionalCount, |
| 1092 List<VariableDeclaration> newParameterDeclarations, |
| 1093 List<Expression> newParameterVariableGets, |
| 1094 Member target, |
| 1095 List<String> givenNamedParameters) { |
| 1096 // First re-create the parameters given (i.e. the non-optional positional on
es) |
| 1097 final targetParameters = target.function.positionalParameters; |
| 1098 positionalCount = math.min(positionalCount, targetParameters.length); |
| 1099 for (int i = 0; i < positionalCount; i++) { |
| 1100 final orgVar = targetParameters[i]; |
| 1101 final variableDeclaration = new VariableDeclaration(orgVar.name, |
| 1102 type: orgVar.type, isFinal: orgVar.isFinal, isConst: orgVar.isConst); |
| 1103 newParameterDeclarations.add(variableDeclaration); |
| 1104 newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| 1105 } |
| 1106 |
| 1107 // Default parameters for the rest of them |
| 1108 _fillInPositionalParameters( |
| 1109 positionalCount, target, newParameterVariableGets); |
| 1110 |
| 1111 // Then all named parameters (given here or not) |
| 1112 final orgNamed = |
| 1113 new List<VariableDeclaration>.from(target.function.namedParameters); |
| 1114 orgNamed.sort((a, b) => a.name.compareTo(b.name)); |
| 1115 final givenArgumentsIterator = givenNamedParameters.iterator; |
| 1116 givenArgumentsIterator.moveNext(); |
| 1117 for (VariableDeclaration named in orgNamed) { |
| 1118 if (givenArgumentsIterator.current == named.name) { |
| 1119 // We have that one: Use it and move the iterator |
| 1120 final variableDeclaration = new VariableDeclaration(named.name); |
| 1121 newParameterDeclarations.add(variableDeclaration); |
| 1122 newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| 1123 givenArgumentsIterator.moveNext(); |
| 1124 } else { |
| 1125 // We don't have that one: Fill it in |
| 1126 _fillInSingleParameter(named, newParameterVariableGets, target); |
| 1127 } |
| 1128 } |
| 1129 } |
| 1130 |
| 1131 /// Adds the new member the same place as the existing member |
| 1132 void _addMember(Member existingMember, Member newMember) { |
| 1133 if (existingMember.enclosingClass != null) { |
| 1134 existingMember.enclosingClass.addMember(newMember); |
| 1135 } else { |
| 1136 existingMember.enclosingLibrary.addMember(newMember); |
| 1137 } |
| 1138 } |
| 1139 |
| 1140 /// Create expressions based on the default values from the given [Member]. |
| 1141 /// |
| 1142 /// More specifically: static gets and nulls will be "copied" whereas other |
| 1143 /// things (e.g. literals or things like "a+b") will be moved from the |
| 1144 /// original member as argument initializers to const fields and both the |
| 1145 /// original member and the expression-copy will use static gets to these. |
| 1146 void _fillInPositionalParameters( |
| 1147 int startFrom, Member copyFrom, List<Expression> fillInto) { |
| 1148 final targetParameters = copyFrom.function.positionalParameters; |
| 1149 for (int i = startFrom; i < targetParameters.length; i++) { |
| 1150 final parameter = targetParameters[i]; |
| 1151 _fillInSingleParameter(parameter, fillInto, copyFrom); |
| 1152 } |
| 1153 } |
| 1154 |
| 1155 /// Create expression based on the default values from the given variable. |
| 1156 /// |
| 1157 /// More specifically: a static get or null will be "copied" whereas other |
| 1158 /// things (e.g. literals or things like "a+b") will be moved from the |
| 1159 /// original member as an argument initializer to a const field and both the |
| 1160 /// original member and the expression-copy will use a static get to it. |
| 1161 void _fillInSingleParameter(VariableDeclaration parameter, |
| 1162 List<Expression> fillInto, Member copyFrom) { |
| 1163 if (parameter.initializer is StaticGet) { |
| 1164 // Reference to const => recreate it |
| 1165 StaticGet staticGet = parameter.initializer; |
| 1166 fillInto.add(new StaticGet(staticGet.target)); |
| 1167 } else if (parameter.initializer == null) { |
| 1168 // No default given => output null |
| 1169 fillInto.add(new NullLiteral()); |
| 1170 } else if (parameter.initializer is IntLiteral) { |
| 1171 // Int literal => recreate (or else class ByteBuffer in typed_data will |
| 1172 // get 2 fields and the C++ code will complain!) |
| 1173 IntLiteral value = parameter.initializer; |
| 1174 fillInto.add(new IntLiteral(value.value)); |
| 1175 } else { |
| 1176 // Advanced stuff => move to static const field and reference that |
| 1177 final initializer = parameter.initializer; |
| 1178 final f = new Field( |
| 1179 new Name('${copyFrom.name.name}%_${parameter.name}', |
| 1180 copyFrom.enclosingLibrary), |
| 1181 type: parameter.type, |
| 1182 initializer: initializer, |
| 1183 isFinal: false, |
| 1184 isConst: true, |
| 1185 isStatic: true, |
| 1186 fileUri: copyFrom.enclosingClass?.fileUri ?? |
| 1187 copyFrom.enclosingLibrary.fileUri); |
| 1188 initializer.parent = f; |
| 1189 |
| 1190 // Add field to the code |
| 1191 if (copyFrom.enclosingClass != null) { |
| 1192 copyFrom.enclosingClass.addMember(f); |
| 1193 } else { |
| 1194 copyFrom.enclosingLibrary.addMember(f); |
| 1195 } |
| 1196 |
| 1197 // Use it at the call site |
| 1198 fillInto.add(new StaticGet(f)); |
| 1199 |
| 1200 // Now replace the initializer in the method to a StaticGet |
| 1201 parameter.initializer = new StaticGet(f)..parent = parameter; |
| 1202 } |
| 1203 } |
| 1204 |
| 1205 /// Create an "original name" for a member. |
| 1206 /// |
| 1207 /// Specifically, for a member "x" just returns "x%original"; |
| 1208 Name _createOriginalName(Member member) { |
| 1209 return new Name("${member.name.name}%original", member.enclosingLibrary); |
| 1210 } |
| 1211 |
| 1212 /// Create a [Name] based on current name and argument signature. |
| 1213 Name _createName(Name name, String argumentsSignature) { |
| 1214 String nameString = '${name.name}%$argumentsSignature'; |
| 1215 return new Name(nameString, name.library); |
| 1216 } |
| 1217 |
| 1218 /// Is the procedure a method? |
| 1219 bool _isMethod(Procedure procedure) => procedure.kind == ProcedureKind.Method; |
| 1220 |
| 1221 /// Is the procedure NOT marked as external? |
| 1222 bool _isNotExternal(Constructor constructor) => !constructor.isExternal; |
| 1223 |
| 1224 /// Does the target function have any optional arguments? (positional/named) |
| 1225 bool _hasAnyOptionalParameters(FunctionNode targetFunction) => |
| 1226 _hasOptionalParameters(targetFunction) || |
| 1227 _hasNamedParameters(targetFunction); |
| 1228 |
| 1229 /// Does the target function have optional positional arguments? |
| 1230 bool _hasOptionalParameters(FunctionNode targetFunction) => |
| 1231 targetFunction.positionalParameters.length > |
| 1232 targetFunction.requiredParameterCount; |
| 1233 |
| 1234 /// Does the target function have named parameters? |
| 1235 bool _hasNamedParameters(FunctionNode targetFunction) => |
| 1236 targetFunction.namedParameters.isNotEmpty; |
| 1237 |
| 1238 bool _callIsLegal(FunctionNode targetFunction, Arguments arguments) { |
| 1239 if ((targetFunction.requiredParameterCount > arguments.positional.length) || |
| 1240 (targetFunction.positionalParameters.length < |
| 1241 arguments.positional.length)) { |
| 1242 // Given too few or too many positional arguments |
| 1243 return false; |
| 1244 } |
| 1245 |
| 1246 // Do we give named that we don't take? |
| 1247 Set<String> givenNamed = arguments.named.map((v) => v.name).toSet(); |
| 1248 Set<String> takenNamed = |
| 1249 targetFunction.namedParameters.map((v) => v.name).toSet(); |
| 1250 givenNamed.removeAll(takenNamed); |
| 1251 return givenNamed.isEmpty; |
| 1252 } |
| 1253 |
| 1254 // Below methods used to add debug prints etc |
| 1255 |
| 1256 Library _getDartCoreLibrary(Program program) { |
| 1257 if (program == null) return null; |
| 1258 return program.libraries.firstWhere((lib) => |
| 1259 lib.importUri.scheme == 'dart' && lib.importUri.path == 'core'); |
| 1260 } |
| 1261 |
| 1262 Procedure _getProcedureInLib(Library lib, String name) { |
| 1263 if (lib == null) return null; |
| 1264 return lib.procedures |
| 1265 .firstWhere((procedure) => procedure.name.name == name); |
| 1266 } |
| 1267 |
| 1268 Procedure _getProcedureInClassInLib( |
| 1269 Library lib, String className, String procedureName) { |
| 1270 if (lib == null) return null; |
| 1271 Class clazz = lib.classes.firstWhere((clazz) => clazz.name == className); |
| 1272 return clazz.procedures |
| 1273 .firstWhere((procedure) => procedure.name.name == procedureName); |
| 1274 } |
| 1275 |
| 1276 Expression _getPrintExpression(String msg, TreeNode treeNode) { |
| 1277 TreeNode program = treeNode; |
| 1278 while (program is! Program) program = program.parent; |
| 1279 var finalMsg = msg; |
| 1280 if (treeNode is Member) { |
| 1281 finalMsg += " [ ${treeNode.name.name} ]"; |
| 1282 if (treeNode.enclosingClass != null) { |
| 1283 finalMsg += " [ class ${treeNode.enclosingClass.name} ]"; |
| 1284 } |
| 1285 if (treeNode.enclosingLibrary != null) { |
| 1286 finalMsg += " [ lib ${treeNode.enclosingLibrary.name} ]"; |
| 1287 } |
| 1288 } |
| 1289 |
| 1290 var stacktrace = new StaticGet(_getProcedureInClassInLib( |
| 1291 _getDartCoreLibrary(program), 'StackTrace', 'current')); |
| 1292 var printStackTrace = new StaticInvocation( |
| 1293 _getProcedureInLib(_getDartCoreLibrary(program), 'print'), |
| 1294 new Arguments([ |
| 1295 new StringConcatenation([ |
| 1296 new StringLiteral(finalMsg), |
| 1297 new StringLiteral("\n"), |
| 1298 stacktrace, |
| 1299 new StringLiteral("\n") |
| 1300 ]) |
| 1301 ])); |
| 1302 |
| 1303 return printStackTrace; |
| 1304 } |
| 1305 } |
| 1306 |
| 1307 class _Pair<K, V> { |
| 1308 final K key; |
| 1309 final V value; |
| 1310 |
| 1311 _Pair(this.key, this.value); |
| 1312 } |
| OLD | NEW |