| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library kernel.transformations.method_call; |
| |
| import 'dart:math' as math; |
| |
| import '../ast.dart'; |
| import '../class_hierarchy.dart'; |
| import '../core_types.dart'; |
| import '../kernel.dart'; |
| import '../visitor.dart'; |
| |
| /// Problems with the method rewrite transformation: |
| /// |
| /// * Cannot rewrite invocations to things called "call" because of tear-offs |
| /// and whatnot that when invoked turns into variableName.call(...). |
| /// |
| /// * Cannot rewrite invocations to things sharing a name with a field because |
| /// one could have called clazz.fieldName(...). |
| /// |
| /// * Rewrites will make stacktraces look weird. |
| /// |
| /// * Rewrites will make noSuchMethod look weird --- e.g. calling a non-existing |
| /// function foo(a: 42) turns into foo%0%a(42), i.e. the method name has |
| /// changed ("foo" vs "foo%0%a") and the arguments has changed (named "a" vs |
| /// positional). |
| /// NOTE: At least for now this can be fixed by changing the |
| /// invocation_mirror_patch file. Doing this I can make all dill, language |
| /// and co19 tests pass! |
| /// |
| /// Somewhat weird: |
| /// |
| /// * Inserts methods that redirect to the correct noSuchMethod invocation |
| /// so that program #1 example below will work. |
| /// The reason it otherwise wouldn't is that b.foo(499, named1: 88) is |
| /// rewritten to b.foo%1%named1(499, 88) which is not legal for class B |
| /// (thus the method would not be create there) but IS legal for Bs parent |
| /// class (A), so it would be created there. The call will thus go to the |
| /// super (A) but shouldn't as foo was overwritten in B. |
| /// |
| /// Program #1 example: |
| /// class A { |
| /// foo(required1, { named1: 499}) => print("Hello from class A"); |
| /// } |
| /// |
| /// class B extends A { |
| /// foo(required1) => print("Hello from class B"); |
| /// } |
| /// |
| /// main() { |
| /// var b = new B(); |
| /// b.foo(499, named1: 88); |
| /// } |
| Program transformProgram( |
| CoreTypes coreTypes, ClassHierarchy hierarchy, Program program, |
| [debug = false]) { |
| new MethodCallTransformer(coreTypes, hierarchy, debug).visitProgram(program); |
| return program; |
| } |
| |
| class MethodCallTransformer extends Transformer { |
| final CoreTypes coreTypes; |
| |
| /// Keep track of "visited" procedures and constructors to not visit already |
| /// visited stuff, nor visit newly created stubs. |
| Set<Member> _visited = new Set<Member>(); |
| |
| /// Some things currently cannot be rewritten. Calls to methods called "call" |
| /// (because invoking tear-offs and closures and whatnot becomes .call) |
| /// as well as clashes with field names as a field can contain a function |
| /// and one can then do a clazz.fieldName(...). |
| Set<String> blacklistedSelectors = new Set<String>.from(["call"]); |
| |
| /// Map from a "originally named" procedure to the "%original" procedure |
| /// for procedures that was moved. |
| Map<Procedure, Procedure> _movedBodies = {}; |
| |
| /// Map from a "originally named" constructor to the "%original" |
| /// constructor for constructors that was moved. |
| Map<Constructor, Constructor> _movedConstructors = {}; |
| |
| /// For static method transformations: |
| /// Maps a procedure to the mapping of argument signature to procedure stub. |
| Map<Procedure, Map<String, Procedure>> _staticProcedureCalls = {}; |
| |
| /// For constructor transformations: |
| /// Maps a constructor to a mapping of argument signature to constructor stub. |
| Map<Constructor, Map<String, Constructor>> _constructorCalls = {}; |
| |
| /// For non-static method transformations: |
| /// Maps from method name to the set of legal number of positional arguments. |
| Map<String, Set<int>> _methodToLegalPositionalArgumentCount = {}; |
| |
| /// For non-static method transformations: |
| /// Maps from name of method to the set of new target names seen for at least |
| /// one instance (i.e. rewriting has been performed from key to all values in |
| /// the mapped to set at least once). |
| Map<Name, Set<String>> _rewrittenMethods = {}; |
| |
| /// For non-static method transformations: |
| /// Maps a procedure to the mapping of argument signature to procedure stub. |
| Map<Procedure, Map<String, Procedure>> _superProcedureCalls = {}; |
| |
| /// Whether in debug mode, i.e. if we can insert extra print statements for |
| /// debugging purposes. |
| bool _debug; |
| |
| /// For noSuchMethod calls. |
| ClassHierarchy hierarchy; |
| Constructor _invocationMirrorConstructor; // cached |
| Procedure _listFrom; // cached |
| |
| MethodCallTransformer(this.coreTypes, this.hierarchy, this._debug); |
| |
| @override |
| TreeNode visitProgram(Program node) { |
| // First move body of all procedures that takes optional positional or named |
| // parameters and record which non-static procedure names have optional |
| // positional arguments. |
| // Do the same for constructors. Then also rewrite constructor initializers |
| // using LocalInitializer and sort named arguments in those initializers |
| for (final library in node.libraries) { |
| for (final procedure in new List<Procedure>.from(library.procedures)) { |
| _moveAndTransformProcedure(procedure); |
| } |
| |
| for (final clazz in library.classes) { |
| for (final field in clazz.fields) { |
| blacklistedSelectors.add(field.name.name); |
| } |
| |
| for (final procedure in new List<Procedure>.from(clazz.procedures)) { |
| // This call creates new procedures |
| _moveAndTransformProcedure(procedure); |
| _recordNonStaticProcedureAndVariableArguments(procedure); |
| } |
| |
| for (final constructor |
| in new List<Constructor>.from(clazz.constructors)) { |
| // This call creates new constructors |
| _moveAndTransformConstructor(constructor); |
| } |
| |
| for (final constructor in clazz.constructors) { |
| _rewriteConstructorInitializations(constructor); |
| } |
| } |
| } |
| |
| // Rewrite calls |
| node.transformChildren(this); |
| |
| // Now for all method calls that was rewritten, make sure those call |
| // destinations actually exist, i.e. for each method with a matching name |
| // where the called-with-arguments is legal, create a stub |
| for (final library in node.libraries) { |
| for (final clazz in library.classes) { |
| for (final procedure in new List<Procedure>.from(clazz.procedures)) { |
| // This call creates new procedures |
| _createNeededNonStaticStubs(procedure); |
| } |
| } |
| } |
| |
| return node; |
| } |
| |
| @override |
| TreeNode visitProcedure(Procedure node) { |
| if (!_visited.contains(node)) { |
| _visited.add(node); |
| node.transformChildren(this); |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitConstructor(Constructor node) { |
| if (!_visited.contains(node)) { |
| _visited.add(node); |
| node.transformChildren(this); |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitStaticInvocation(StaticInvocation node) { |
| node.transformChildren(this); |
| if (!_isMethod(node.target)) return node; |
| if (!_hasAnyOptionalParameters(node.target.function)) return node; |
| if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| |
| // Rewrite with let if needed (without named arguments it won't do anything) |
| Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| |
| // Create/lookup target and set it as the new target |
| node.target = _getNewTargetForStaticLikeInvocation( |
| node.target, node.arguments, _staticProcedureCalls); |
| |
| // Now turn any named parameters into positional parameters |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return rewrittenNode; |
| } |
| |
| @override |
| TreeNode visitDirectMethodInvocation(DirectMethodInvocation node) { |
| node.transformChildren(this); |
| if (!_isMethod(node.target)) return node; |
| if (!_hasAnyOptionalParameters(node.target.function)) return node; |
| if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| |
| // Rewrite with let if needed (without named arguments it won't do anything) |
| Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| |
| // Create/lookup target and set it as the new target |
| node.target = _getNewTargetForStaticLikeInvocation( |
| node.target, node.arguments, _superProcedureCalls); |
| |
| // Now turn any named parameters into positional parameters instead |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return rewrittenNode; |
| } |
| |
| @override |
| TreeNode visitSuperMethodInvocation(SuperMethodInvocation node) { |
| // SuperMethodInvocation was changed since I originally wrote this, |
| // and now it seems to never be called anyway. |
| throw "visitSuperMethodInvocation is not implemented!"; |
| } |
| |
| @override |
| TreeNode visitMethodInvocation(MethodInvocation node) { |
| node.transformChildren(this); |
| final name = node.name.name; |
| |
| // Don't renamed calls to methods that clashes in name with a field |
| // or is called "call". |
| if (blacklistedSelectors.contains(name)) return node; |
| |
| // Rewrite with let if needed (without named arguments it won't do anything) |
| Expression rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| |
| String argumentsSignature = _createArgumentsSignature(node.arguments); |
| if (node.arguments.named.isEmpty) { |
| // Positional: Don't rewrite if no procedure with that name can be called |
| // with a variable number of arguments, or where the number of arguments |
| // called with here isn't a legal number of arguments to any such |
| // procedure. |
| // Note for named arguments: Named arguments are always rewritten |
| // (except for 'call' methods) so there's no such check |
| final okCounts = _methodToLegalPositionalArgumentCount[name]; |
| |
| if (okCounts == null || |
| !okCounts.contains(node.arguments.positional.length)) { |
| return node; |
| } |
| } |
| |
| // Rewrite this call |
| final originalName = node.name; |
| node.name = _createName(node.name, argumentsSignature); |
| |
| // Remember that we rewrote this call |
| _rewrittenMethods |
| .putIfAbsent(originalName, () => new Set<String>()) |
| .add(argumentsSignature); |
| |
| // Now turn any named parameters into positional parameters instead |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return rewrittenNode; |
| } |
| |
| @override |
| TreeNode visitConstructorInvocation(ConstructorInvocation node) { |
| node.transformChildren(this); |
| if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| |
| Expression rewrittenNode; |
| if (node.isConst) { |
| // Sort named arguments by name => it's const so there's no side-effects! |
| // but DO NOT rewrite with let! |
| node.arguments.named.sort((a, b) => a.name.compareTo(b.name)); |
| rewrittenNode = node; |
| } else { |
| rewrittenNode = _rewriteWithLetAndSort(node, node.arguments); |
| } |
| node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| |
| // Now turn named parameters into positional parameters instead |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return rewrittenNode; |
| } |
| |
| @override |
| TreeNode visitSuperInitializer(SuperInitializer node) { |
| // Note that sorting was done in _rewriteConstructorInitializations |
| node.transformChildren(this); |
| if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| |
| node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| |
| // Now turn named parameters into positional parameters instead |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return node; |
| } |
| |
| @override |
| TreeNode visitRedirectingInitializer(RedirectingInitializer node) { |
| // Note that sorting was done in _rewriteConstructorInitializations |
| node.transformChildren(this); |
| if (!_callIsLegal(node.target.function, node.arguments)) return node; |
| |
| node.target = _getNewTargetForConstructor(node.target, node.arguments); |
| |
| // Now turn named parameters into positional parameters instead |
| _turnNamedArgumentsIntoPositional(node.arguments); |
| return node; |
| } |
| |
| /// Gets the new target for an invocation, using cache or creating a new one. |
| /// |
| /// Assumes that any let-rewrite, named argument sorting etc has been done |
| /// already. |
| Procedure _getNewTargetForStaticLikeInvocation(Procedure target, |
| Arguments arguments, Map<Procedure, Map<String, Procedure>> cache) { |
| final createdProcedures = cache.putIfAbsent(target, () => {}); |
| |
| // Rewrite target |
| final argumentsSignature = _createArgumentsSignature(arguments); |
| return createdProcedures[argumentsSignature] ?? |
| _createAndCacheInvocationProcedure( |
| argumentsSignature, |
| arguments.positional.length, |
| arguments.named.map((e) => e.name).toList(), |
| target, |
| _movedBodies[target], |
| createdProcedures, |
| true); |
| } |
| |
| /// Rewrite the [Argument]s turning named arguments into positional arguments. |
| /// |
| /// Note that if the [Argument]s does not take any named parameters this |
| /// method does nothing. |
| void _turnNamedArgumentsIntoPositional(Arguments arguments) { |
| for (final named in arguments.named) { |
| arguments.positional.add(named.value..parent = arguments); |
| } |
| arguments.named.clear(); |
| } |
| |
| /// Gets the new target for an invocation, using cache or creating a new one. |
| /// |
| /// Assumes that any let-rewrite, named argument sorting etc has been done |
| /// already. |
| Constructor _getNewTargetForConstructor( |
| Constructor target, Arguments arguments) { |
| if (!_isNotExternal(target)) return target; |
| if (!_hasAnyOptionalParameters(target.function)) return target; |
| |
| final argumentsSignature = _createArgumentsSignature(arguments); |
| final createdConstructor = _constructorCalls.putIfAbsent(target, () => {}); |
| return createdConstructor[argumentsSignature] ?? |
| _createAndCacheInvocationConstructor( |
| argumentsSignature, |
| arguments.positional.length, |
| arguments.named.map((e) => e.name).toList(), |
| target, |
| _movedConstructors[target], |
| createdConstructor, |
| true); |
| } |
| |
| /// Create a signature for the [Arguments]. |
| /// |
| /// Assumes that any needed sorting etc has already been done. |
| /// |
| /// Looks like x%positionalCount%named --- but it shouldn't matter if always |
| /// using these methods |
| String _createArgumentsSignature(Arguments arguments) { |
| String namedString = arguments.named.map((e) => e.name).join("%"); |
| return "${arguments.positional.length}%$namedString"; |
| } |
| |
| /// Parse the argument signature. |
| /// |
| /// First element will be the string representation of the number of |
| /// positional arguments used. |
| /// The rest will be the named arguments, except that with no named arguments |
| /// there still is a 2nd entry: the empty string... |
| List<String> _parseArgumentsSignature(String argumentsSignature) { |
| return argumentsSignature.split("%"); |
| } |
| |
| /// Rewrites an expression with let, replacing expressions in the [Arguments]. |
| /// |
| /// Sorts the named arguments after rewriting with let. |
| /// |
| /// Note that this method does nothing if there are no named arguments, or the |
| /// named arguments list contain only a single named argument as any sorting |
| /// would have no effect. As such, the let-rewrite will also have no effect. |
| /// In such a case the return value is [original]. |
| Expression _rewriteWithLetAndSort(Expression original, Arguments arguments) { |
| final named = arguments.named; |
| |
| // Only bother if names can be unordered |
| if (named.length < 2) return original; |
| |
| // Rewrite named with let in given order |
| Let let; |
| for (int i = named.length - 1; i >= 0; i--) { |
| VariableDeclaration letDeclaration = |
| new VariableDeclaration.forValue(named[i].value); |
| named[i].value = new VariableGet(letDeclaration)..parent = arguments; |
| let = new Let(letDeclaration, let ?? original); |
| } |
| |
| // Sort named arguments by name |
| named.sort((a, b) => a.name.compareTo(b.name)); |
| |
| // Now also add the given positional arguments into the let |
| final expressions = arguments.positional; |
| for (int i = expressions.length - 1; i >= 0; i--) { |
| VariableDeclaration letDeclaration = |
| new VariableDeclaration.forValue(expressions[i]); |
| expressions[i] = new VariableGet(letDeclaration)..parent = arguments; |
| let = new Let(letDeclaration, let ?? original); |
| } |
| |
| return let; |
| } |
| |
| /// Creates all needed stubs for non static procedures. |
| /// |
| /// More specifically: If calls have been made to a procedure with the same |
| /// name as this procedure with both 1 and 2 arguments, where both of these |
| /// are legal inputs to this procedure, create stubs for both of them, |
| /// each of which calls with whatever default parameter values are defined for |
| /// the non-given arguments. |
| void _createNeededNonStaticStubs(Procedure procedure) { |
| final incomingCalls = _rewrittenMethods[procedure.name]; |
| if (incomingCalls != null && |
| procedure.kind == ProcedureKind.Method && |
| !procedure.isStatic) { |
| final createdOnSuper = _superProcedureCalls[procedure]; |
| final names = |
| procedure.function.namedParameters.map((e) => e.name).toSet(); |
| |
| // A procedure with this name was called on at least one object with |
| // an argument signature like any in [incomingCalls] |
| nextArgumentSignature: |
| for (final argumentsSignature in incomingCalls) { |
| // Skip if it was created in a super call already |
| if (createdOnSuper != null && |
| createdOnSuper.containsKey(argumentsSignature)) { |
| continue; |
| } |
| |
| final elements = _parseArgumentsSignature(argumentsSignature); |
| int positional = int.parse(elements[0]); |
| |
| if (positional < procedure.function.requiredParameterCount || |
| positional > procedure.function.positionalParameters.length) { |
| // We don't take that number of positional parameters! |
| // Call noSuchMethod in case anyone called on object with wrong |
| // parameters, but where superclass does take these parameters. |
| _createNoSuchMethodStub( |
| argumentsSignature, positional, elements.sublist(1), procedure); |
| continue; |
| } |
| |
| if (elements.length > 2 || elements[1] != "") { |
| // Named: Could the call be for this method? |
| for (int i = 1; i < elements.length; i++) { |
| String name = elements[i]; |
| // Using a name that we don't have? |
| if (!names.contains(name)) { |
| // Call noSuchMethod in case anyone called on object with wrong |
| // parameters, but where superclass does take these parameters. |
| _createNoSuchMethodStub(argumentsSignature, positional, |
| elements.sublist(1), procedure); |
| continue nextArgumentSignature; |
| } |
| } |
| } |
| |
| // Potential legal call => make stub |
| // Note the ?? here: E.g. contains on list doesn't take optionals so it |
| // wasn't moved, but calls were rewritten because contains on string |
| // takes either 1 or 2 arguments. |
| final destination = _movedBodies[procedure] ?? procedure; |
| _createAndCacheInvocationProcedure(argumentsSignature, positional, |
| elements.sublist(1), procedure, destination, {}, false); |
| } |
| } |
| } |
| |
| /// Records how this procedure can be called (if it is non-static). |
| /// |
| /// More specifically: Assuming that the procedure given is non-static taking |
| /// a variable number of positional parameters, record all number of arguments |
| /// that is legal, e.g. foo(int a, [int b]) is legal for 1 and 2 parameters. |
| /// If it takes named parameters, remember how many positional there is so |
| /// we also know to rewrite calls without the named arguments. |
| void _recordNonStaticProcedureAndVariableArguments(Procedure procedure) { |
| if (_isMethod(procedure) && |
| !procedure.isStatic && |
| _hasAnyOptionalParameters(procedure.function)) { |
| final name = procedure.name.name; |
| final okCounts = _methodToLegalPositionalArgumentCount.putIfAbsent( |
| name, () => new Set<int>()); |
| for (int i = procedure.function.requiredParameterCount; |
| i <= procedure.function.positionalParameters.length; |
| i++) { |
| okCounts.add(i); |
| } |
| } |
| } |
| |
| /// Move body of procedure to new procedure and call that from this procedure. |
| /// |
| /// More specifically: For all procedures with optional positional parameters, |
| /// or named parameters, create a new procedure without optional positional |
| /// parameters and named parameters and move the body of the original |
| /// procedure into this new procedure. |
| /// Then make the body of the original procedure call the new procedure. |
| /// |
| /// The idea is that all rewrites should call the moved procedure instead, |
| /// bypassing the optional/named arguments entirely. |
| void _moveAndTransformProcedure(Procedure procedure) { |
| if (_isMethod(procedure) && _hasAnyOptionalParameters(procedure.function)) { |
| final function = procedure.function; |
| |
| // Create variable lists |
| final newParameterDeclarations = <VariableDeclaration>[]; |
| final newNamedParameterDeclarations = <VariableDeclaration>[]; |
| final newParameterVariableGets = <Expression>[]; |
| final targetParameters = function.positionalParameters; |
| final targetNamedParameters = function.namedParameters; |
| _moveVariableInitialization( |
| targetParameters, |
| targetNamedParameters, |
| newParameterDeclarations, |
| newNamedParameterDeclarations, |
| newParameterVariableGets, |
| procedure.function); |
| |
| // Create new procedure looking like the old one |
| // (with the old body and parameters) |
| FunctionNode functionNode = _createShallowFunctionCopy(function); |
| final newProcedure = new Procedure( |
| _createOriginalName(procedure), ProcedureKind.Method, functionNode, |
| isAbstract: procedure.isAbstract, |
| isStatic: procedure.isStatic, |
| isConst: procedure.isConst, |
| fileUri: procedure.fileUri); |
| |
| // Add procedure to the code |
| _addMember(procedure, newProcedure); |
| |
| // Map moved body |
| _movedBodies[procedure] = newProcedure; |
| |
| // Transform original procedure |
| if (procedure.isAbstract && procedure.function.body == null) { |
| // do basically nothing then |
| procedure.function.positionalParameters = newParameterDeclarations; |
| procedure.function.namedParameters = newNamedParameterDeclarations; |
| } else if (procedure.isStatic) { |
| final expression = new StaticInvocation( |
| newProcedure, new Arguments(newParameterVariableGets)); |
| final statement = new ReturnStatement(expression) |
| ..parent = procedure.function; |
| procedure.function.body = statement; |
| procedure.function.positionalParameters = newParameterDeclarations; |
| procedure.function.namedParameters = newNamedParameterDeclarations; |
| } else { |
| final expression = new DirectMethodInvocation(new ThisExpression(), |
| newProcedure, new Arguments(newParameterVariableGets)); |
| final statement = new ReturnStatement(expression) |
| ..parent = procedure.function; |
| procedure.function.body = statement; |
| procedure.function.positionalParameters = newParameterDeclarations; |
| procedure.function.namedParameters = newNamedParameterDeclarations; |
| } |
| |
| if (_debug) { |
| // Debug flag set: Print something to the terminal before returning to |
| // easily detect if rewrites are missing. |
| Expression debugPrint = _getPrintExpression( |
| "DEBUG! Procedure shouldn't have been called...", procedure); |
| procedure.function.body = new Block( |
| [new ExpressionStatement(debugPrint), procedure.function.body]) |
| ..parent = procedure.function; |
| } |
| |
| // Mark original procedure as seen (i.e. don't transform it further) |
| _visited.add(procedure); |
| } |
| } |
| |
| /// Rewrite constructor initializers by introducing variables and sorting. |
| /// |
| /// For any* [SuperInitializer] or [RedirectingInitializer], extract the |
| /// parameters, put them into variables, then sorting the named parameters. |
| /// The idea is to sort the named parameters without changing any invocation |
| /// order. |
| /// |
| /// * only with at least 2 named arguments, otherwise sorting would do nothing |
| void _rewriteConstructorInitializations(Constructor constructor) { |
| if (_isNotExternal(constructor)) { |
| // Basically copied from "super_calls.dart" |
| List<Initializer> initializers = constructor.initializers; |
| int foundIndex = -1; |
| Arguments arguments; |
| for (int i = initializers.length - 1; i >= 0; --i) { |
| Initializer initializer = initializers[i]; |
| if (initializer is SuperInitializer) { |
| foundIndex = i; |
| arguments = initializer.arguments; |
| break; |
| } else if (initializer is RedirectingInitializer) { |
| foundIndex = i; |
| arguments = initializer.arguments; |
| break; |
| } |
| } |
| if (foundIndex == -1) return; |
| |
| // Rewrite using variables if using named parameters (so we can sort them) |
| // (note that with 1 named it cannot be unsorted so we don't bother) |
| if (arguments.named.length < 2) return; |
| |
| int argumentCount = arguments.positional.length + arguments.named.length; |
| |
| // Make room for [argumentCount] [LocalInitializer]s before the |
| // super/redirector call. |
| initializers.length += argumentCount; |
| initializers.setRange( |
| foundIndex + argumentCount, // destination start (inclusive) |
| initializers.length, // destination end (exclusive) |
| initializers, // source list |
| foundIndex); // source start index |
| |
| // Fill in the [argumentCount] reserved slots with the evaluation |
| // expressions of the arguments to the super/redirector constructor call |
| int storeIndex = foundIndex; |
| for (int i = 0; i < arguments.positional.length; ++i) { |
| var variable = |
| new VariableDeclaration.forValue(arguments.positional[i]); |
| arguments.positional[i] = new VariableGet(variable)..parent = arguments; |
| initializers[storeIndex++] = new LocalInitializer(variable) |
| ..parent = constructor; |
| } |
| for (int i = 0; i < arguments.named.length; ++i) { |
| NamedExpression argument = arguments.named[i]; |
| var variable = new VariableDeclaration.forValue(argument.value); |
| arguments.named[i].value = new VariableGet(variable)..parent = argument; |
| initializers[storeIndex++] = new LocalInitializer(variable) |
| ..parent = constructor; |
| } |
| |
| // Sort the named arguments |
| arguments.named.sort((a, b) => a.name.compareTo(b.name)); |
| } |
| } |
| |
| /// Move body of constructor to new one and call that from this one. |
| /// |
| /// More specifically: For all constructors with optional positional |
| /// parameters, or named parameters, create a new constructor without optional |
| /// positional parameters and named parameters, and move the body of the |
| /// original constructor into this new constructor. |
| /// Then make the original constructor redirect to the new constructor. |
| /// |
| /// The idea is that all rewrites should call the moved constructor instead, |
| /// bypassing the optional/named arguments entirely. |
| /// |
| /// This method is very similar to _moveAndTransformProcedure |
| void _moveAndTransformConstructor(Constructor constructor) { |
| if (_isNotExternal(constructor) && |
| _hasAnyOptionalParameters(constructor.function)) { |
| final function = constructor.function; |
| |
| // Create variable lists |
| final newParameterDeclarations = <VariableDeclaration>[]; |
| final newNamedParameterDeclarations = <VariableDeclaration>[]; |
| final newParameterVariableGets = <Expression>[]; |
| final targetParameters = function.positionalParameters; |
| final targetNamedParameters = function.namedParameters; |
| _moveVariableInitialization( |
| targetParameters, |
| targetNamedParameters, |
| newParameterDeclarations, |
| newNamedParameterDeclarations, |
| newParameterVariableGets, |
| constructor.function); |
| |
| // Create new constructor looking like the old one |
| // (with the old body, parameters and initializers) |
| FunctionNode functionNode = _createShallowFunctionCopy(function); |
| final newConstructor = new Constructor(functionNode, |
| name: _createOriginalName(constructor), |
| isConst: constructor.isConst, |
| isExternal: constructor.isExternal, |
| initializers: constructor.initializers); |
| |
| // Add constructor to the code |
| _addMember(constructor, newConstructor); |
| |
| // Map moved body |
| _movedConstructors[constructor] = newConstructor; |
| |
| // Transform original constructor |
| constructor.function.body = null; |
| constructor.function.positionalParameters = newParameterDeclarations; |
| constructor.function.namedParameters = newNamedParameterDeclarations; |
| constructor.initializers = [ |
| new RedirectingInitializer( |
| newConstructor, new Arguments(newParameterVariableGets)) |
| ..parent = constructor |
| ]; |
| |
| if (_debug) { |
| // Debug flag set: Print something to the terminal before returning to |
| // easily detect if rewrites are missing. |
| Expression debugPrint = _getPrintExpression( |
| "DEBUG! Constructor shouldn't have been called...", constructor); |
| var variable = new VariableDeclaration.forValue(debugPrint); |
| final debugInitializer = new LocalInitializer(variable) |
| ..parent = constructor; |
| final redirector = constructor.initializers[0]; |
| constructor.initializers = [debugInitializer, redirector]; |
| } |
| |
| // Mark original procedure as seen (i.e. don't transform it further) |
| _visited.add(constructor); |
| } |
| } |
| |
| /// Creates a new [FunctionNode] based on the given one. |
| /// |
| /// Parameters are taken directly (i.e. after returning the parameters will |
| /// have a new parent (the returned value), but still be referenced in the |
| /// original [FunctionNode]. |
| /// The same goes for the body of the function. |
| /// The caller should take steps to remedy this after this call. |
| /// |
| /// The parameters are no longer optional and named parameters have been |
| /// sorted and turned into regular parameters in the returned [FunctionNode]. |
| FunctionNode _createShallowFunctionCopy(FunctionNode function) { |
| final newParameters = |
| new List<VariableDeclaration>.from(function.positionalParameters); |
| final named = new List<VariableDeclaration>.from(function.namedParameters); |
| named.sort((a, b) => a.name.compareTo(b.name)); |
| newParameters.addAll(named); |
| final functionNode = new FunctionNode(function.body, |
| positionalParameters: newParameters, |
| namedParameters: [], |
| requiredParameterCount: newParameters.length, |
| returnType: function.returnType, |
| asyncMarker: function.asyncMarker, |
| dartAsyncMarker: function.dartAsyncMarker); |
| return functionNode; |
| } |
| |
| /// Creates new variables, moving old initializers into them |
| /// |
| /// Specifically: Given lists for output, create new variables based on |
| /// original parameters. Any new variable will receive the original variables |
| /// initializer, and the original variable will have its initializer set to |
| /// null. |
| /// Named parameters have been sorted in [newParameterVariableGets]. |
| void _moveVariableInitialization( |
| List<VariableDeclaration> originalParameters, |
| List<VariableDeclaration> originalNamedParameters, |
| List<VariableDeclaration> newParameterDeclarations, |
| List<VariableDeclaration> newNamedParameterDeclarations, |
| List<Expression> newParameterVariableGets, |
| TreeNode newStuffParent) { |
| for (final orgVar in originalParameters) { |
| final variableDeclaration = new VariableDeclaration(orgVar.name, |
| initializer: orgVar.initializer, |
| type: orgVar.type, |
| isFinal: orgVar.isFinal, |
| isConst: orgVar.isConst) |
| ..parent = newStuffParent; |
| variableDeclaration.initializer?.parent = variableDeclaration; |
| newParameterDeclarations.add(variableDeclaration); |
| orgVar.initializer = null; |
| newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| } |
| |
| // Named expressions in newParameterVariableGets should be sorted |
| final tmp = new List<_Pair<String, Expression>>(); |
| for (final orgVar in originalNamedParameters) { |
| final variableDeclaration = new VariableDeclaration(orgVar.name, |
| initializer: orgVar.initializer, |
| type: orgVar.type, |
| isFinal: orgVar.isFinal, |
| isConst: orgVar.isConst) |
| ..parent = newStuffParent; |
| variableDeclaration.initializer?.parent = variableDeclaration; |
| newNamedParameterDeclarations.add(variableDeclaration); |
| orgVar.initializer = null; |
| tmp.add(new _Pair(orgVar.name, new VariableGet(variableDeclaration))); |
| } |
| tmp.sort((a, b) => a.key.compareTo(b.key)); |
| for (final item in tmp) { |
| newParameterVariableGets.add(item.value); |
| } |
| } |
| |
| /// Creates a stub redirecting to noSuchMethod. |
| /// |
| /// Needed because if B extends A, both have a foo method, but taking |
| /// different optional parameters, a call on an instance of B with parameters |
| /// for A should actually result in a noSuchMethod call, but if only A has |
| /// the rewritten method name, that method will be called... |
| /// TODO: We only have to create these stubs for arguments that a procedures |
| /// super allows, otherwise it will become a noSuchMethod automatically! |
| Procedure _createNoSuchMethodStub( |
| String argumentsSignature, |
| int positionalCount, |
| List<String> givenNamedParameters, |
| Procedure existing) { |
| // Build parameter lists |
| final newParameterDeclarations = <VariableDeclaration>[]; |
| final newParameterVariableGets = <Expression>[]; |
| for (int i = 0; i < positionalCount + givenNamedParameters.length; i++) { |
| final variableDeclaration = new VariableDeclaration("v%$i"); |
| newParameterDeclarations.add(variableDeclaration); |
| newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| } |
| |
| var procedureName = _createName(existing.name, argumentsSignature); |
| |
| // Find noSuchMethod to call |
| Member noSuchMethod = hierarchy.getDispatchTarget( |
| existing.enclosingClass, new Name("noSuchMethod")); |
| Arguments argumentsToNoSuchMethod; |
| |
| if (noSuchMethod.function.positionalParameters.length == 1 && |
| noSuchMethod.function.namedParameters.isEmpty) { |
| // We have a correct noSuchMethod method. |
| ConstructorInvocation invocation = _createInvocation( |
| procedureName.name, new Arguments(newParameterVariableGets)); |
| argumentsToNoSuchMethod = new Arguments([invocation]); |
| } else { |
| // Get noSuchMethod on Object then... |
| noSuchMethod = hierarchy.getDispatchTarget( |
| coreTypes.objectClass, new Name("noSuchMethod")); |
| ConstructorInvocation invocation = _createInvocation( |
| procedureName.name, new Arguments(newParameterVariableGets)); |
| ConstructorInvocation invocationPrime = |
| _createInvocation("noSuchMethod", new Arguments([invocation])); |
| argumentsToNoSuchMethod = new Arguments([invocationPrime]); |
| } |
| |
| // Create return statement to call noSuchMethod |
| ReturnStatement statement; |
| final expression = new DirectMethodInvocation( |
| new ThisExpression(), noSuchMethod, argumentsToNoSuchMethod); |
| statement = new ReturnStatement(expression); |
| |
| // Build procedure |
| final functionNode = new FunctionNode(statement, |
| positionalParameters: newParameterDeclarations, |
| namedParameters: [], |
| requiredParameterCount: newParameterDeclarations.length, |
| returnType: existing.function.returnType, |
| asyncMarker: existing.function.asyncMarker, |
| dartAsyncMarker: existing.function.dartAsyncMarker); |
| final procedure = new Procedure( |
| procedureName, ProcedureKind.Method, functionNode, |
| isStatic: existing.isStatic, fileUri: existing.fileUri); |
| |
| // Add procedure to the code |
| _addMember(existing, procedure); |
| |
| // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| _visited.add(procedure); |
| |
| return procedure; |
| } |
| |
| /// Creates an "new _InvocationMirror(...)" invocation. |
| ConstructorInvocation _createInvocation( |
| String methodName, Arguments callArguments) { |
| if (_invocationMirrorConstructor == null) { |
| Class clazz = coreTypes.invocationMirrorClass; |
| _invocationMirrorConstructor = clazz.constructors[0]; |
| } |
| |
| // The _InvocationMirror constructor takes the following arguments: |
| // * Method name (a string). |
| // * An arguments descriptor - a list consisting of: |
| // - number of arguments (including receiver). |
| // - number of positional arguments (including receiver). |
| // - pairs (2 entries in the list) of |
| // * named arguments name. |
| // * index of named argument in arguments list. |
| // * A list of arguments, where the first ones are the positional arguments. |
| // * Whether it's a super invocation or not. |
| |
| int numPositionalArguments = callArguments.positional.length + 1; |
| int numArguments = numPositionalArguments + callArguments.named.length; |
| List<Expression> argumentsDescriptor = [ |
| new IntLiteral(numArguments), |
| new IntLiteral(numPositionalArguments) |
| ]; |
| List<Expression> arguments = []; |
| arguments.add(new ThisExpression()); |
| for (Expression pos in callArguments.positional) { |
| arguments.add(pos); |
| } |
| for (NamedExpression named in callArguments.named) { |
| argumentsDescriptor.add(new StringLiteral(named.name)); |
| argumentsDescriptor.add(new IntLiteral(arguments.length)); |
| arguments.add(named.value); |
| } |
| |
| return new ConstructorInvocation( |
| _invocationMirrorConstructor, |
| new Arguments([ |
| new StringLiteral(methodName), |
| _fixedLengthList(argumentsDescriptor), |
| _fixedLengthList(arguments), |
| new BoolLiteral(false) |
| ])); |
| } |
| |
| /// Create a fixed length list containing given expressions. |
| Expression _fixedLengthList(List<Expression> list) { |
| _listFrom ??= coreTypes.listFromConstructor; |
| return new StaticInvocation( |
| _listFrom, |
| new Arguments([new ListLiteral(list)], |
| named: [new NamedExpression("growable", new BoolLiteral(false))], |
| types: [const DynamicType()])); |
| } |
| |
| /// Creates a new procedure taking given arguments, caching it. |
| /// |
| /// Copies any non-given default values for parameters into the new procedure |
| /// to be able to call the [realTarget] without using optionals and named |
| /// parameters. |
| Procedure _createAndCacheInvocationProcedure( |
| String argumentsSignature, |
| int positionalCount, |
| List<String> givenNamedParameters, |
| Procedure target, |
| Procedure realTarget, |
| Map<String, Procedure> createdProcedures, |
| bool doSpecialCaseForAllParameters) { |
| // Special case: Calling with all parameters |
| if (doSpecialCaseForAllParameters && |
| positionalCount == target.function.positionalParameters.length && |
| givenNamedParameters.length == target.function.namedParameters.length) { |
| // We don't cache this procedure as this could make it look like |
| // something with name argumentsSignature actually exists |
| // while it doesn't (which is bad as we could then decide that we don't |
| // need to create a stub even though we do!) |
| return realTarget; |
| } |
| |
| // Create and cache (save) constructor |
| |
| // Build parameter lists |
| final newParameterDeclarations = <VariableDeclaration>[]; |
| final newParameterVariableGets = <Expression>[]; |
| _extractAndCreateParameters(positionalCount, newParameterDeclarations, |
| newParameterVariableGets, target, givenNamedParameters); |
| |
| // Create return statement to call real target |
| ReturnStatement statement; |
| if (target.isAbstract && target.function?.body == null) { |
| // statement should just be null then |
| } else if (target.isStatic) { |
| final expression = new StaticInvocation( |
| realTarget, new Arguments(newParameterVariableGets)); |
| statement = new ReturnStatement(expression); |
| } else { |
| final expression = new DirectMethodInvocation(new ThisExpression(), |
| realTarget, new Arguments(newParameterVariableGets)); |
| statement = new ReturnStatement(expression); |
| } |
| |
| // Build procedure |
| final functionNode = new FunctionNode(statement, |
| positionalParameters: newParameterDeclarations, |
| namedParameters: [], |
| requiredParameterCount: newParameterDeclarations.length, |
| returnType: target.function.returnType, |
| asyncMarker: target.function.asyncMarker, |
| dartAsyncMarker: target.function.dartAsyncMarker); |
| final procedure = new Procedure( |
| _createName(target.name, argumentsSignature), |
| ProcedureKind.Method, |
| functionNode, |
| isAbstract: target.isAbstract, |
| isStatic: target.isStatic, |
| isConst: target.isConst, |
| fileUri: target.fileUri); |
| |
| // Add procedure to the code |
| _addMember(target, procedure); |
| |
| // Cache it for future reference |
| createdProcedures[argumentsSignature] = procedure; |
| |
| // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| _visited.add(procedure); |
| |
| return procedure; |
| } |
| |
| /// Creates a new constructor taking given arguments, caching it. |
| /// |
| /// Copies any non-given default values for parameters into the new |
| /// constructor to be able to call the [realTarget] without using optionals |
| /// and named parameters. |
| Constructor _createAndCacheInvocationConstructor( |
| String argumentsSignature, |
| int positionalCount, |
| List<String> givenNamedParameters, |
| Constructor target, |
| Constructor realTarget, |
| Map<String, Constructor> createdConstructor, |
| bool doSpecialCaseForAllParameters) { |
| // Special case: Calling with all parameters |
| if (doSpecialCaseForAllParameters && |
| positionalCount == target.function.positionalParameters.length && |
| givenNamedParameters.length == target.function.namedParameters.length) { |
| createdConstructor[argumentsSignature] = realTarget; |
| return realTarget; |
| } |
| |
| // Create and cache (save) constructor |
| |
| // Build parameter lists |
| final newParameterDeclarations = <VariableDeclaration>[]; |
| final newParameterVariableGets = <Expression>[]; |
| _extractAndCreateParameters(positionalCount, newParameterDeclarations, |
| newParameterVariableGets, target, givenNamedParameters); |
| |
| // Build constructor |
| final functionNode = new FunctionNode(null, |
| positionalParameters: newParameterDeclarations, |
| namedParameters: [], |
| requiredParameterCount: newParameterDeclarations.length, |
| returnType: target.function.returnType, |
| asyncMarker: target.function.asyncMarker, |
| dartAsyncMarker: target.function.dartAsyncMarker); |
| final constructor = new Constructor(functionNode, |
| name: _createName(target.name, argumentsSignature), |
| isConst: target.isConst, |
| isExternal: target.isExternal, |
| initializers: [ |
| new RedirectingInitializer( |
| realTarget, new Arguments(newParameterVariableGets)) |
| ]); |
| |
| // Add procedure to the code |
| _addMember(target, constructor); |
| |
| // Cache it for future reference |
| createdConstructor[argumentsSignature] = constructor; |
| |
| // Mark the new procedure as visited already (i.e. don't rewrite it again!) |
| _visited.add(constructor); |
| |
| return constructor; |
| } |
| |
| /// Extracts and creates parameters into the first two given lists. |
| /// |
| /// What is done: |
| /// Step 1: Re-create the parameters given (i.e. the non-optional positional |
| /// ones) - i.e. create a new variable with the same name etc, put it in |
| /// [newParameterDeclarations]; create VariableGet for that and put it in |
| /// [newParameterVariableGets] |
| /// Step 2: Re-create the positional parameters NOT given, i.e. insert |
| /// defaults and add to [newParameterVariableGets] only. |
| /// Step 3: Re-create the named arguments (in sorted order). For actually |
| /// given named parameters, do as in step 1, for not-given named parameters |
| /// do as in step 2. |
| /// |
| /// NOTE: [newParameterDeclarations] and [newParameterVariableGets] are OUTPUT |
| /// lists. |
| void _extractAndCreateParameters( |
| int positionalCount, |
| List<VariableDeclaration> newParameterDeclarations, |
| List<Expression> newParameterVariableGets, |
| Member target, |
| List<String> givenNamedParameters) { |
| // First re-create the parameters given (i.e. the non-optional positional ones) |
| final targetParameters = target.function.positionalParameters; |
| positionalCount = math.min(positionalCount, targetParameters.length); |
| for (int i = 0; i < positionalCount; i++) { |
| final orgVar = targetParameters[i]; |
| final variableDeclaration = new VariableDeclaration(orgVar.name, |
| type: orgVar.type, isFinal: orgVar.isFinal, isConst: orgVar.isConst); |
| newParameterDeclarations.add(variableDeclaration); |
| newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| } |
| |
| // Default parameters for the rest of them |
| _fillInPositionalParameters( |
| positionalCount, target, newParameterVariableGets); |
| |
| // Then all named parameters (given here or not) |
| final orgNamed = |
| new List<VariableDeclaration>.from(target.function.namedParameters); |
| orgNamed.sort((a, b) => a.name.compareTo(b.name)); |
| final givenArgumentsIterator = givenNamedParameters.iterator; |
| givenArgumentsIterator.moveNext(); |
| for (VariableDeclaration named in orgNamed) { |
| if (givenArgumentsIterator.current == named.name) { |
| // We have that one: Use it and move the iterator |
| final variableDeclaration = new VariableDeclaration(named.name); |
| newParameterDeclarations.add(variableDeclaration); |
| newParameterVariableGets.add(new VariableGet(variableDeclaration)); |
| givenArgumentsIterator.moveNext(); |
| } else { |
| // We don't have that one: Fill it in |
| _fillInSingleParameter(named, newParameterVariableGets, target); |
| } |
| } |
| } |
| |
| /// Adds the new member the same place as the existing member |
| void _addMember(Member existingMember, Member newMember) { |
| if (existingMember.enclosingClass != null) { |
| existingMember.enclosingClass.addMember(newMember); |
| } else { |
| existingMember.enclosingLibrary.addMember(newMember); |
| } |
| } |
| |
| /// Create expressions based on the default values from the given [Member]. |
| /// |
| /// More specifically: static gets and nulls will be "copied" whereas other |
| /// things (e.g. literals or things like "a+b") will be moved from the |
| /// original member as argument initializers to const fields and both the |
| /// original member and the expression-copy will use static gets to these. |
| void _fillInPositionalParameters( |
| int startFrom, Member copyFrom, List<Expression> fillInto) { |
| final targetParameters = copyFrom.function.positionalParameters; |
| for (int i = startFrom; i < targetParameters.length; i++) { |
| final parameter = targetParameters[i]; |
| _fillInSingleParameter(parameter, fillInto, copyFrom); |
| } |
| } |
| |
| /// Create expression based on the default values from the given variable. |
| /// |
| /// More specifically: a static get or null will be "copied" whereas other |
| /// things (e.g. literals or things like "a+b") will be moved from the |
| /// original member as an argument initializer to a const field and both the |
| /// original member and the expression-copy will use a static get to it. |
| void _fillInSingleParameter(VariableDeclaration parameter, |
| List<Expression> fillInto, Member copyFrom) { |
| if (parameter.initializer is StaticGet) { |
| // Reference to const => recreate it |
| StaticGet staticGet = parameter.initializer; |
| fillInto.add(new StaticGet(staticGet.target)); |
| } else if (parameter.initializer == null) { |
| // No default given => output null |
| fillInto.add(new NullLiteral()); |
| } else if (parameter.initializer is IntLiteral) { |
| // Int literal => recreate (or else class ByteBuffer in typed_data will |
| // get 2 fields and the C++ code will complain!) |
| IntLiteral value = parameter.initializer; |
| fillInto.add(new IntLiteral(value.value)); |
| } else { |
| // Advanced stuff => move to static const field and reference that |
| final initializer = parameter.initializer; |
| final f = new Field( |
| new Name('${copyFrom.name.name}%_${parameter.name}', |
| copyFrom.enclosingLibrary), |
| type: parameter.type, |
| initializer: initializer, |
| isFinal: false, |
| isConst: true, |
| isStatic: true, |
| fileUri: copyFrom.enclosingClass?.fileUri ?? |
| copyFrom.enclosingLibrary.fileUri); |
| initializer.parent = f; |
| |
| // Add field to the code |
| if (copyFrom.enclosingClass != null) { |
| copyFrom.enclosingClass.addMember(f); |
| } else { |
| copyFrom.enclosingLibrary.addMember(f); |
| } |
| |
| // Use it at the call site |
| fillInto.add(new StaticGet(f)); |
| |
| // Now replace the initializer in the method to a StaticGet |
| parameter.initializer = new StaticGet(f)..parent = parameter; |
| } |
| } |
| |
| /// Create an "original name" for a member. |
| /// |
| /// Specifically, for a member "x" just returns "x%original"; |
| Name _createOriginalName(Member member) { |
| return new Name("${member.name.name}%original", member.enclosingLibrary); |
| } |
| |
| /// Create a [Name] based on current name and argument signature. |
| Name _createName(Name name, String argumentsSignature) { |
| String nameString = '${name.name}%$argumentsSignature'; |
| return new Name(nameString, name.library); |
| } |
| |
| /// Is the procedure a method? |
| bool _isMethod(Procedure procedure) => procedure.kind == ProcedureKind.Method; |
| |
| /// Is the procedure NOT marked as external? |
| bool _isNotExternal(Constructor constructor) => !constructor.isExternal; |
| |
| /// Does the target function have any optional arguments? (positional/named) |
| bool _hasAnyOptionalParameters(FunctionNode targetFunction) => |
| _hasOptionalParameters(targetFunction) || |
| _hasNamedParameters(targetFunction); |
| |
| /// Does the target function have optional positional arguments? |
| bool _hasOptionalParameters(FunctionNode targetFunction) => |
| targetFunction.positionalParameters.length > |
| targetFunction.requiredParameterCount; |
| |
| /// Does the target function have named parameters? |
| bool _hasNamedParameters(FunctionNode targetFunction) => |
| targetFunction.namedParameters.isNotEmpty; |
| |
| bool _callIsLegal(FunctionNode targetFunction, Arguments arguments) { |
| if ((targetFunction.requiredParameterCount > arguments.positional.length) || |
| (targetFunction.positionalParameters.length < |
| arguments.positional.length)) { |
| // Given too few or too many positional arguments |
| return false; |
| } |
| |
| // Do we give named that we don't take? |
| Set<String> givenNamed = arguments.named.map((v) => v.name).toSet(); |
| Set<String> takenNamed = |
| targetFunction.namedParameters.map((v) => v.name).toSet(); |
| givenNamed.removeAll(takenNamed); |
| return givenNamed.isEmpty; |
| } |
| |
| // Below methods used to add debug prints etc |
| |
| Library _getDartCoreLibrary(Program program) { |
| if (program == null) return null; |
| return program.libraries.firstWhere((lib) => |
| lib.importUri.scheme == 'dart' && lib.importUri.path == 'core'); |
| } |
| |
| Procedure _getProcedureInLib(Library lib, String name) { |
| if (lib == null) return null; |
| return lib.procedures |
| .firstWhere((procedure) => procedure.name.name == name); |
| } |
| |
| Procedure _getProcedureInClassInLib( |
| Library lib, String className, String procedureName) { |
| if (lib == null) return null; |
| Class clazz = lib.classes.firstWhere((clazz) => clazz.name == className); |
| return clazz.procedures |
| .firstWhere((procedure) => procedure.name.name == procedureName); |
| } |
| |
| Expression _getPrintExpression(String msg, TreeNode treeNode) { |
| TreeNode program = treeNode; |
| while (program is! Program) program = program.parent; |
| var finalMsg = msg; |
| if (treeNode is Member) { |
| finalMsg += " [ ${treeNode.name.name} ]"; |
| if (treeNode.enclosingClass != null) { |
| finalMsg += " [ class ${treeNode.enclosingClass.name} ]"; |
| } |
| if (treeNode.enclosingLibrary != null) { |
| finalMsg += " [ lib ${treeNode.enclosingLibrary.name} ]"; |
| } |
| } |
| |
| var stacktrace = new StaticGet(_getProcedureInClassInLib( |
| _getDartCoreLibrary(program), 'StackTrace', 'current')); |
| var printStackTrace = new StaticInvocation( |
| _getProcedureInLib(_getDartCoreLibrary(program), 'print'), |
| new Arguments([ |
| new StringConcatenation([ |
| new StringLiteral(finalMsg), |
| new StringLiteral("\n"), |
| stacktrace, |
| new StringLiteral("\n") |
| ]) |
| ])); |
| |
| return printStackTrace; |
| } |
| } |
| |
| class _Pair<K, V> { |
| final K key; |
| final V value; |
| |
| _Pair(this.key, this.value); |
| } |