blob: 07be5ad5e7445ceedfe17ee6f4ee445a7a029a72 [file] [log] [blame]
// Copyright (c) 2020, 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 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_visitor.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
class RuntimeTypeEqualityHelper {
final TypeSystemImpl _typeSystem;
RuntimeTypeEqualityHelper(TypeSystemImpl typeSystem)
: _typeSystem = typeSystem;
/// Return `true` if runtime types [T1] and [T2] are equal.
///
/// nnbd/feature-specification.md#runtime-type-equality-operator
bool equal(DartType T1, DartType T2) {
var N1 = _typeSystem.normalize(T1);
var N2 = _typeSystem.normalize(T2);
return N1.acceptWithArgument(const RuntimeTypeEqualityVisitor(), N2);
}
}
class RuntimeTypeEqualityVisitor
extends TypeVisitorWithArgument<bool, DartType> {
const RuntimeTypeEqualityVisitor();
@override
bool visitDynamicType(DynamicType T1, DartType T2) {
return identical(T1, T2);
}
@override
bool visitFunctionType(FunctionType T1, DartType T2) {
if (T2 is FunctionType) {
var typeParameters = _typeParameters(T1.typeFormals, T2.typeFormals);
if (typeParameters == null) {
return false;
}
bool equal(DartType T1, DartType T2) {
T1 = typeParameters.T1_substitution.substituteType(T1);
T2 = typeParameters.T2_substitution.substituteType(T2);
return T1.acceptWithArgument(this, T2);
}
if (!equal(T1.returnType, T2.returnType)) {
return false;
}
var T1_parameters = T1.parameters;
var T2_parameters = T2.parameters;
if (T1_parameters.length != T2_parameters.length) {
return false;
}
for (var i = 0; i < T1_parameters.length; i++) {
var T1_parameter = T1_parameters[i];
var T2_parameter = T2_parameters[i];
// ignore: deprecated_member_use_from_same_package
if (T1_parameter.parameterKind != T2_parameter.parameterKind) {
return false;
}
if (T1_parameter.isNamed) {
if (T1_parameter.name != T2_parameter.name) {
return false;
}
}
if (!equal(T1_parameter.type, T2_parameter.type)) {
return false;
}
}
return true;
}
return false;
}
@override
bool visitInterfaceType(InterfaceType T1, DartType T2) {
if (T2 is InterfaceType &&
T1.element == T2.element &&
_compatibleNullability(T1, T2)) {
var T1_typeArguments = T1.typeArguments;
var T2_typeArguments = T2.typeArguments;
if (T1_typeArguments.length == T2_typeArguments.length) {
for (var i = 0; i < T1_typeArguments.length; i++) {
var T1_typeArgument = T1_typeArguments[i];
var T2_typeArgument = T2_typeArguments[i];
if (!T1_typeArgument.acceptWithArgument(this, T2_typeArgument)) {
return false;
}
}
return true;
}
}
return false;
}
@override
bool visitNeverType(NeverType T1, DartType T2) {
// Note, that all types are normalized before this visitor.
// So, `Never?` never happens, it is already `Null`.
assert(T1.nullabilitySuffix != NullabilitySuffix.question);
return T2 is NeverTypeImpl && _compatibleNullability(T1, T2);
}
@override
bool visitTypeParameterType(TypeParameterType T1, DartType T2) {
return T2 is TypeParameterType &&
_compatibleNullability(T1, T2) &&
T1.element == T2.element;
}
@override
bool visitVoidType(VoidType T1, DartType T2) {
return identical(T1, T2);
}
bool _compatibleNullability(DartType T1, DartType T2) {
var T1_nullability = T1.nullabilitySuffix;
var T2_nullability = T2.nullabilitySuffix;
return T1_nullability == T2_nullability ||
T1_nullability == NullabilitySuffix.star &&
T2_nullability == NullabilitySuffix.none ||
T2_nullability == NullabilitySuffix.star &&
T1_nullability == NullabilitySuffix.none;
}
/// Determines if the two lists of type parameters are equal. If they are,
/// returns a [_TypeParametersResult] indicating the substitutions necessary
/// to demonstrate their equality. If they aren't, returns `null`.
_TypeParametersResult _typeParameters(
List<TypeParameterElement> T1_parameters,
List<TypeParameterElement> T2_parameters,
) {
if (T1_parameters.length != T2_parameters.length) {
return null;
}
var newParameters = <TypeParameterElementImpl>[];
var newTypes = <TypeParameterType>[];
for (var i = 0; i < T1_parameters.length; i++) {
var name = T1_parameters[i].name;
var newParameter = TypeParameterElementImpl.synthetic(name);
newParameters.add(newParameter);
var newType = newParameter.instantiate(
nullabilitySuffix: NullabilitySuffix.none,
);
newTypes.add(newType);
}
var T1_substitution = Substitution.fromPairs(T1_parameters, newTypes);
var T2_substitution = Substitution.fromPairs(T2_parameters, newTypes);
for (var i = 0; i < T1_parameters.length; i++) {
var T1_parameter = T1_parameters[i];
var T2_parameter = T2_parameters[i];
var T1_bound = T1_parameter.bound;
var T2_bound = T2_parameter.bound;
if (T1_bound == null && T2_bound == null) {
// OK, no bound.
} else if (T1_bound != null && T2_bound != null) {
T1_bound = T1_substitution.substituteType(T1_bound);
T2_bound = T2_substitution.substituteType(T2_bound);
if (!T1_bound.acceptWithArgument(this, T2_bound)) {
return null;
}
} else {
return null;
}
}
return _TypeParametersResult(T1_substitution, T2_substitution);
}
}
class _TypeParametersResult {
final Substitution T1_substitution;
final Substitution T2_substitution;
_TypeParametersResult(this.T1_substitution, this.T2_substitution);
}