Files
sdk/pkg/kernel/lib/transformations/method_call.dart
T
Konstantin Shcheglov 77d4eb609b Use LibraryIndex in CoreTypes instead of extending it.
A smaller step this time - change only clients, but keep implementation
the same for now.

Not all patched SDKs have all classes and procedures, so I have to make
some them lazy. All of them could be done for consistency, but this
might slightly affect performance for often used dart:core classes.

Also it's a bit of mess now.
Maybe group these classes into logical structures, coreTypes.core.list,
coreTypes.async.future, coreTypes.async.futureMicrotask, etc. Something
like it was before this change, but fixed types and names instead of
more flexible (but in general slower) strings.

R=ahe@google.com, kmillikin@google.com, paulberry@google.com, sigmund@google.com
BUG=

Review-Url: https://codereview.chromium.org/2890933002 .
2017-05-23 09:30:37 -07:00

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(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> 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;
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) {
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(
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,
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);
}