00f97489cf
According to the language specification the root class is always equivalent to the Object class. R=ahe@google.com, kmillikin@google.com, paulberry@google.com, sigmund@google.com BUG= Review-Url: https://codereview.chromium.org/2919833002 .
1316 lines
52 KiB
Dart
1316 lines
52 KiB
Dart
// 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);
|
|
}
|