blob: b269aff3173deb141706f3b7f4994460d99dbb9c [file] [log] [blame]
// 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);
/// }
Component transformComponent(
CoreTypes coreTypes, ClassHierarchy hierarchy, Component component,
[debug = false]) {
new MethodCallTransformer(coreTypes, hierarchy, debug)
.visitComponent(component);
return component;
}
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 visitComponent(Component 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(Component component) {
if (component == null) return null;
return component.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 component = treeNode;
while (component is! Component) component = component.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(component), 'StackTrace', 'current'));
var printStackTrace = new StaticInvocation(
_getProcedureInLib(_getDartCoreLibrary(component), '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);
}