blob: b33dbcd7881c45c0002066bdbcf9cd3d22d91fd9 [file] [log] [blame]
// Copyright (c) 2018, 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.
import 'dart:math';
import 'package:kernel/ast.dart';
/// Class that holds the covariant and generic-covariant-impl information for
/// a member.
// TODO(johnniwinther): Cache this in ClassMember.
// TODO(johnniwinther): Maybe compact initial positional masks into a single
// int.
class Covariance {
static const int GenericCovariantImpl = 1;
static const int Covariant = 2;
/// Returns the covariance mask for [parameter].
static int covarianceFromParameter(VariableDeclaration parameter) =>
(parameter.isCovariant ? Covariant : 0) |
(parameter.isGenericCovariantImpl ? GenericCovariantImpl : 0);
/// Returns the covariance mask for [field].
static int covarianceFromField(Field field) =>
(field.isCovariant ? Covariant : 0) |
(field.isGenericCovariantImpl ? GenericCovariantImpl : 0);
/// Applies the [covariance] mask to [parameter].
static void covarianceToParameter(
int covariance, VariableDeclaration parameter) {
if ((covariance & Covariant) != 0) {
parameter.isCovariant = true;
}
if ((covariance & GenericCovariantImpl) != 0) {
parameter.isGenericCovariantImpl = true;
}
}
/// Applies the [covariance] mask to parameter.
static void covarianceToField(int covariance, Field field) {
if ((covariance & Covariant) != 0) {
field.isCovariant = true;
}
if ((covariance & GenericCovariantImpl) != 0) {
field.isGenericCovariantImpl = true;
}
}
/// The covariance mask for the positional parameters.
///
/// If no positional parameters have covariance, this is `null`.
final List<int>? _positionalParameters;
/// The covariance mask for the named parameters with name covariance.
///
/// If no named parameters have covariance, this is `null`.
final Map<String, int>? _namedParameters;
/// The generic-covariant-impl state for the type parameters.
///
/// If no type parameters are generic-covariant-impl, this is `null`.
final List<bool>? _typeParameters;
Covariance.internal(
this._positionalParameters, this._namedParameters, this._typeParameters) {
assert(_positionalParameters == null ||
_positionalParameters!.any((element) => element != 0));
assert(_namedParameters == null ||
_namedParameters!.values.isNotEmpty &&
_namedParameters!.values.every((element) => element != 0));
assert(
_typeParameters == null || _typeParameters!.any((element) => element));
}
/// The empty covariance.
///
/// This is used for all members that do not use any covariance, regardless
/// of parameter structure.
const Covariance.empty()
: _positionalParameters = null,
_namedParameters = null,
_typeParameters = null;
/// Computes the covariance for the setter aspect of [field].
///
/// The getter aspect of a field never uses covariance.
factory Covariance.fromField(Field field) {
int covariance = covarianceFromField(field);
if (covariance == 0) {
return const Covariance.empty();
}
return new Covariance.internal(<int>[covariance], null, null);
}
/// Computes the covariance for the [setter].
factory Covariance.fromSetter(Procedure setter) {
int covariance =
covarianceFromParameter(setter.function!.positionalParameters.first);
if (covariance == 0) {
return const Covariance.empty();
}
return new Covariance.internal(<int>[covariance], null, null);
}
/// Computes the covariance for the [procedure].
factory Covariance.fromMethod(Procedure procedure) {
FunctionNode function = procedure.function!;
List<int>? positionalParameters;
if (function.positionalParameters.isNotEmpty) {
for (int index = 0;
index < function.positionalParameters.length;
index++) {
int covariance =
covarianceFromParameter(function.positionalParameters[index]);
if (covariance != 0) {
positionalParameters ??=
new List<int>.filled(function.positionalParameters.length, 0);
positionalParameters[index] = covariance;
}
}
}
Map<String, int>? namedParameters;
if (function.namedParameters.isNotEmpty) {
for (int index = 0; index < function.namedParameters.length; index++) {
VariableDeclaration parameter = function.namedParameters[index];
int covariance = covarianceFromParameter(parameter);
if (covariance != 0) {
namedParameters ??= {};
namedParameters[parameter.name!] = covariance;
}
}
}
List<bool>? typeParameters;
if (function.typeParameters.isNotEmpty) {
for (int index = 0; index < function.typeParameters.length; index++) {
if (function.typeParameters[index].isGenericCovariantImpl) {
typeParameters ??=
new List<bool>.filled(function.typeParameters.length, false);
typeParameters[index] = true;
}
}
}
if (positionalParameters == null &&
namedParameters == null &&
typeParameters == null) {
return const Covariance.empty();
}
return new Covariance.internal(
positionalParameters, namedParameters, typeParameters);
}
/// Computes the covariance for [member].
///
/// If [forSetter] is `true`, the covariance is computed for the setter
/// aspect of [member]. Otherwise, the covariance for the getter/method aspect
/// of [member] is computed.
factory Covariance.fromMember(Member member, {required bool forSetter}) {
// ignore: unnecessary_null_comparison
assert(forSetter != null);
if (member is Procedure) {
if (member.kind == ProcedureKind.Getter) {
return const Covariance.empty();
} else if (member.kind == ProcedureKind.Setter) {
return new Covariance.fromSetter(member);
} else {
return new Covariance.fromMethod(member);
}
} else if (member is Field) {
if (forSetter) {
return new Covariance.fromField(member);
} else {
return const Covariance.empty();
}
} else {
throw new UnsupportedError(
"Unexpected member $member (${member.runtimeType})");
}
}
/// Returns `true` if this is the empty covariance.
bool get isEmpty =>
_positionalParameters == null &&
_namedParameters == null &&
_typeParameters == null;
/// Returns the covariance mask for the [index]th positional parameter.
int getPositionalVariance(int index) {
List<int>? positionalParameters = _positionalParameters;
return positionalParameters != null && index < positionalParameters.length
? positionalParameters[index]
: 0;
}
/// Returns the covariance mask for the named parameter with the [name].
int getNamedVariance(String name) {
Map<String, int>? namedParameters = _namedParameters;
return namedParameters != null ? (namedParameters[name] ?? 0) : 0;
}
/// Returns `true` if the [index]th type parameter is generic-covariant-impl.
bool isTypeParameterGenericCovariantImpl(int index) {
List<bool>? typeParameters = _typeParameters;
return typeParameters != null && index < typeParameters.length
? typeParameters[index]
: false;
}
/// Returns the merge of this covariance with [other] in which parameters are
/// covariant if they are covariant in either [this] or [other].
Covariance merge(Covariance other) {
if (identical(this, other)) return this;
List<int>? positionalParameters;
if (_positionalParameters == null) {
positionalParameters = other._positionalParameters;
} else if (other._positionalParameters == null) {
positionalParameters = _positionalParameters;
} else {
positionalParameters = new List<int>.filled(
max(_positionalParameters!.length,
other._positionalParameters!.length),
0);
for (int index = 0; index < positionalParameters.length; index++) {
positionalParameters[index] =
getPositionalVariance(index) | other.getPositionalVariance(index);
}
}
Map<String, int>? namedParameters;
if (_namedParameters == null) {
namedParameters = other._namedParameters;
} else if (other._namedParameters == null) {
namedParameters = _namedParameters;
} else {
namedParameters = {};
Set<String> names = {
..._namedParameters!.keys,
...other._namedParameters!.keys
};
for (String name in names) {
namedParameters[name] =
getNamedVariance(name) | other.getNamedVariance(name);
}
}
List<bool>? typeParameters;
if (_typeParameters == null) {
typeParameters = other._typeParameters;
} else if (other._typeParameters == null) {
typeParameters = _typeParameters;
} else {
typeParameters = new List<bool>.filled(
max(_typeParameters!.length, other._typeParameters!.length), false);
for (int index = 0; index < typeParameters.length; index++) {
typeParameters[index] = isTypeParameterGenericCovariantImpl(index) ||
other.isTypeParameterGenericCovariantImpl(index);
}
}
if (positionalParameters == null &&
namedParameters == null &&
typeParameters == null) {
return const Covariance.empty();
}
return new Covariance.internal(
positionalParameters, namedParameters, typeParameters);
}
/// Update [member] to have the covariant flags set with the covariance in
/// [this].
///
/// No covariance bits are removed from [member] during this process.
void applyCovariance(Member member) {
if (isEmpty) return;
if (member is Procedure) {
FunctionNode function = member.function!;
List<int>? positionalParameters = _positionalParameters;
if (positionalParameters != null) {
for (int index = 0; index < positionalParameters.length; index++) {
if (index < function.positionalParameters.length) {
covarianceToParameter(positionalParameters[index],
function.positionalParameters[index]);
}
}
}
if (_namedParameters != null) {
for (VariableDeclaration parameter in function.namedParameters) {
covarianceToParameter(getNamedVariance(parameter.name!), parameter);
}
}
List<bool>? typeParameters = _typeParameters;
if (typeParameters != null) {
for (int index = 0; index < typeParameters.length; index++) {
if (index < function.typeParameters.length) {
if (typeParameters[index]) {
function.typeParameters[index].isGenericCovariantImpl = true;
}
}
}
}
} else if (member is Field) {
if (_positionalParameters != null) {
covarianceToField(getPositionalVariance(0), member);
}
} else {
throw new UnsupportedError(
"Unexpected member $member (${member.runtimeType})");
}
}
@override
int get hashCode {
int hash = 0;
List<int>? positionalParameters = _positionalParameters;
if (positionalParameters != null) {
for (int covariance in positionalParameters) {
hash += covariance.hashCode * 17;
}
}
Map<String, int>? namedParameters = _namedParameters;
if (namedParameters != null) {
for (String name in namedParameters.keys) {
hash += name.hashCode * 19 + namedParameters[name].hashCode * 23;
}
}
List<bool>? typeParameters = _typeParameters;
if (typeParameters != null) {
for (bool covariance in typeParameters) {
if (covariance) {
hash += covariance.hashCode * 31;
}
}
}
return hash;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is Covariance) {
if (_positionalParameters != other._positionalParameters) {
if (_positionalParameters == null ||
other._positionalParameters == null) {
return false;
}
int positionalParameterCount = max(
_positionalParameters!.length, other._positionalParameters!.length);
for (int i = 0; i < positionalParameterCount; i++) {
if (getPositionalVariance(i) != other.getPositionalVariance(i)) {
return false;
}
}
}
if (_namedParameters != other._namedParameters) {
if (_namedParameters == null || other._namedParameters == null) {
return false;
}
Set<String> names = {
..._namedParameters!.keys,
...other._namedParameters!.keys
};
for (String name in names) {
if (getNamedVariance(name) != other.getNamedVariance(name)) {
return false;
}
}
}
if (_typeParameters != other._typeParameters) {
if (_typeParameters == null || other._typeParameters == null) {
return false;
}
int typeParameterCount =
max(_typeParameters!.length, other._typeParameters!.length);
for (int i = 0; i < typeParameterCount; i++) {
if (isTypeParameterGenericCovariantImpl(i) !=
other.isTypeParameterGenericCovariantImpl(i)) {
return false;
}
}
}
return true;
}
return false;
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
if (isEmpty) {
sb.write('Covariance.empty()');
} else {
sb.write('Covariance(');
String comma = '';
List<int>? positionalParameters = _positionalParameters;
if (positionalParameters != null) {
for (int index = 0; index < positionalParameters.length; index++) {
if (positionalParameters[index] != 0) {
sb.write(comma);
sb.write('$index:');
switch (positionalParameters[index]) {
case GenericCovariantImpl:
sb.write('GenericCovariantImpl');
break;
case Covariant:
sb.write('Covariant');
break;
default:
sb.write('GenericCovariantImpl+Covariant');
break;
}
comma = ',';
}
}
}
Map<String, int>? namedParameters = _namedParameters;
if (namedParameters != null) {
for (String name in namedParameters.keys) {
int covariance = namedParameters[name]!;
if (covariance != 0) {
sb.write(comma);
sb.write('$name:');
switch (covariance) {
case GenericCovariantImpl:
sb.write('GenericCovariantImpl');
break;
case Covariant:
sb.write('Covariant');
break;
default:
sb.write('GenericCovariantImpl+Covariant');
break;
}
comma = ',';
}
}
}
List<bool>? typeParameters = _typeParameters;
if (typeParameters != null) {
sb.write(comma);
sb.write('types:');
comma = '';
for (int index = 0; index < typeParameters.length; index++) {
if (typeParameters[index]) {
sb.write(comma);
sb.write('$index');
comma = ',';
}
}
}
sb.write(')');
}
return sb.toString();
}
}