| // 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. |
| |
| // @dart = 2.9 |
| |
| import 'dart:math'; |
| |
| import 'package:kernel/ast.dart' hide MapEntry; |
| |
| /// 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, {bool forSetter}) { |
| 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) => |
| _positionalParameters != null && index < _positionalParameters.length |
| ? _positionalParameters[index] |
| : 0; |
| |
| /// Returns the covariance mask for the named parameter with the [name]. |
| int getNamedVariance(String name) => |
| _namedParameters != null ? (_namedParameters[name] ?? 0) : 0; |
| |
| /// Returns `true` if the [index]th type parameter is generic-covariant-impl. |
| bool isTypeParameterGenericCovariantImpl(int index) => |
| _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), |
| null); |
| 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), null); |
| 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; |
| 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); |
| } |
| } |
| 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; |
| if (_positionalParameters != null) { |
| for (int covariance in _positionalParameters) { |
| hash += covariance.hashCode * 17; |
| } |
| } |
| if (_namedParameters != null) { |
| for (String name in _namedParameters.keys) { |
| hash += name.hashCode * 19 + _namedParameters[name].hashCode * 23; |
| } |
| } |
| 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 = ''; |
| 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 = ','; |
| } |
| } |
| } |
| 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 = ','; |
| } |
| } |
| } |
| 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(); |
| } |
| } |