diff --git a/lib/src/ilist/ilist.dart b/lib/src/ilist/ilist.dart index 2947e33..2c22d17 100644 --- a/lib/src/ilist/ilist.dart +++ b/lib/src/ilist/ilist.dart @@ -14,6 +14,90 @@ import "l_add.dart"; import "l_add_all.dart"; import "l_flat.dart"; +/// This is an [IList] which is always empty. +@immutable +class IListEmpty // ignore: must_be_immutable + extends IList { + /// Creates an empty list. In most cases, you should use `const IList.empty()`. + /// + /// IMPORTANT: You must always use the `const` keyword. + /// It's always wrong to use an `IListEmpty()` which is not constant. + @literal + const IListEmpty._([this.config = const ConfigList()]) + : super._gen(); + + @override + final ConfigList config; + + /// An empty list is always flushed, by definition. + @override + bool get isFlushed => true; + + /// Nothing happens when you flush a empty list, by definition. + @override + IListEmpty get flush => this; + + /// An empty list is always empty, by definition + @override + bool get isEmpty => true; + + /// An empty list is always empty, by definition + @override + bool get isNotEmpty => false; + + /// An empty list does not contain anything, by definition + @override + bool contains(covariant T? element) => false; + + /// An empty list is always of length `0`, by definition + @override + int get length => 0; + + /// An empty list has no first element, by definition + @override + Never get first => throw StateError("No element"); + + /// An empty list has no last element, by definition + @override + Never get last => throw StateError("No element"); + + /// An empty list has no single element, by definition + @override + Never get single => throw StateError("No element"); + + /// An empty list is always the reversed version of itself, by definition + @override + IListEmpty get reversed => this; + + /// An empty list is always the cleared version of itself, by definition + @override + IListEmpty clear() => this; + + @override + int get _counter => 0; + + @override + L get _l => LFlat.unsafe([]); + + /// Hash codes must be the same for objects that are equal to each other + /// according to operator ==. + @override + int? get _hashCode { + return isDeepEquals + ? hash2(const ListEquality().hash([]), config.hashCode) + : hash2(identityHashCode(_l), config.hashCode); + } + + @override + set _hashCode(int? value) {} + + @override + bool same(IList? other) => + (other != null) && + (other is IListEmpty) && + (config == other.config); +} + /// This is an [IList] which can be made constant. /// Note: Don't ever use it without the "const" keyword, because it will be unsafe. /// @@ -34,15 +118,6 @@ class IListConst // ignore: must_be_immutable [this.config = const ConfigList()]) : super._gen(); - /// Creates a empty constant list. - /// - /// IMPORTANT: You must always use the `const` keyword. - /// It's always wrong to use an `IListConst.empty()` which is not constant. - @literal - const IListConst.empty([this.config = const ConfigList()]) - : _list = const [], - super._gen(); - final List _list; @override @@ -200,9 +275,9 @@ abstract class IList // ignore: must_be_immutable IList.withConfig(iterable ?? const [], defaultConfig); /// Create an empty [IList]. - /// Use it with const: `const IList.empty()` (It's always an [IListConst]). + /// Use it with const: `const IList.empty()` (It's always an [IListEmpty]). @literal - const factory IList.empty() = IListConst.empty; + const factory IList.empty() = IListEmpty._; const IList._gen(); diff --git a/lib/src/imap/imap.dart b/lib/src/imap/imap.dart index a76fc55..3502424 100644 --- a/lib/src/imap/imap.dart +++ b/lib/src/imap/imap.dart @@ -15,6 +15,86 @@ import "m_add_all.dart"; import "m_flat.dart"; import "m_replace.dart"; +/// This is an [IMap] which is always empty. +@immutable +class IMapEmpty // ignore: must_be_immutable + extends IMap { + /// Creates an empty map. In most cases, you should use `const IMap.empty()`. + /// + /// IMPORTANT: You must always use the `const` keyword. + /// It's always wrong to use an `IMapEmpty()` which is not constant. + @literal + const IMapEmpty._([this.config = const ConfigMap()]) + : super._gen(); + + @override + final ConfigMap config; + + /// An empty map is always flushed, by definition. + @override + bool get isFlushed => true; + + /// Nothing happens when you flush a empty map, by definition. + @override + IMapEmpty get flush => this; + + /// An empty map is always empty, by definition + @override + bool get isEmpty => true; + + /// An empty map is always empty, by definition + @override + bool get isNotEmpty => false; + + /// An empty map does not contain anything, by definition + @override + bool contains(K key, V value) => false; + + /// An empty map does not contain anything, by definition + @override + bool containsKey(K? key) => false; + + /// An empty map does not contain anything, by definition + @override + bool containsValue(V? value) => false; + + /// An empty map does not contain anything, by definition + @override + bool containsEntry(MapEntry entry) => false; + + /// An empty map is always of length `0`, by definition + @override + int get length => 0; + + /// An empty map is always the cleared version of itself, by definition + @override + IMapEmpty clear() => this; + + @override + int get _counter => 0; + + @override + M get _m => MFlat.unsafe({}); + + /// Hash codes must be the same for objects that are equal to each other + /// according to operator ==. + @override + int? get _hashCode { + return isDeepEquals + ? hash2(const MapEquality().hash({}), config.hashCode) + : hash2(identityHashCode(_m), config.hashCode); + } + + @override + set _hashCode(int? value) {} + + @override + bool same(IMap? other) => + (other != null) && + (other is IMapEmpty) && + (config == other.config); +} + /// This is an [IMap] which can be made constant. /// Note: Don't ever use it without the "const" keyword, because it will be unsafe. /// @@ -35,15 +115,6 @@ class IMapConst // ignore: must_be_immutable [this.config = const ConfigMap()]) : super._gen(); - /// Creates a empty constant map. - /// - /// IMPORTANT: You must always use the `const` keyword. - /// It's always wrong to use an `IMapConst.empty()` which is not constant. - @literal - const IMapConst.empty([this.config = const ConfigMap()]) - : _map = const {}, - super._gen(); - final Map _map; @override @@ -182,9 +253,9 @@ abstract class IMap // ignore: must_be_immutable IMap.withConfig(map, defaultConfig); /// Create an empty [IMap]. - /// Use it with const: `const IMap.empty()` (It's always an [IMapConst]). + /// Use it with const: `const IMap.empty()` (It's always an [IMapEmpty]). @literal - const factory IMap.empty() = IMapConst.empty; + const factory IMap.empty() = IMapEmpty._; const IMap._gen(); diff --git a/lib/src/iset/iset.dart b/lib/src/iset/iset.dart index 18ca2ee..ba09ac2 100644 --- a/lib/src/iset/iset.dart +++ b/lib/src/iset/iset.dart @@ -14,6 +14,86 @@ import "s_add.dart"; import "s_add_all.dart"; import "s_flat.dart"; +/// This is an [ISet] which is always empty. +@immutable +class ISetEmpty // ignore: must_be_immutable + extends ISet { + /// Creates an empty set. In most cases, you should use `const ISet.empty()`. + /// + /// IMPORTANT: You must always use the `const` keyword. + /// It's always wrong to use an `ISetEmpty()` which is not constant. + @literal + const ISetEmpty._([this.config = const ConfigSet()]) + : super._gen(); + + @override + final ConfigSet config; + + /// An empty set is always flushed, by definition. + @override + bool get isFlushed => true; + + /// Nothing happens when you flush a empty set, by definition. + @override + ISetEmpty get flush => this; + + /// An empty set is always empty, by definition + @override + bool get isEmpty => true; + + /// An empty set is always empty, by definition + @override + bool get isNotEmpty => false; + + /// An empty set does not contain anything, by definition + @override + bool contains(covariant T? element) => false; + + /// An empty set is always of length `0`, by definition + @override + int get length => 0; + + /// An empty set has no first element, by definition + @override + Never get first => throw StateError("No element"); + + /// An empty set has no last element, by definition + @override + Never get last => throw StateError("No element"); + + /// An empty set has no single element, by definition + @override + Never get single => throw StateError("No element"); + + /// An empty set is always the cleared version of itself, by definition + @override + ISetEmpty clear() => this; + + @override + int get _counter => 0; + + @override + S get _s => SFlat.unsafe({}); + + /// Hash codes must be the same for objects that are equal to each other + /// according to operator ==. + @override + int? get _hashCode { + return isDeepEquals + ? hash2(const SetEquality().hash({}), config.hashCode) + : hash2(identityHashCode(_s), config.hashCode); + } + + @override + set _hashCode(int? value) {} + + @override + bool same(ISet? other) => + (other != null) && + (other is ISetEmpty) && + (config == other.config); +} + /// This is an [ISet] which can be made constant. /// Note: Don't ever use it without the "const" keyword, because it will be unsafe. /// @@ -37,15 +117,6 @@ class ISetConst // ignore: must_be_immutable [this.config = const ConfigSet()]) : super._gen(); - /// Creates a empty constant set. - /// - /// IMPORTANT: You must always use the `const` keyword. - /// It's always wrong to use an `ISetConst.empty()` which is not constant. - @literal - const ISetConst.empty([this.config = const ConfigSet()]) - : _set = const {}, - super._gen(); - final Set _set; @override @@ -197,9 +268,9 @@ abstract class ISet // ignore: must_be_immutable ISet.withConfig(iterable, defaultConfig); /// Create an empty [ISet]. - /// Use it with const: `const ISet.empty()` (It's always an [ISetConst]). + /// Use it with const: `const ISet.empty()` (It's always an [ISetEmpty]). @literal - const factory ISet.empty() = ISetConst.empty; + const factory ISet.empty() = ISetEmpty._; const ISet._gen(); diff --git a/test/ilist/ilist_empty_test.dart b/test/ilist/ilist_empty_test.dart new file mode 100644 index 0000000..af6fa62 --- /dev/null +++ b/test/ilist/ilist_empty_test.dart @@ -0,0 +1,105 @@ +// Developed by Marcelo Glasberg (2021) https://glasberg.dev and https://github.com/marcglasberg +// and Philippe Fanaro https://github.com/psygo +// For more info, see: https://pub.dartlang.org/packages/fast_immutable_collections +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:test/test.dart'; + +void main() { + setUp(() { + ImmutableCollection.resetAllConfigurations(); + ImmutableCollection.autoFlush = false; + }); + + test("Runtime Type", () { + expect(const IList.empty(), isA()); + expect(const IList.empty(), isA()); + expect(const IList.empty(), isA>()); + expect(const IList.empty(), isA>()); + + expect(const IList.empty(), isA()); + expect(const IList.empty(), isA()); + expect(const IList.empty(), isA>()); + expect(const IList.empty(), isA>()); + }); + + test("Make sure the IListEmpty can be modified and later iterated", () { + // LAddAll + IList list = const IList.empty(); + list = list.addAll(["a", "b", "c"]); + list.forEach((_) { }); + + // LAdd + list = const IList.empty(); + list = list.add("d"); + list.forEach((_) { }); + }); + + test("Make sure the internal list is List, and not List", () { + const l1 = IList.empty(); + expect(l1.runtimeType.toString(), 'IListEmpty'); + + const l2 = IListConst([1, 2, 3]); + expect(l2.runtimeType.toString(), 'IListConst'); + + final l3 = l1.addAll(l2); + expect(l3.runtimeType.toString(), 'IListImpl'); + + final result = l3.where((int i) => i == 2).toList(); + expect(result, [2]); + }); + + test(".same() is working properly", () { + expect(const IList.empty().same(const IList.empty()), isTrue); + }); + + test("equality", () { + // equalItems + expect(IList(["a", "b"]).equalItems(const IList.empty()), isFalse); + expect(const IListConst(["a", "b"]).equalItems(const IList.empty()), isFalse); + + expect(IList().equalItems(const IList.empty()), isTrue); + expect(const IListConst([]).equalItems(const IList.empty()), isTrue); + expect(const IList.empty().equalItems(const IList.empty()), isTrue); + expect(const IList.empty().equalItems(const IList.empty()), isTrue); + + + // equalItemsAndConfig + expect(IList(["a", "b"]).equalItemsAndConfig(const IList.empty()), isFalse); + expect(const IListConst(["a", "b"]).equalItemsAndConfig(const IList.empty()), isFalse); + + expect(IList().equalItemsAndConfig(const IList.empty()), isTrue); + expect(const IListConst([]).equalItemsAndConfig(const IList.empty()), isTrue); + expect(const IList.empty().equalItemsAndConfig(const IList.empty()), isTrue); + expect(const IList.empty().equalItemsAndConfig(const IList.empty()), isTrue); + }); + + test("isEmpty | isNotEmpty", () { + expect(const IList.empty().isEmpty, isTrue); + expect(const IList.empty().isNotEmpty, isFalse); + }); + + test("contains", () { + expect(const IList.empty().contains(Object()), isFalse); + expect(const IList.empty().contains(null), isFalse); + }); + + test("length", () { + expect(const IList.empty().length, 0); + }); + + test("fist | last | single", () { + expect(() => const IList.empty().first, throwsStateError); + expect(() => const IList.empty().last, throwsStateError); + expect(() => const IList.empty().single, throwsStateError); + }); + + test("reversed", () { + const list = IList.empty(); + expect(identical(list, list.reversed), isTrue); + }); + + test("clear()", () { + const list = IList.empty(); + expect(identical(list, list.clear()), isTrue); + }); +} diff --git a/test/imap/imap_empty_test.dart b/test/imap/imap_empty_test.dart new file mode 100644 index 0000000..9223bd4 --- /dev/null +++ b/test/imap/imap_empty_test.dart @@ -0,0 +1,110 @@ +// Developed by Marcelo Glasberg (2021) https://glasberg.dev and https://github.com/marcglasberg +// and Philippe Fanaro https://github.com/psygo +// For more info, see: https://pub.dartlang.org/packages/fast_immutable_collections +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:test/test.dart'; + +void main() { + setUp(() { + ImmutableCollection.resetAllConfigurations(); + ImmutableCollection.autoFlush = false; + }); + + test("Runtime Type", () { + expect(const IMap.empty(), isA()); + expect(const IMap.empty(), isA()); + expect(const IMap.empty(), isA>()); + expect(const IMap.empty(), isA>()); + + expect(const IMap.empty(), isA()); + expect(const IMap.empty(), isA()); + expect(const IMap.empty(), isA>()); + expect(const IMap.empty(), isA>()); + }); + + test("Make sure the IMapEmpty can be modified and later iterated", () { + // MAddAll + IMap map = const IMap.empty(); + map = map.addEntries([ + const MapEntry("a", 1), + const MapEntry("b", 2), + const MapEntry("c", 3) + ]); + map.forEach((_, __) { }); + + // MAdd + map = const IMap.empty(); + map = map.add("d", 4); + map.forEach((_, __) { }); + + // MReplace + map = const IMap.empty(); + map = map.add("d", 42); + map.forEach((_, __) { }); + }); + + test("Make sure the internal map is Map, and not Map", () { + const m1 = IMap.empty(); + expect(m1.runtimeType.toString(), 'IMapEmpty'); + + const m2 = IMapConst({'a': 1, 'b': 2, 'c': 3}); + expect(m2.runtimeType.toString(), 'IMapConst'); + + final m3 = m1.addAll(m2); + expect(m3.runtimeType.toString(), 'IMapImpl'); + + final result = m3.where((String key, int value) => value == 2); + expect(result, {'b': 2}.lock); + }); + + test(".same() is working properly", () { + expect(const IMap.empty().same(const IMap.empty()), isTrue); + }); + + test("equality", () { + // equalItems + expect(IMap({1: "a", 2: "b"}).equalItems(const IMap.empty().entries), isFalse); + expect(const IMapConst({1: "a", 2: "b"}).equalItems(const IMap.empty().entries), isFalse); + + expect(IMap().equalItems(const IMap.empty().entries), isTrue); + expect(const IMapConst({}).equalItems(const IMap.empty().entries), isTrue); + expect(const IMap.empty().equalItems(const IMap.empty().entries), isTrue); + + + // equalItemsAndConfig + expect(IMap({1: "a", 2: "b"}).equalItemsAndConfig(const IMap.empty()), isFalse); + expect(const IMapConst({1: "a", 2: "b"}).equalItemsAndConfig(const IMap.empty()), isFalse); + + expect(IMap().equalItemsAndConfig(const IMap.empty()), isTrue); + expect(const IMapConst({}).equalItemsAndConfig(const IMap.empty()), isTrue); + expect(const IMap.empty().equalItemsAndConfig(const IMap.empty()), isTrue); + }); + + test("isEmpty | isNotEmpty", () { + expect(const IMap.empty().isEmpty, isTrue); + expect(const IMap.empty().isNotEmpty, isFalse); + }); + + test("contains | -Key | -Value | -Entry", () { + expect(const IMap.empty().contains(Object(), Object()), isFalse); + expect(const IMap.empty().contains(null, null), isFalse); + + expect(const IMap.empty().containsKey(Object()), isFalse); + expect(const IMap.empty().containsKey(null), isFalse); + + expect(const IMap.empty().containsValue(Object()), isFalse); + expect(const IMap.empty().containsValue(null), isFalse); + + expect(const IMap.empty().containsEntry(const MapEntry(Object(), Object())), isFalse); + expect(const IMap.empty().containsEntry(const MapEntry(null, null)), isFalse); + }); + + test("length", () { + expect(const IMap.empty().length, 0); + }); + + test("clear()", () { + const list = IMap.empty(); + expect(identical(list, list.clear()), isTrue); + }); +} diff --git a/test/iset/iset_empty_test.dart b/test/iset/iset_empty_test.dart new file mode 100644 index 0000000..d68f17b --- /dev/null +++ b/test/iset/iset_empty_test.dart @@ -0,0 +1,98 @@ +// Developed by Marcelo Glasberg (2021) https://glasberg.dev and https://github.com/marcglasberg +// and Philippe Fanaro https://github.com/psygo +// For more info, see: https://pub.dartlang.org/packages/fast_immutable_collections +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:test/test.dart'; + +void main() { + setUp(() { + ImmutableCollection.resetAllConfigurations(); + ImmutableCollection.autoFlush = false; + }); + + test("Runtime Type", () { + expect(const ISet.empty(), isA()); + expect(const ISet.empty(), isA()); + expect(const ISet.empty(), isA>()); + expect(const ISet.empty(), isA>()); + + expect(const ISet.empty(), isA()); + expect(const ISet.empty(), isA()); + expect(const ISet.empty(), isA>()); + expect(const ISet.empty(), isA>()); + }); + + test("Make sure the ISetEmpty can be modified and later iterated", () { + // SAddAll + ISet set = const ISet.empty(); + set = set.addAll(["a", "b", "c"]); + set.forEach((_) { }); + + // SAdd + set = const ISet.empty(); + set = set.add("d"); + set.forEach((_) { }); + }); + + test("Make sure the internal set is Set, and not Set", () { + const s1 = ISet.empty(); + expect(s1.runtimeType.toString(), 'ISetEmpty'); + + const s2 = ISetConst({1, 2, 3}); + expect(s2.runtimeType.toString(), 'ISetConst'); + + final s3 = s1.addAll(s2); + expect(s3.runtimeType.toString(), 'ISetImpl'); + + final result = s3.where((int i) => i == 2).toSet(); + expect(result, [2]); + }); + + test(".same() is working properly", () { + expect(const ISet.empty().same(const ISet.empty()), isTrue); + }); + + test("equality", () { + // equalItems + expect(ISet({"a", "b"}).equalItems(const ISet.empty()), isFalse); + expect(const ISetConst({"a", "b"}).equalItems(const ISet.empty()), isFalse); + + expect(ISet().equalItems(const ISet.empty()), isTrue); + expect(const ISetConst({}).equalItems(const ISet.empty()), isTrue); + expect(const ISet.empty().equalItems(const ISet.empty()), isTrue); + + + // equalItemsAndConfig + expect(ISet({"a", "b"}).equalItemsAndConfig(const ISet.empty()), isFalse); + expect(const ISetConst({"a", "b"}).equalItemsAndConfig(const ISet.empty()), isFalse); + + expect(ISet().equalItemsAndConfig(const ISet.empty()), isTrue); + expect(const ISetConst({}).equalItemsAndConfig(const ISet.empty()), isTrue); + expect(const ISet.empty().equalItemsAndConfig(const ISet.empty()), isTrue); + }); + + test("isEmpty | isNotEmpty", () { + expect(const ISet.empty().isEmpty, isTrue); + expect(const ISet.empty().isNotEmpty, isFalse); + }); + + test("contains", () { + expect(const ISet.empty().contains(Object()), isFalse); + expect(const ISet.empty().contains(null), isFalse); + }); + + test("length", () { + expect(const ISet.empty().length, 0); + }); + + test("fist | last | single", () { + expect(() => const ISet.empty().first, throwsStateError); + expect(() => const ISet.empty().last, throwsStateError); + expect(() => const ISet.empty().single, throwsStateError); + }); + + test("clear()", () { + const list = ISet.empty(); + expect(identical(list, list.clear()), isTrue); + }); +}