Skip to content

Commit bb8db16

Browse files
stereotype441Commit Queue
authored andcommitted
[mini_types] Implement and test hashCode and equality logic.
Previously, the `hashCode` and `==` logic for the `Type` hierarchy used a short-cut: convert the type to a string and then hash or equality-check the result. However, that trick won't work for generic function types (which I intend to implement in a follow-up CL), because generic function types that are alpha-equivalent (i.e. equivalent except for the names of type formals) need to be considered equal. So, as preparation for supporting generic function types, this change adds full implementations of `hashCode` and `==` for all the classes in the `Type` hierarchy. Change-Id: I8312d22cabaeb8c38cd58da80acdb3f068ee5c2b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395620 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Chloe Stefantsova <[email protected]>
1 parent 47681c7 commit bb8db16

File tree

3 files changed

+175
-9
lines changed

3 files changed

+175
-9
lines changed

pkg/_fe_analyzer_shared/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies:
1515
# See also https://dart.dev/tools/pub/dependencies.
1616
dev_dependencies:
1717
checks: any
18+
collection: any
1819
macros: any
1920
test: any
2021

pkg/_fe_analyzer_shared/test/mini_types.dart

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'dart:core' hide Type;
1111

1212
import 'package:_fe_analyzer_shared/src/type_inference/nullability_suffix.dart';
1313
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
14+
import 'package:collection/collection.dart';
1415

1516
/// Surrounds [s] with parentheses if [condition] is `true`, otherwise returns
1617
/// [s] unchanged.
@@ -62,6 +63,14 @@ class FunctionType extends Type
6263
}
6364
}
6465

66+
@override
67+
int get hashCode => Object.hash(
68+
returnType,
69+
const ListEquality().hash(positionalParameters),
70+
requiredPositionalParameterCount,
71+
const ListEquality().hash(namedParameters),
72+
nullabilitySuffix);
73+
6574
@override
6675
List<Type> get positionalParameterTypes => positionalParameters;
6776

@@ -71,6 +80,17 @@ class FunctionType extends Type
7180
@override
7281
List<Never> get typeFormals => const [];
7382

83+
@override
84+
bool operator ==(Object other) =>
85+
other is FunctionType &&
86+
returnType == other.returnType &&
87+
const ListEquality()
88+
.equals(positionalParameters, other.positionalParameters) &&
89+
requiredPositionalParameterCount ==
90+
other.requiredPositionalParameterCount &&
91+
const ListEquality().equals(namedParameters, other.namedParameters) &&
92+
nullabilitySuffix == other.nullabilitySuffix;
93+
7494
@override
7595
Type? closureWithRespectToUnknown({required bool covariant}) {
7696
Type? newReturnType =
@@ -197,6 +217,16 @@ class NamedFunctionParameter
197217
NamedFunctionParameter(
198218
{required this.isRequired, required this.name, required this.type});
199219

220+
@override
221+
int get hashCode => Object.hash(name, type, isRequired);
222+
223+
@override
224+
bool operator ==(Object other) =>
225+
other is NamedFunctionParameter &&
226+
name == other.name &&
227+
type == other.type &&
228+
isRequired == other.isRequired;
229+
200230
@override
201231
String toString() => [if (isRequired) 'required', type, name].join(' ');
202232
}
@@ -209,6 +239,13 @@ class NamedType implements SharedNamedTypeStructure<Type> {
209239
final Type type;
210240

211241
NamedType({required this.name, required this.type});
242+
243+
@override
244+
int get hashCode => Object.hash(name, type);
245+
246+
@override
247+
bool operator ==(Object other) =>
248+
other is NamedType && name == other.name && type == other.type;
212249
}
213250

214251
/// Representation of the type `Never` suitable for unit testing of code in the
@@ -276,13 +313,24 @@ class PrimaryType extends Type {
276313
NullabilitySuffix nullabilitySuffix = NullabilitySuffix.none})
277314
: this._(nameInfo, args: args, nullabilitySuffix: nullabilitySuffix);
278315

316+
@override
317+
int get hashCode => Object.hash(runtimeType, nameInfo,
318+
const ListEquality().hash(args), nullabilitySuffix);
319+
279320
bool get isInterfaceType {
280321
return nameInfo is InterfaceTypeName;
281322
}
282323

283324
/// The name of the type.
284325
String get name => nameInfo.name;
285326

