|
| 1 | +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:analyzer/dart/element/element.dart'; |
| 6 | +import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| 7 | +import 'package:analyzer/dart/element/type.dart'; |
| 8 | +import 'package:analyzer/src/dart/element/element.dart'; |
| 9 | +import 'package:analyzer/src/dart/element/type.dart'; |
| 10 | +import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| 11 | +import 'package:analyzer/src/dart/element/type_visitor.dart'; |
| 12 | +import 'package:analyzer/src/generated/type_system.dart'; |
| 13 | + |
| 14 | +class RuntimeTypeEqualityHelper { |
| 15 | + final TypeSystemImpl _typeSystem; |
| 16 | + |
| 17 | + RuntimeTypeEqualityHelper(TypeSystemImpl typeSystem) |
| 18 | + : _typeSystem = typeSystem; |
| 19 | + |
| 20 | + /// Return `true` if runtime types [T1] and [T2] are equal. |
| 21 | + /// |
| 22 | + /// nnbd/feature-specification.md#runtime-type-equality-operator |
| 23 | + bool equal(DartType T1, DartType T2) { |
| 24 | + var N1 = _typeSystem.normalize(T1); |
| 25 | + var N2 = _typeSystem.normalize(T2); |
| 26 | + return const RuntimeTypeEqualityVisitor().visit(N1, N2); |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +class RuntimeTypeEqualityVisitor extends DartTypeVisitor1<bool, DartType> { |
| 31 | + const RuntimeTypeEqualityVisitor(); |
| 32 | + |
| 33 | + @override |
| 34 | + bool defaultDartType(DartType T1, DartType T2) { |
| 35 | + throw UnimplementedError('(${T1.runtimeType}) $T1'); |
| 36 | + } |
| 37 | + |
| 38 | + bool visit(DartType T1, DartType T2) { |
| 39 | + return DartTypeVisitor1.visit(T1, this, T2); |
| 40 | + } |
| 41 | + |
| 42 | + @override |
| 43 | + bool visitDynamicType(DynamicTypeImpl T1, DartType T2) { |
| 44 | + return identical(T1, T2); |
| 45 | + } |
| 46 | + |
| 47 | + @override |
| 48 | + bool visitFunctionType(FunctionType T1, DartType T2) { |
| 49 | + if (T2 is FunctionType) { |
| 50 | + var typeParameters = _typeParameters(T1.typeFormals, T2.typeFormals); |
| 51 | + if (typeParameters == null) { |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + bool equal(DartType T1, DartType T2) { |
| 56 | + T1 = typeParameters.T1_substitution.substituteType(T1); |
| 57 | + T2 = typeParameters.T2_substitution.substituteType(T2); |
| 58 | + return visit(T1, T2); |
| 59 | + } |
| 60 | + |
| 61 | + if (!equal(T1.returnType, T2.returnType)) { |
| 62 | + return false; |
| 63 | + } |
| 64 | + |
| 65 | + var T1_parameters = T1.parameters; |
| 66 | + var T2_parameters = T2.parameters; |
| 67 | + if (T1_parameters.length != T2_parameters.length) { |
| 68 | + return false; |
| 69 | + } |
| 70 | + |
| 71 | + for (var i = 0; i < T1_parameters.length; i++) { |
| 72 | + var T1_parameter = T1_parameters[i]; |
| 73 | + var T2_parameter = T2_parameters[i]; |
| 74 | + |
| 75 | + // ignore: deprecated_member_use_from_same_package |
| 76 | + if (T1_parameter.parameterKind != T2_parameter.parameterKind) { |
| 77 | + return false; |
| 78 | + } |
| 79 | + |
| 80 | + if (T1_parameter.isNamed) { |
| 81 | + if (T1_parameter.name != T2_parameter.name) { |
| 82 | + return false; |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + if (!equal(T1_parameter.type, T2_parameter.type)) { |
| 87 | + return false; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + return true; |
| 92 | + } |
| 93 | + return false; |
| 94 | + } |
| 95 | + |
| 96 | + @override |
| 97 | + bool visitInterfaceType(InterfaceType T1, DartType T2) { |
| 98 | + if (T2 is InterfaceType && |
| 99 | + T1.element == T2.element && |
| 100 | + _compatibleNullability(T1, T2)) { |
| 101 | + var T1_typeArguments = T1.typeArguments; |
| 102 | + var T2_typeArguments = T2.typeArguments; |
| 103 | + if (T1_typeArguments.length == T2_typeArguments.length) { |
| 104 | + for (var i = 0; i < T1_typeArguments.length; i++) { |
| 105 | + var T1_typeArgument = T1_typeArguments[i]; |
| 106 | + var T2_typeArgument = T2_typeArguments[i]; |
| 107 | + if (!visit(T1_typeArgument, T2_typeArgument)) { |
| 108 | + return false; |
| 109 | + } |
| 110 | + } |
| 111 | + return true; |
| 112 | + } |
| 113 | + } |
| 114 | + return false; |
| 115 | + } |
| 116 | + |
| 117 | + @override |
| 118 | + bool visitNeverType(NeverTypeImpl T1, DartType T2) { |
| 119 | + // Note, that all types are normalized before this visitor. |
| 120 | + // So, `Never?` never happens, it is already `Null`. |
| 121 | + assert(T1.nullabilitySuffix != NullabilitySuffix.question); |
| 122 | + return T2 is NeverTypeImpl && _compatibleNullability(T1, T2); |
| 123 | + } |
| 124 | + |
| 125 | + @override |
| 126 | + bool visitTypeParameterType(TypeParameterType T1, DartType T2) { |
| 127 | + return T2 is TypeParameterType && |
| 128 | + _compatibleNullability(T1, T2) && |
| 129 | + T1.element == T2.element; |
| 130 | + } |
| 131 | + |
| 132 | + @override |
| 133 | + bool visitVoidType(VoidType T1, DartType T2) { |
| 134 | + return identical(T1, T2); |
| 135 | + } |
| 136 | + |
| 137 | + bool _compatibleNullability(DartType T1, DartType T2) { |
| 138 | + var T1_nullability = T1.nullabilitySuffix; |
| 139 | + var T2_nullability = T2.nullabilitySuffix; |
| 140 | + return T1_nullability == T2_nullability || |
| 141 | + T1_nullability == NullabilitySuffix.star && |
| 142 | + T2_nullability == NullabilitySuffix.none || |
| 143 | + T2_nullability == NullabilitySuffix.star && |
| 144 | + T1_nullability == NullabilitySuffix.none; |
| 145 | + } |
| 146 | + |
| 147 | + /// Determines if the two lists of type parameters are equal. If they are, |
| 148 | + /// returns a [_TypeParametersResult] indicating the substitutions necessary |
| 149 | + /// to demonstrate their equality. If they aren't, returns `null`. |
| 150 | + _TypeParametersResult _typeParameters( |
| 151 | + List<TypeParameterElement> T1_parameters, |
| 152 | + List<TypeParameterElement> T2_parameters, |
| 153 | + ) { |
| 154 | + if (T1_parameters.length != T2_parameters.length) { |
| 155 | + return null; |
| 156 | + } |
| 157 | + |
| 158 | + var newParameters = <TypeParameterElementImpl>[]; |
| 159 | + var newTypes = <TypeParameterType>[]; |
| 160 | + for (var i = 0; i < T1_parameters.length; i++) { |
| 161 | + var name = T1_parameters[i].name; |
| 162 | + var newParameter = TypeParameterElementImpl.synthetic(name); |
| 163 | + newParameters.add(newParameter); |
| 164 | + |
| 165 | + var newType = newParameter.instantiate( |
| 166 | + nullabilitySuffix: NullabilitySuffix.none, |
| 167 | + ); |
| 168 | + newTypes.add(newType); |
| 169 | + } |
| 170 | + |
| 171 | + var T1_substitution = Substitution.fromPairs(T1_parameters, newTypes); |
| 172 | + var T2_substitution = Substitution.fromPairs(T2_parameters, newTypes); |
| 173 | + for (var i = 0; i < T1_parameters.length; i++) { |
| 174 | + var T1_parameter = T1_parameters[i]; |
| 175 | + var T2_parameter = T2_parameters[i]; |
| 176 | + |
| 177 | + var T1_bound = T1_parameter.bound; |
| 178 | + var T2_bound = T2_parameter.bound; |
| 179 | + if (T1_bound == null && T2_bound == null) { |
| 180 | + // OK, no bound. |
| 181 | + } else if (T1_bound != null && T2_bound != null) { |
| 182 | + T1_bound = T1_substitution.substituteType(T1_bound); |
| 183 | + T2_bound = T2_substitution.substituteType(T2_bound); |
| 184 | + if (!visit(T1_bound, T2_bound)) { |
| 185 | + return null; |
| 186 | + } |
| 187 | + } else { |
| 188 | + return null; |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + return _TypeParametersResult(T1_substitution, T2_substitution); |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +class _TypeParametersResult { |
| 197 | + final Substitution T1_substitution; |
| 198 | + final Substitution T2_substitution; |
| 199 | + |
| 200 | + _TypeParametersResult(this.T1_substitution, this.T2_substitution); |
| 201 | +} |
0 commit comments