blob: b60d435a9231a4166625831c8e6be8fc5bc0d6dd [file] [log] [blame]
// Copyright (c) 2015, 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 dart2js.call_structure;
import '../common.dart';
import '../common/names.dart' show Names;
import '../elements/elements.dart';
import '../tree/tree.dart';
import '../util/util.dart';
import 'selector.dart' show Selector;
/// The structure of the arguments at a call-site.
// TODO(johnniwinther): Should these be cached?
// TODO(johnniwinther): Should isGetter/isSetter be part of the call structure
// instead of the selector?
class CallStructure {
static const CallStructure NO_ARGS = const CallStructure.unnamed(0);
static const CallStructure ONE_ARG = const CallStructure.unnamed(1);
static const CallStructure TWO_ARGS = const CallStructure.unnamed(2);
static const CallStructure THREE_ARGS = const CallStructure.unnamed(3);
/// The numbers of arguments of the call. Includes named arguments.
final int argumentCount;
/// The number of named arguments of the call.
int get namedArgumentCount => 0;
/// The number of positional argument of the call.
int get positionalArgumentCount => argumentCount;
const CallStructure.unnamed(this.argumentCount);
factory CallStructure(int argumentCount, [List<String> namedArguments]) {
if (namedArguments == null || namedArguments.isEmpty) {
return new CallStructure.unnamed(argumentCount);
}
return new NamedCallStructure(argumentCount, namedArguments);
}
/// Creates the [CallStructure] corresponding to calling [signature] as
/// declared, that is, all named arguments are in the order of declaration.
factory CallStructure.fromSignature(FunctionSignature signature) {
List<String> namedParameters;
if (signature.optionalParametersAreNamed) {
namedParameters =
signature.optionalParameters.map((e) => e.name).toList();
}
return new CallStructure(signature.parameterCount, namedParameters);
}
/// `true` if this call has named arguments.
bool get isNamed => false;
/// `true` if this call has no named arguments.
bool get isUnnamed => true;
/// The names of the named arguments in call-site order.
List<String> get namedArguments => const <String>[];
/// The names of the named arguments in canonicalized order.
List<String> getOrderedNamedArguments() => const <String>[];
/// A description of the argument structure.
String structureToString() => 'arity=$argumentCount';
String toString() => 'CallStructure(${structureToString()})';
Selector get callSelector => new Selector.call(Names.call, this);
bool match(CallStructure other) {
if (identical(this, other)) return true;
return this.argumentCount == other.argumentCount &&
this.namedArgumentCount == other.namedArgumentCount &&
sameNames(this.namedArguments, other.namedArguments);
}
// TODO(johnniwinther): Cache hash code?
int get hashCode {
return Hashing.listHash(namedArguments,
Hashing.objectHash(argumentCount, namedArguments.length));
}
bool operator ==(other) {
if (other is! CallStructure) return false;
return match(other);
}
bool signatureApplies(FunctionSignature parameters) {
if (argumentCount > parameters.parameterCount) return false;
int requiredParameterCount = parameters.requiredParameterCount;
int optionalParameterCount = parameters.optionalParameterCount;
if (positionalArgumentCount < requiredParameterCount) return false;
if (!parameters.optionalParametersAreNamed) {
// We have already checked that the number of arguments are
// not greater than the number of parameters. Therefore the
// number of positional arguments are not greater than the
// number of parameters.
assert(positionalArgumentCount <= parameters.parameterCount);
return namedArguments.isEmpty;
} else {
if (positionalArgumentCount > requiredParameterCount) return false;
assert(positionalArgumentCount == requiredParameterCount);
if (namedArgumentCount > optionalParameterCount) return false;
Set<String> nameSet = new Set<String>();
parameters.optionalParameters.forEach((Element element) {
nameSet.add(element.name);
});
for (String name in namedArguments) {
if (!nameSet.contains(name)) return false;
// TODO(5213): By removing from the set we are checking
// that we are not passing the name twice. We should have this
// check in the resolver also.
nameSet.remove(name);
}
return true;
}
}
/**
* Returns a `List` with the evaluated arguments in the normalized order.
*
* [compileDefaultValue] is a function that returns a compiled constant
* of an optional argument that is not in [compiledArguments].
*
* Precondition: `this.applies(element, world)`.
*
* Invariant: [element] must be the implementation element.
*/
/*<T>*/ List/*<T>*/ makeArgumentsList(
Link<Node> arguments,
FunctionElement element,
/*T*/ compileArgument(Node argument),
/*T*/ compileDefaultValue(ParameterElement element)) {
assert(invariant(element, element.isImplementation));
List/*<T>*/ result = new List();
FunctionSignature parameters = element.functionSignature;
parameters.forEachRequiredParameter((ParameterElement element) {
result.add(compileArgument(arguments.head));
arguments = arguments.tail;
});
if (!parameters.optionalParametersAreNamed) {
parameters.forEachOptionalParameter((ParameterElement element) {
if (!arguments.isEmpty) {
result.add(compileArgument(arguments.head));
arguments = arguments.tail;
} else {
result.add(compileDefaultValue(element));
}
});
} else {
// Visit named arguments and add them into a temporary list.
List compiledNamedArguments = [];
for (; !arguments.isEmpty; arguments = arguments.tail) {
NamedArgument namedArgument = arguments.head;
compiledNamedArguments.add(compileArgument(namedArgument.expression));
}
// Iterate over the optional parameters of the signature, and try to
// find them in [compiledNamedArguments]. If found, we use the
// value in the temporary list, otherwise the default value.
parameters.orderedOptionalParameters.forEach((ParameterElement element) {
int foundIndex = namedArguments.indexOf(element.name);
if (foundIndex != -1) {
result.add(compiledNamedArguments[foundIndex]);
} else {
result.add(compileDefaultValue(element));
}
});
}
return result;
}
/**
* Fills [list] with the arguments in the order expected by
* [callee], and where [caller] is a synthesized element
*
* [compileArgument] is a function that returns a compiled version
* of a parameter of [callee].
*
* [compileConstant] is a function that returns a compiled constant
* of an optional argument that is not in the parameters of [callee].
*
* Returns [:true:] if the signature of the [caller] matches the
* signature of the [callee], [:false:] otherwise.
*/
static/*<T>*/ bool addForwardingElementArgumentsToList(
ConstructorElement caller,
List/*<T>*/ list,
ConstructorElement callee,
/*T*/ compileArgument(ParameterElement element),
/*T*/ compileConstant(ParameterElement element)) {
assert(invariant(caller, !callee.isMalformed,
message: "Cannot compute arguments to malformed constructor: "
"$caller calling $callee."));
FunctionSignature signature = caller.functionSignature;
Map<Node, ParameterElement> mapping = <Node, ParameterElement>{};
// TODO(ngeoffray): This is a hack that fakes up AST nodes, so
// that we can call [addArgumentsToList].
Link<Node> computeCallNodesFromParameters() {
LinkBuilder<Node> builder = new LinkBuilder<Node>();
signature.forEachRequiredParameter((ParameterElement element) {
Node node = element.node;
mapping[node] = element;
builder.addLast(node);
});
if (signature.optionalParametersAreNamed) {
signature.forEachOptionalParameter((ParameterElement element) {
mapping[element.initializer] = element;
builder.addLast(new NamedArgument(null, null, element.initializer));
});
} else {
signature.forEachOptionalParameter((ParameterElement element) {
Node node = element.node;
mapping[node] = element;
builder.addLast(node);
});
}
return builder.toLink();
}
/*T*/ internalCompileArgument(Node node) {
return compileArgument(mapping[node]);
}
Link<Node> nodes = computeCallNodesFromParameters();
// Synthesize a structure for the call.
// TODO(ngeoffray): Should the resolver do it instead?
CallStructure callStructure = new CallStructure.fromSignature(signature);
if (!callStructure.signatureApplies(signature)) {
return false;
}
list.addAll(callStructure.makeArgumentsList(
nodes, callee, internalCompileArgument, compileConstant));
return true;
}
static bool sameNames(List<String> first, List<String> second) {
for (int i = 0; i < first.length; i++) {
if (first[i] != second[i]) return false;
}
return true;
}
}
///
class NamedCallStructure extends CallStructure {
final List<String> namedArguments;
final List<String> _orderedNamedArguments = <String>[];
NamedCallStructure(int argumentCount, this.namedArguments)
: super.unnamed(argumentCount) {
assert(namedArguments.isNotEmpty);
}
@override
bool get isNamed => true;
@override
bool get isUnnamed => false;
@override
int get namedArgumentCount => namedArguments.length;
@override
int get positionalArgumentCount => argumentCount - namedArgumentCount;
@override
List<String> getOrderedNamedArguments() {
if (!_orderedNamedArguments.isEmpty) return _orderedNamedArguments;
_orderedNamedArguments.addAll(namedArguments);
_orderedNamedArguments.sort((String first, String second) {
return first.compareTo(second);
});
return _orderedNamedArguments;
}
@override
String structureToString() {
return 'arity=$argumentCount, named=[${namedArguments.join(', ')}]';
}
}