327+
@override
328+
bool operator ==(Object other) =>
329+
other is PrimaryType &&
330+
nameInfo == other.nameInfo &&
331+
const ListEquality().equals(args, other.args) &&
332+
nullabilitySuffix == other.nullabilitySuffix;
333+
286334
@override
287335
Type? closureWithRespectToUnknown({required bool covariant}) {
288336
List<Type>? newArgs =
@@ -315,9 +363,6 @@ class RecordType extends Type implements SharedRecordTypeStructure<Type> {
315363

316364
final List<NamedType> namedTypes;
317365

318-
@override
319-
List<NamedType> get sortedNamedTypes => namedTypes;
320-
321366
RecordType({
322367
required this.positionalTypes,
323368
required this.namedTypes,
@@ -329,6 +374,23 @@ class RecordType extends Type implements SharedRecordTypeStructure<Type> {
329374
}
330375
}
331376

377+
@override
378+
int get hashCode => Object.hash(
379+
runtimeType,
380+
const ListEquality().hash(positionalTypes),
381+
const ListEquality().hash(namedTypes),
382+
nullabilitySuffix);
383+
384+
@override
385+
List<NamedType> get sortedNamedTypes => namedTypes;
386+
387+
@override
388+
bool operator ==(Object other) =>
389+
other is RecordType &&
390+
const ListEquality().equals(positionalTypes, other.positionalTypes) &&
391+
const ListEquality().equals(namedTypes, other.namedTypes) &&
392+
nullabilitySuffix == other.nullabilitySuffix;
393+
332394
@override
333395
Type? closureWithRespectToUnknown({required bool covariant}) {
334396
List<Type>? newPositional;
@@ -448,14 +510,8 @@ abstract class Type implements SharedTypeStructure<Type> {
448510

449511
const Type._({this.nullabilitySuffix = NullabilitySuffix.none});
450512

451-
@override
452-
int get hashCode => type.hashCode;
453-
454513
String get type => toString();
455514

456-
@override
457-
bool operator ==(Object other) => other is Type && this.type == other.type;
458-
459515
/// Finds the nearest type that doesn't involve the unknown type (`_`).
460516
///
461517
/// If [covariant] is `true`, a supertype will be returned (replacing `_` with
@@ -551,6 +607,17 @@ class TypeParameter extends TypeNameInfo
551607
@override
552608
String get displayName => name;
553609

610+
@override
611+
int get hashCode {
612+
// To ensure that generic function types with different type formal names
613+
// have the same hash code, [FunctionType.hashCode] substitutes in a
614+
// consistently-named set of synthetic type formals in place of the type
615+
// formals. Since a fresh set of synthetic type formals will be created each
616+
// time [FunctionType.hashCode] is called, it's important that two type
617+
// formals with the same name (and bound) have the same hash code.
618+
return Object.hash(name, bound);
619+
}
620+
554621
@override
555622
String toString() => name;
556623
}
@@ -575,6 +642,17 @@ class TypeParameterType extends Type {
575642
/// The type parameter's bound.
576643
Type get bound => typeParameter.bound;
577644

645+
@override
646+
int get hashCode =>
647+
Object.hash(runtimeType, typeParameter, promotion, nullabilitySuffix);
648+
649+
@override
650+
bool operator ==(Object other) =>
651+
other is TypeParameterType &&
652+
typeParameter == other.typeParameter &&
653+
promotion == other.promotion &&
654+
nullabilitySuffix == other.nullabilitySuffix;
655+
578656
@override
579657
Type? closureWithRespectToUnknown({required bool covariant}) {
580658
var newPromotion =
@@ -1318,6 +1396,14 @@ class UnknownType extends Type implements SharedUnknownTypeStructure<Type> {
13181396
const UnknownType({super.nullabilitySuffix = NullabilitySuffix.none})
13191397
: super._();
13201398

1399+
@override
1400+
int get hashCode =>
1401+
Object.hash(runtimeType, nullabilitySuffix, nullabilitySuffix);
1402+
1403+
@override
1404+
bool operator ==(Object other) =>
1405+
other is UnknownType && nullabilitySuffix == other.nullabilitySuffix;
1406+
13211407
@override
13221408
Type? closureWithRespectToUnknown({required bool covariant}) =>
13231409
covariant ? Type('Object?') : NeverType.instance;

pkg/_fe_analyzer_shared/test/mini_types_test.dart

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,85 @@ main() {
523523
});
524524
});
525525

526+
group('hashCode and equality:', () {
527+
void checkEqual(Type t1, Type t2) {
528+
expect(t1 == t2, isTrue);
529+
expect(t1.hashCode == t2.hashCode, isTrue);
530+
}
531+
532+
void checkNotEqual(Type t1, Type t2) {
533+
expect(t1 == t2, isFalse);
534+
// Note: don't compare `t1.hashCode` to `t2.hashCode` because it's not
535+
// guaranteed whether they will be different or not. And besides, it
536+
// really only matters for efficiency, and efficiency is not needed for
537+
// the "mini_types" representation because it's only used in unit tests.
538+
}
539+
540+
test('FunctionType', () {
541+
checkEqual(Type('void Function()'), Type('void Function()'));
542+
checkNotEqual(Type('void Function()?'), Type('void Function()'));
543+
checkNotEqual(Type('T Function()'), Type('void Function()'));
544+
checkNotEqual(Type('void Function(T)'), Type('void Function()'));
545+
checkEqual(Type('void Function(T)'), Type('void Function(T)'));
546+
checkNotEqual(Type('void Function(T)'), Type('void Function(U)'));
547+
checkNotEqual(Type('void Function(T)'), Type('void Function([T])'));
548+
checkNotEqual(Type('void Function({T t})'), Type('void Function()'));
549+
checkEqual(Type('void Function({T t})'), Type('void Function({T t})'));
550+
checkNotEqual(
551+
Type('void Function({T t})'), Type('void Function({required T t})'));
552+
checkNotEqual(Type('void Function({T t})'), Type('void Function({U t})'));
553+
checkNotEqual(Type('void Function({T t})'), Type('void Function({T u})'));
554+
});
555+
556+
test('PrimaryType', () {
557+
checkEqual(Type('int'), Type('int'));
558+
checkNotEqual(Type('int'), Type('String'));
559+
checkNotEqual(Type('int'), Type('int?'));
560+
checkEqual(Type('Map<int, String>'), Type('Map<int, String>'));
561+
checkNotEqual(Type('Map<int, String>'), Type('Map<int, double>'));
562+
checkNotEqual(Type('Map<int, String>'), Type('Map<num, String>'));
563+
checkNotEqual(Type('List<int>'), Type('Iterable<int>'));
564+
checkEqual(Type('dynamic'), Type('dynamic'));
565+
checkEqual(Type('error'), Type('error'));
566+
checkEqual(Type('Never'), Type('Never'));
567+
checkEqual(Type('Null'), Type('Null'));
568+
checkEqual(Type('void'), Type('void'));
569+
checkEqual(Type('FutureOr<int>'), Type('FutureOr<int>'));
570+
checkNotEqual(Type('dynamic'), Type('error'));
571+
checkNotEqual(Type('error'), Type('Never'));
572+
checkNotEqual(Type('Never'), Type('Null'));
573+
checkNotEqual(Type('Null'), Type('void'));
574+
checkNotEqual(Type('void'), Type('dynamic'));
575+
checkNotEqual(Type('FutureOr<int>'), Type('FutureOr<String>'));
576+
checkNotEqual(Type('FutureOr<int>'), Type('dynamic'));
577+
});
578+
579+
test('RecordType', () {
580+
checkEqual(Type('(int,)'), Type('(int,)'));
581+
checkNotEqual(Type('(int,)?'), Type('(int,)'));
582+
checkNotEqual(Type('(int, T)'), Type('(int,)'));
583+
checkNotEqual(Type('(T,)'), Type('(U,)'));
584+
checkNotEqual(Type('(int, {T t})'), Type('(int,)'));
585+
checkEqual(Type('({T t})'), Type('({T t})'));
586+
checkNotEqual(Type('({T t})'), Type('({U t})'));
587+
checkNotEqual(Type('({T t})'), Type('({T u})'));
588+
});
589+
590+
test('TypeParameterType', () {
591+
checkEqual(Type('T'), Type('T'));
592+
checkNotEqual(Type('T?'), Type('T'));
593+
checkNotEqual(Type('T'), Type('U'));
594+
checkNotEqual(Type('T&int'), Type('T'));
595+
checkEqual(Type('T&int'), Type('T&int'));
596+
checkNotEqual(Type('T&int'), Type('T&String'));
597+
});
598+
599+
test('UnknownType', () {
600+
checkEqual(Type('_'), Type('_'));
601+
checkNotEqual(Type('_?'), Type('_'));
602+
});
603+
});
604+
526605
group('recursivelyDemote:', () {
527606
group('FunctionType:', () {
528607
group('return type:', () {

0 commit comments

Comments
 (0)