Skip to content

Instantly share code, notes, and snippets.

@sunderee
Created April 21, 2025 10:18
Show Gist options
  • Save sunderee/fcf48375eb2435ddb054fbad30668ef2 to your computer and use it in GitHub Desktop.
Save sunderee/fcf48375eb2435ddb054fbad30668ef2 to your computer and use it in GitHub Desktop.
Minimalist equatable implementation that does not use third-party dependencies.
abstract base class Equatable {
const Equatable();
List<Object?> get props;
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is Equatable &&
runtimeType == other.runtimeType &&
_iterableEquals(props, other.props);
}
@override
int get hashCode => runtimeType.hashCode ^ _mapPropsToHashCode(props);
@override
String toString() => _mapPropsToString(runtimeType, props);
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
int _mapPropsToHashCode(Iterable<Object?>? props) =>
_finish(props == null ? 0 : props.fold(0, _combine));
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _iterableEquals(Iterable<Object?> a, Iterable<Object?> b) {
assert(
a is! Set && b is! Set,
"iterableEquals doesn't support Sets. Use setEquals instead.",
);
if (identical(a, b)) {
return true;
}
if (a.length != b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (!_objectsEquals(a.elementAt(i), b.elementAt(i))) {
return false;
}
}
return true;
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _numEquals(num a, num b) => a == b;
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _setEquals(Set<Object?> a, Set<Object?> b) {
if (identical(a, b)) {
return true;
}
if (a.length != b.length) {
return false;
}
for (final element in a) {
if (!b.any((e) => _objectsEquals(element, e))) {
return false;
}
}
return true;
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _mapEquals(Map<Object?, Object?> a, Map<Object?, Object?> b) {
if (identical(a, b)) {
return true;
}
if (a.length != b.length) {
return false;
}
for (final key in a.keys) {
if (!_objectsEquals(a[key], b[key])) {
return false;
}
}
return true;
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _objectsEquals(Object? a, Object? b) {
if (identical(a, b)) {
return true;
}
if (a is num && b is num) {
return _numEquals(a, b);
} else if (_isEquatable(a) && _isEquatable(b)) {
return a == b;
} else if (a is Set && b is Set) {
return _setEquals(a, b);
} else if (a is Iterable && b is Iterable) {
return _iterableEquals(a, b);
} else if (a is Map && b is Map) {
return _mapEquals(a, b);
} else if (a?.runtimeType != b?.runtimeType) {
return false;
} else if (a != b) {
return false;
}
return true;
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
bool _isEquatable(Object? object) => object is Equatable;
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
int _combine(int hash, Object? object) {
if (object is Map) {
object.keys
.sorted((Object? a, Object? b) => a.hashCode - b.hashCode)
.forEach((Object? key) {
hash = hash ^ _combine(hash, [key, (object! as Map)[key]]);
});
return hash;
}
if (object is Set) {
object = object.sorted((Object? a, Object? b) => a.hashCode - b.hashCode);
}
if (object is Iterable) {
for (final value in object) {
hash = hash ^ _combine(hash, value);
}
return hash ^ object.length;
}
hash = 0x1fffffff & (hash + object.hashCode);
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
int _finish(int hash) {
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
String _mapPropsToString(Type runtimeType, List<Object?> props) =>
'$runtimeType(${props.map((prop) => prop.toString()).join(', ')})';
}
extension _IterableExtension<T> on Iterable<T> {
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
List<T> sorted(Comparator<T> compare) => [...this]..sort(compare);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment