Skip to content

Commit d550879

Browse files
scheglovcommit-bot@chromium.org
authored andcommitted
Implement runtime type equality.
Change-Id: I603b5147d7d7e6d8e7267be9c861324b575f259d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134041 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Paul Berry <[email protected]>
1 parent 5d7f309 commit d550879

File tree

6 files changed

+659
-0
lines changed

6 files changed

+659
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
}

pkg/analyzer/lib/src/dart/element/type_visitor.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,76 @@ class DartTypeVisitor<R> {
6262
throw UnimplementedError('(${type.runtimeType}) $type');
6363
}
6464
}
65+
66+
class DartTypeVisitor1<R, T> {
67+
const DartTypeVisitor1();
68+
69+
R defaultDartType(DartType type, T arg) => null;
70+
71+
R visitDynamicType(DynamicTypeImpl type, T arg) {
72+
return defaultDartType(type, arg);
73+
}
74+
75+
R visitFunctionType(FunctionType type, T arg) {
76+
return defaultDartType(type, arg);
77+
}
78+
79+
R visitFunctionTypeBuilder(FunctionTypeBuilder type, T arg) {
80+
return defaultDartType(type, arg);
81+
}
82+
83+
R visitInterfaceType(InterfaceType type, T arg) {
84+
return defaultDartType(type, arg);
85+
}
86+
87+
R visitNamedTypeBuilder(NamedTypeBuilder type, T arg) {
88+
return defaultDartType(type, arg);
89+
}
90+
91+
R visitNeverType(NeverTypeImpl type, T arg) {
92+
return defaultDartType(type, arg);
93+
}
94+
95+
R visitTypeParameterType(TypeParameterType type, T arg) {
96+
return defaultDartType(type, arg);
97+
}
98+
99+
R visitUnknownInferredType(UnknownInferredType type, T arg) {
100+
return defaultDartType(type, arg);
101+
}
102+
103+
R visitVoidType(VoidType type, T arg) {
104+
return defaultDartType(type, arg);
105+
}
106+
107+
static R visit<R, T>(DartType type, DartTypeVisitor1<R, T> visitor, T arg) {
108+
if (type is NeverTypeImpl) {
109+
return visitor.visitNeverType(type, arg);
110+
}
111+
if (type is DynamicTypeImpl) {
112+
return visitor.visitDynamicType(type, arg);
113+
}
114+
if (type is FunctionType) {
115+
return visitor.visitFunctionType(type, arg);
116+
}
117+
if (type is FunctionTypeBuilder) {
118+
return visitor.visitFunctionTypeBuilder(type, arg);
119+
}
120+
if (type is InterfaceType) {
121+
return visitor.visitInterfaceType(type, arg);
122+
}
123+
if (type is NamedTypeBuilder) {
124+
return visitor.visitNamedTypeBuilder(type, arg);
125+
}
126+
if (type is TypeParameterType) {
127+
return visitor.visitTypeParameterType(type, arg);
128+
}
129+
if (type is UnknownInferredType) {
130+
return visitor.visitUnknownInferredType(type, arg);
131+
}
132+
if (type is VoidType) {
133+
return visitor.visitVoidType(type, arg);
134+
}
135+
throw UnimplementedError('(${type.runtimeType}) $type');
136+
}
137+
}

pkg/analyzer/lib/src/generated/type_system.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:analyzer/src/dart/element/element.dart';
1818
import 'package:analyzer/src/dart/element/member.dart' show TypeParameterMember;
1919
import 'package:analyzer/src/dart/element/normalize.dart';
2020
import 'package:analyzer/src/dart/element/nullability_eliminator.dart';
21+
import 'package:analyzer/src/dart/element/runtime_type_equality.dart';
2122
import 'package:analyzer/src/dart/element/top_merge.dart';
2223
import 'package:analyzer/src/dart/element/type.dart';
2324
import 'package:analyzer/src/dart/element/type_algebra.dart';
@@ -1520,6 +1521,13 @@ class Dart2TypeSystem extends TypeSystem {
15201521
return NormalizeHelper(this).normalize(T);
15211522
}
15221523

1524+
/// Return `true` if runtime types [T1] and [T2] are equal.
1525+
///
1526+
/// nnbd/feature-specification.md#runtime-type-equality-operator
1527+
bool runtimeTypesEqual(DartType T1, DartType T2) {
1528+
return RuntimeTypeEqualityHelper(this).equal(T1, T2);
1529+
}
1530+
15231531
@override
15241532
DartType refineBinaryExpressionType(DartType leftType, TokenType operator,
15251533
DartType rightType, DartType currentType) {

pkg/analyzer/test/generated/elements_types_mixin.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ import 'package:analyzer/src/generated/utilities_dart.dart';
1414
import 'package:meta/meta.dart';
1515

1616
mixin ElementsTypesMixin {
17+
InterfaceType get boolNone {
18+
var element = typeProvider.boolElement;
19+
return interfaceTypeNone(element);
20+
}
21+
22+
InterfaceType get boolQuestion {
23+
var element = typeProvider.boolElement;
24+
return interfaceTypeQuestion(element);
25+
}
26+
27+
InterfaceType get boolStar {
28+
var element = typeProvider.boolElement;
29+
return interfaceTypeStar(element);
30+
}
31+
1732
InterfaceType get doubleNone {
1833
var element = typeProvider.doubleType.element;
1934
return interfaceTypeNone(element);

0 commit comments

Comments
 (0)