diff --git a/lib/data_class_macro.dart b/lib/data_class_macro.dart index f30ef5d..98c2097 100644 --- a/lib/data_class_macro.dart +++ b/lib/data_class_macro.dart @@ -1,10 +1,8 @@ -import 'package:collection/collection.dart'; +/// Experimental support for data classes in Dart using package:macros. +library data_class_macro; export 'src/constructable_macro.dart' show Constructable; export 'src/copyable_macro.dart' show Copyable; export 'src/data_macro.dart' show Data; export 'src/equatable_macro.dart' show Equatable; export 'src/stringable_macro.dart' show Stringable; - -final deepEquals = const DeepCollectionEquality().equals; -const undefined = Object(); diff --git a/lib/src/_data_class_macro.dart b/lib/src/_data_class_macro.dart new file mode 100644 index 0000000..a8021ff --- /dev/null +++ b/lib/src/_data_class_macro.dart @@ -0,0 +1,48 @@ +import 'package:collection/collection.dart'; + +// Libraries used in augmented code. +final dartCore = Uri.parse('dart:core'); +final dataClassMacro = Uri.parse( + 'package:data_class_macro/src/_data_class_macro.dart', +); + +// Methods used in augmented code. +const undefined = Object(); +final deepEquals = const DeepCollectionEquality().equals; + +// TODO(felangel): use jenkins hash from `package:equatable` +// once https://github.com/felangel/equatable/pull/174 is published. +/// Generate a hash for the provided [fields]. +int hashAll(Iterable? fields) { + return _finish(fields == null ? 0 : fields.fold(0, _combine)); +} + +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); +} + +int _finish(int hash) { + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); +} diff --git a/lib/src/copyable_macro.dart b/lib/src/copyable_macro.dart index ae3ca4d..e2d0934 100644 --- a/lib/src/copyable_macro.dart +++ b/lib/src/copyable_macro.dart @@ -1,6 +1,7 @@ import 'dart:core'; import 'package:collection/collection.dart'; +import 'package:data_class_macro/src/_data_class_macro.dart'; import 'package:data_class_macro/src/macro_extensions.dart'; import 'package:macros/macros.dart'; diff --git a/lib/src/equatable_macro.dart b/lib/src/equatable_macro.dart index 75e5bd7..096299f 100644 --- a/lib/src/equatable_macro.dart +++ b/lib/src/equatable_macro.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:data_class_macro/src/_data_class_macro.dart'; import 'package:data_class_macro/src/macro_extensions.dart'; import 'package:macros/macros.dart'; @@ -129,9 +130,9 @@ macro class Equatable implements ClassDeclarationsMacro, ClassDefinitionMacro { ); if (hashCode == null) return; - final (hashCodeMethod, object, fields) = await ( + final (hashCodeMethod, hashAll, fields) = await ( builder.buildMethod(hashCode.identifier), - builder.codeFrom(dartCore, 'Object'), + builder.codeFrom(dataClassMacro, 'hashAll'), builder.allFieldsOf(clazz), ).wait; @@ -141,8 +142,8 @@ macro class Equatable implements ClassDeclarationsMacro, ClassDefinitionMacro { FunctionBodyCode.fromParts( [ '=> ', - object, - '.hashAll([', + hashAll, + '([', fieldNames.join(', '), ']);', ], diff --git a/lib/src/macro_extensions.dart b/lib/src/macro_extensions.dart index 61f3c05..726bd85 100644 --- a/lib/src/macro_extensions.dart +++ b/lib/src/macro_extensions.dart @@ -248,9 +248,3 @@ extension TypeAnnotationX on TypeAnnotation { return null; } } - -// Used libraries -final dartCore = Uri.parse('dart:core'); -final dataClassMacro = Uri.parse( - 'package:data_class_macro/data_class_macro.dart', -); diff --git a/lib/src/stringable_macro.dart b/lib/src/stringable_macro.dart index 11ab291..5877a33 100644 --- a/lib/src/stringable_macro.dart +++ b/lib/src/stringable_macro.dart @@ -1,6 +1,7 @@ import 'dart:core'; import 'package:collection/collection.dart'; +import 'package:data_class_macro/src/_data_class_macro.dart'; import 'package:data_class_macro/src/macro_extensions.dart'; import 'package:macros/macros.dart'; diff --git a/test/src/data_macro/empty_class_test.dart b/test/src/data_macro/empty_class_test.dart index 662e998..cf5cedf 100644 --- a/test/src/data_macro/empty_class_test.dart +++ b/test/src/data_macro/empty_class_test.dart @@ -19,7 +19,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptyClass().hashCode, equals(Object.hashAll([]))); + expect(EmptyClass().hashCode, equals(EmptyClass().hashCode)); }); test('toString is correct', () { diff --git a/test/src/data_macro/empty_nested_subclass_test.dart b/test/src/data_macro/empty_nested_subclass_test.dart index 0cca189..7bfc288 100644 --- a/test/src/data_macro/empty_nested_subclass_test.dart +++ b/test/src/data_macro/empty_nested_subclass_test.dart @@ -32,7 +32,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptyNestedSubClass().hashCode, equals(Object.hashAll([]))); + expect(EmptyNestedSubClass().hashCode, equals(EmptyNestedSubClass().hashCode)); }); test('toString is correct', () { diff --git a/test/src/data_macro/empty_subclass_test.dart b/test/src/data_macro/empty_subclass_test.dart index 92e649c..acb1c1d 100644 --- a/test/src/data_macro/empty_subclass_test.dart +++ b/test/src/data_macro/empty_subclass_test.dart @@ -24,7 +24,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptySubClass().hashCode, equals(Object.hashAll([]))); + expect(EmptySubClass().hashCode, equals(EmptySubClass().hashCode)); }); test('toString is correct', () { diff --git a/test/src/equatable_macro/empty_class_test.dart b/test/src/equatable_macro/empty_class_test.dart index 924ce8c..205723e 100644 --- a/test/src/equatable_macro/empty_class_test.dart +++ b/test/src/equatable_macro/empty_class_test.dart @@ -11,7 +11,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptyClass().hashCode, equals(Object.hashAll([]))); + expect(EmptyClass().hashCode, equals(EmptyClass().hashCode)); }); }); } diff --git a/test/src/equatable_macro/empty_nested_subclass_test.dart b/test/src/equatable_macro/empty_nested_subclass_test.dart index 26ced34..9a0e403 100644 --- a/test/src/equatable_macro/empty_nested_subclass_test.dart +++ b/test/src/equatable_macro/empty_nested_subclass_test.dart @@ -15,7 +15,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptyNestedSubClass().hashCode, equals(Object.hashAll([]))); + expect(EmptyNestedSubClass().hashCode, equals(EmptyNestedSubClass().hashCode)); }); }); } diff --git a/test/src/equatable_macro/empty_subclass_test.dart b/test/src/equatable_macro/empty_subclass_test.dart index bebabdd..702b678 100644 --- a/test/src/equatable_macro/empty_subclass_test.dart +++ b/test/src/equatable_macro/empty_subclass_test.dart @@ -15,7 +15,7 @@ void main() { }); test('hashCode is correct', () { - expect(EmptySubClass().hashCode, equals(Object.hashAll([]))); + expect(EmptySubClass().hashCode, equals(EmptySubClass().hashCode)); }); }); } diff --git a/test/src/equatable_macro/static_field_class_test.dart b/test/src/equatable_macro/static_field_class_test.dart index 359949e..5f089a2 100644 --- a/test/src/equatable_macro/static_field_class_test.dart +++ b/test/src/equatable_macro/static_field_class_test.dart @@ -13,7 +13,7 @@ void main() { }); test('hashCode is correct', () { - expect(StaticFieldClass().hashCode, equals(Object.hashAll([]))); + expect(StaticFieldClass().hashCode, equals(StaticFieldClass().hashCode)); }); }); }