Chromium Code Reviews| Index: pkg/kernel/lib/transformations/method_call.dart |
| diff --git a/pkg/kernel/lib/transformations/method_call.dart b/pkg/kernel/lib/transformations/method_call.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d3b61e4285398469b5d1fb6f82a2407f02c4f805 |
| --- /dev/null |
| +++ b/pkg/kernel/lib/transformations/method_call.dart |
| @@ -0,0 +1,1311 @@ |
| +// 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 '../ast.dart'; |
| +import '../kernel.dart'; |
| +import '../visitor.dart'; |
| +import '../class_hierarchy.dart'; |
| +import '../core_types.dart'; |
| +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.
|
| + |
| +/// 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(Program program, [debug = false]) { |
| + new MethodCallTransformer(debug).visitProgram(program); |
| + return program; |
| +} |
| + |
| +class MethodCallTransformer extends Transformer { |
| + /// 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> 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.
|
| + |
| + /// 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; |
| + CoreTypes coreTypes; |
| + Constructor _invocationMirrorConstructor; // cached |
| + Procedure _listFrom; // cached |
| + |
| + MethodCallTransformer(this._debug); |
| + |
| + @override |
| + TreeNode visitProgram(Program node) { |
| + hierarchy = new ClassHierarchy(node); |
| + coreTypes = new CoreTypes(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) { |
| + stopNames.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 (stopNames.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); |
| + 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( |
| + hierarchy.rootClass, 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); |
| + 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.getCoreClass('dart:core', '_InvocationMirror'); |
| + _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) { |
| + if (_listFrom == null) { |
| + Class clazz = coreTypes.getCoreClass('dart:core', 'List'); |
| + _listFrom = clazz.procedures.firstWhere((c) => c.name.name == "from"); |
| + } |
| + 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); |
| + 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); |
| + 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> { |
| + K key; |
| + V value; |
|
kustermann
2017/02/14 15:21:23
final
jensj
2017/02/15 11:46:21
Done.
|
| + |
| + _Pair(this.key, this.value); |
| +} |