Created
April 21, 2025 10:18
-
-
Save sunderee/fcf48375eb2435ddb054fbad30668ef2 to your computer and use it in GitHub Desktop.
Minimalist equatable implementation that does not use third-party dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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