blob: 448ad06dc1cf4f69bf247d5dc3ee9485bd406c10 [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/names.dart' show Names;
import '../elements/entities.dart' show ParameterStructure;
import '../serialization/serialization_interfaces.dart';
import '../util/util.dart';
import 'selector.dart' show Selector;
/// The structure of the arguments at a call-site.
///
/// A call-site passes some number of arguments: some positional arguments
/// followed by some named arguments. There may also be type arguments.
///
/// A CallStructure is unmodifiable.
// TODO(johnniwinther): Should isGetter/isSetter be part of the call structure
// instead of the selector?
class CallStructure {
/// Tag used for identifying serialized [CallStructure] objects in a debugging
/// data stream.
static const String tag = 'call-structure';
static const CallStructure NO_ARGS = CallStructure._(0);
static const CallStructure ONE_ARG = CallStructure._(1);
static const CallStructure TWO_ARGS = CallStructure._(2);
static const List<List<CallStructure>> _common = [
[NO_ARGS, CallStructure._(0, 1), CallStructure._(0, 2)],
[ONE_ARG, CallStructure._(1, 1), CallStructure._(1, 2)],
[TWO_ARGS, CallStructure._(2, 1), CallStructure._(2, 2)],
[CallStructure._(3), CallStructure._(3, 1), CallStructure._(3, 2)],
[CallStructure._(4), CallStructure._(4, 1), CallStructure._(4, 2)],
[CallStructure._(5), CallStructure._(5, 1), CallStructure._(5, 2)],
[CallStructure._(6)],
[CallStructure._(7)],
[CallStructure._(8)],
[CallStructure._(9)],
[CallStructure._(10)],
];
/// The number of type arguments of the call.
final int typeArgumentCount;
/// 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._(this.argumentCount, [this.typeArgumentCount = 0]);
factory CallStructure.unnamed(int argumentCount,
[int typeArgumentCount = 0]) {
// This simple canonicalization of common call structures greatly reduces
// the number of allocations of CallStructure objects.
if (argumentCount < _common.length) {
final row = _common[argumentCount];
if (typeArgumentCount < row.length) {
final result = row[typeArgumentCount];
assert(result.argumentCount == argumentCount &&
result.typeArgumentCount == typeArgumentCount);
return result;
}
}
return CallStructure._(argumentCount, typeArgumentCount);
}
factory CallStructure(int argumentCount,
[List<String>? namedArguments, int typeArgumentCount = 0]) {
if (namedArguments == null || namedArguments.isEmpty) {
return CallStructure.unnamed(argumentCount, typeArgumentCount);
}
return _NamedCallStructure(
argumentCount, namedArguments, typeArgumentCount, null);
}
/// Deserializes a [CallStructure] object from [source].
factory CallStructure.readFromDataSource(DataSourceReader source) {
source.begin(tag);
int argumentCount = source.readInt();
List<String> namedArguments = source.readStrings()!;
int typeArgumentCount = source.readInt();
source.end(tag);
return CallStructure(argumentCount, namedArguments, typeArgumentCount);
}
/// Serializes this [CallStructure] to [sink].
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeInt(argumentCount);
sink.writeStrings(namedArguments);
sink.writeInt(typeArgumentCount);
sink.end(tag);
}
/// Returns `true` if this call structure is normalized, that is, its named
/// arguments are sorted.
bool get isNormalized => true;
/// Returns the normalized version of this call structure.
///
/// A [CallStructure] is normalized if its named arguments are sorted.
CallStructure toNormalized() => this;
CallStructure withTypeArgumentCount(int typeArgumentCount) =>
CallStructure(argumentCount, namedArguments, typeArgumentCount);
/// `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 [];
/// The names of the named arguments in canonicalized order.
List<String> getOrderedNamedArguments() => const [];
CallStructure get nonGeneric => typeArgumentCount == 0
? this
: CallStructure(argumentCount, namedArguments);
/// Short textual representation use for testing.
String get shortText {
StringBuffer sb = StringBuffer();
sb.write('(');
sb.write(positionalArgumentCount);
if (namedArgumentCount > 0) {
sb.write(',');
sb.write(getOrderedNamedArguments().join(','));
}
sb.write(')');
return sb.toString();
}
/// A description of the argument structure.
String structureToString() {
StringBuffer sb = StringBuffer();
sb.write('arity=$argumentCount');
if (typeArgumentCount != 0) {
sb.write(', types=$typeArgumentCount');
}
return sb.toString();
}
@override
String toString() => 'CallStructure(${structureToString()})';
Selector get callSelector => Selector.call(Names.call, this);
bool match(CallStructure other) {
if (identical(this, other)) return true;
return this.argumentCount == other.argumentCount &&
this.namedArgumentCount == other.namedArgumentCount &&
this.typeArgumentCount == other.typeArgumentCount &&
_sameNames(this.namedArguments, other.namedArguments);
}
// TODO(johnniwinther): Cache hash code?
@override
int get hashCode {
return Hashing.listHash(
namedArguments,
Hashing.objectHash(argumentCount,
Hashing.objectHash(typeArgumentCount, namedArguments.length)));
}
@override
bool operator ==(other) {
if (other is! CallStructure) return false;
return match(other);
}
bool signatureApplies(ParameterStructure parameters) {
int requiredParameterCount = parameters.requiredPositionalParameters;
int optionalParameterCount = parameters.optionalParameters;
int parameterCount = parameters.totalParameters;
if (argumentCount > parameterCount) return false;
if (positionalArgumentCount < requiredParameterCount) return false;
if (typeArgumentCount != 0) {
if (typeArgumentCount != parameters.typeParameters) return false;
}
if (parameters.namedParameters.isEmpty) {
// 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 <= parameterCount);
return namedArguments.isEmpty;
} else {
if (positionalArgumentCount > requiredParameterCount) return false;
assert(positionalArgumentCount == requiredParameterCount);
if (namedArgumentCount >
optionalParameterCount + parameters.requiredNamedParameters.length)
return false;
int nameIndex = 0;
List<String> namedParameters = parameters.namedParameters;
int seenRequiredNamedParameters = 0;
for (String name in getOrderedNamedArguments()) {
bool found = false;
// Note: we start at the existing index because arguments are sorted.
while (nameIndex < namedParameters.length) {
String parameterName = namedParameters[nameIndex];
if (name == parameterName) {
if (parameters.requiredNamedParameters.contains(name))
seenRequiredNamedParameters++;
found = true;
break;
}
nameIndex++;
}
if (!found) return false;
}
return seenRequiredNamedParameters ==
parameters.requiredNamedParameters.length;
}
}
static bool _sameNames(List<String> first, List<String> second) {
assert(first.length == second.length);
for (int i = 0; i < first.length; i++) {
if (first[i] != second[i]) return false;
}
return true;
}
}
/// Call structure with named arguments. This is an implementation detail of the
/// CallStructure interface.
class _NamedCallStructure extends CallStructure {
@override
final List<String> namedArguments;
/// The list of ordered named arguments is computed lazily. Initially `null`.
List<String>? _orderedNamedArguments;
_NamedCallStructure(int argumentCount, this.namedArguments,
int typeArgumentCount, this._orderedNamedArguments)
: assert(namedArguments.isNotEmpty),
super._(argumentCount, typeArgumentCount);
@override
bool get isNamed => true;
@override
bool get isUnnamed => false;
@override
int get namedArgumentCount => namedArguments.length;
@override
int get positionalArgumentCount => argumentCount - namedArgumentCount;
@override
bool get isNormalized =>
identical(namedArguments, getOrderedNamedArguments());
@override
CallStructure toNormalized() => isNormalized
? this
: _NamedCallStructure(argumentCount, getOrderedNamedArguments(),
typeArgumentCount, getOrderedNamedArguments());
@override
List<String> getOrderedNamedArguments() {
return _orderedNamedArguments ??= _getOrderedNamedArguments();
}
List<String> _getOrderedNamedArguments() {
List<String> ordered = List.of(namedArguments, growable: false);
ordered.sort((String first, String second) => first.compareTo(second));
// Use the same List if [namedArguments] is already ordered to indicate this
// _NamedCallStructure is normalized.
if (CallStructure._sameNames(ordered, namedArguments)) {
return namedArguments;
}
return ordered;
}
@override
String structureToString() {
StringBuffer sb = StringBuffer();
sb.write('arity=$argumentCount, named=[${namedArguments.join(', ')}]');
if (typeArgumentCount != 0) {
sb.write(', types=$typeArgumentCount');
}
return sb.toString();
}
}