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