From 1266306d2cc5a9f53e1ada495b41b273e7002213 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Fri, 7 Feb 2025 16:12:18 +0100 Subject: [PATCH] Updates, tweaks, and fixes for copy-on-write behavior. - Depends on SDK 3.0.0. - Removes null-soundness checks. - Changes pedantic lints to official lints. Fixes new lint warnings. - `BuiltList` and `BuiltSet` have "efficient" `.toList()` and `.toSet()` which reuses the backing list/set in a `CopyOnWrite{List,Set}`, and only makes a copy if that list is written to. The lazy operations returning a view on the type should not be based on the shared view. Similar issue exists for maps, but is harder to fix. --- analysis_options.yaml | 2 +- benchmark/lib/benchmark.dart | 4 +- benchmark/pubspec.yaml | 4 + example/pubspec.yaml | 5 +- lib/built_collection.dart | 11 +-- lib/src/internal/copy_on_write_list.dart | 75 ++++++++++------- lib/src/internal/copy_on_write_map.dart | 6 +- lib/src/internal/copy_on_write_set.dart | 60 +++++++------- lib/src/internal/test_helpers.dart | 16 ++-- lib/src/internal/type_helper.dart | 11 +++ lib/src/list.dart | 11 +-- lib/src/list/built_list.dart | 33 ++------ lib/src/list/list_builder.dart | 2 +- lib/src/list_multimap.dart | 5 +- .../list_multimap/built_list_multimap.dart | 12 +-- .../list_multimap/list_multimap_builder.dart | 6 +- lib/src/map.dart | 9 ++- lib/src/map/built_map.dart | 35 +++----- lib/src/map/map_builder.dart | 4 +- lib/src/set.dart | 11 +-- lib/src/set/built_set.dart | 36 +++------ lib/src/set/set_builder.dart | 4 +- lib/src/set_multimap.dart | 5 +- lib/src/set_multimap/built_set_multimap.dart | 12 +-- .../set_multimap/set_multimap_builder.dart | 6 +- pubspec.yaml | 6 +- test/internal/copy_on_write_list_test.dart | 50 ++++++++++++ test/internal/copy_on_write_set_test.dart | 42 ++++++++++ test/list/built_list_test.dart | 80 +++++++++++++++++-- test/list/list_builder_test.dart | 5 +- .../built_list_multimap_test.dart | 26 +++--- .../list_multimap_builder_test.dart | 7 +- test/map/built_map_test.dart | 12 +-- test/map/map_builder_test.dart | 14 ++-- test/set/built_set_test.dart | 70 +++++++++++++--- test/set/set_builder_test.dart | 6 +- .../set_multimap/built_set_multimap_test.dart | 26 +++--- .../set_multimap_builder_test.dart | 7 +- 38 files changed, 479 insertions(+), 257 deletions(-) create mode 100644 lib/src/internal/type_helper.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 108d105..572dd23 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml diff --git a/benchmark/lib/benchmark.dart b/benchmark/lib/benchmark.dart index 58a3f67..dabb290 100644 --- a/benchmark/lib/benchmark.dart +++ b/benchmark/lib/benchmark.dart @@ -32,7 +32,7 @@ class BuiltCollectionBenchmark { Iterable list = List.generate(1000, (x) => x); var fastLazyIterable = list.map((x) => x + 1); var slowLazyIterable = list.map(_shortDelay); - var builderFactory = () => ListBuilder()..addAll(list); + builderFactory() => ListBuilder()..addAll(list); _benchmark('ListBuilder.$name,list', function, builderFactory, list); _benchmark('ListBuilder.$name,fast lazy iterable', function, @@ -50,7 +50,7 @@ class BuiltCollectionBenchmark { Iterable list = List.generate(1000, (x) => x); var fastLazyIterable = list.map((x) => x + 1); var slowLazyIterable = list.map(_shortDelay); - var builderFactory = () => SetBuilder(); + builderFactory() => SetBuilder(); _benchmark('SetBuilder.$name,list', function, builderFactory, list); _benchmark('SetBuilder.$name,fast lazy iterable', function, diff --git a/benchmark/pubspec.yaml b/benchmark/pubspec.yaml index 4355ce5..f134bc5 100644 --- a/benchmark/pubspec.yaml +++ b/benchmark/pubspec.yaml @@ -8,6 +8,10 @@ publish_to: none environment: sdk: '>=2.12.0 <4.0.0' +dependencies: + built_collection: + path: .. + dev_dependencies: build_runner: any build_test: any diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 23c9158..43d00b9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,9 +4,12 @@ description: > Example, not for publishing. homepage: https://github.com/google/built_collection.dart +# This package is not intended for consumption on pub.dev. DO NOT publish. +publish_to: none + environment: sdk: '>=2.12.0 <4.0.0' -dependency_overrides: +dependencies: built_collection: path: .. diff --git a/lib/built_collection.dart b/lib/built_collection.dart index 09104d4..20c4a36 100644 --- a/lib/built_collection.dart +++ b/lib/built_collection.dart @@ -104,9 +104,10 @@ /// a copy, but return a copy-on-write wrapper. So, Built Collections can be /// efficiently and easily used with code that needs core SDK collections but /// does not mutate them. +library; -export 'src/list.dart' hide OverriddenHashcodeBuiltList; -export 'src/list_multimap.dart' hide OverriddenHashcodeBuiltListMultimap; -export 'src/map.dart' hide OverriddenHashcodeBuiltMap; -export 'src/set.dart' hide OverriddenHashcodeBuiltSet; -export 'src/set_multimap.dart' hide OverriddenHashcodeBuiltSetMultimap; +export 'src/list.dart' hide OverriddenHashCodeBuiltList; +export 'src/list_multimap.dart' hide OverriddenHashCodeBuiltListMultimap; +export 'src/map.dart' hide OverriddenHashCodeBuiltMap; +export 'src/set.dart' hide OverriddenHashCodeBuiltSet; +export 'src/set_multimap.dart' hide OverriddenHashCodeBuiltSetMultimap; diff --git a/lib/src/internal/copy_on_write_list.dart b/lib/src/internal/copy_on_write_list.dart index a41f28d..cab848a 100644 --- a/lib/src/internal/copy_on_write_list.dart +++ b/lib/src/internal/copy_on_write_list.dart @@ -2,16 +2,20 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'dart:collection' show ListBase; import 'dart:math'; -class CopyOnWriteList implements List { +class CopyOnWriteList extends ListBase { bool _copyBeforeWrite; final bool _growable; List _list; CopyOnWriteList(this._list, this._growable) : _copyBeforeWrite = true; - // Read-only methods: just forward. + // Read-only methods returning values: just forward. + // Methods returning a lazy view use default list implementation + // until first write, to avoid capturing a `_list` which may be replaced + // on a write. @override int get length => _list.length; @@ -26,10 +30,10 @@ class CopyOnWriteList implements List { bool any(bool Function(E) test) => _list.any(test); @override - Map asMap() => _list.asMap(); + Map asMap() => _copyBeforeWrite ? super.asMap() : _list.asMap(); @override - List cast() => CopyOnWriteList(_list.cast(), _growable); + List cast() => _copyBeforeWrite ? super.cast() : _list.cast(); @override bool contains(Object? element) => _list.contains(element); @@ -41,7 +45,8 @@ class CopyOnWriteList implements List { bool every(bool Function(E) test) => _list.every(test); @override - Iterable expand(Iterable Function(E) f) => _list.expand(f); + Iterable expand(Iterable Function(E) f) => + _copyBeforeWrite ? super.expand(f) : _list.expand(f); @override E get first => _list.first; @@ -55,16 +60,20 @@ class CopyOnWriteList implements List { _list.fold(initialValue, combine); @override - Iterable followedBy(Iterable other) => _list.followedBy(other); + Iterable followedBy(Iterable other) => + _copyBeforeWrite ? super.followedBy(other) : _list.followedBy(other); @override - void forEach(void Function(E) f) => _list.forEach(f); + void forEach(void Function(E) action) => _list.forEach(action); @override - Iterable getRange(int start, int end) => _list.getRange(start, end); + Iterable getRange(int start, int end) => _copyBeforeWrite + ? super.getRange(start, end) + : _list.getRange(start, end); @override - int indexOf(E element, [int start = 0]) => _list.indexOf(element, start); + int indexOf(covariant E element, [int start = 0]) => + _list.indexOf(element, start); @override int indexWhere(bool Function(E) test, [int start = 0]) => @@ -77,7 +86,8 @@ class CopyOnWriteList implements List { bool get isNotEmpty => _list.isNotEmpty; @override - Iterator get iterator => _list.iterator; + Iterator get iterator => + _copyBeforeWrite ? super.iterator : _list.iterator; @override String join([String separator = '']) => _list.join(separator); @@ -86,7 +96,8 @@ class CopyOnWriteList implements List { E get last => _list.last; @override - int lastIndexOf(E element, [int? start]) => _list.lastIndexOf(element, start); + int lastIndexOf(covariant E element, [int? start]) => + _list.lastIndexOf(element, start); @override int lastIndexWhere(bool Function(E) test, [int? start]) => @@ -97,13 +108,15 @@ class CopyOnWriteList implements List { _list.lastWhere(test, orElse: orElse); @override - Iterable map(T Function(E) f) => _list.map(f); + Iterable map(T Function(E) f) => + _copyBeforeWrite ? super.map(f) : _list.map(f); @override E reduce(E Function(E, E) combine) => _list.reduce(combine); @override - Iterable get reversed => _list.reversed; + Iterable get reversed => + _copyBeforeWrite ? super.reversed : _list.reversed; @override E get single => _list.single; @@ -113,19 +126,23 @@ class CopyOnWriteList implements List { _list.singleWhere(test, orElse: orElse); @override - Iterable skip(int count) => _list.skip(count); + Iterable skip(int count) => + _copyBeforeWrite ? super.skip(count) : _list.skip(count); @override - Iterable skipWhile(bool Function(E) test) => _list.skipWhile(test); + Iterable skipWhile(bool Function(E) test) => + _copyBeforeWrite ? super.skipWhile(test) : _list.skipWhile(test); @override List sublist(int start, [int? end]) => _list.sublist(start, end); @override - Iterable take(int count) => _list.take(count); + Iterable take(int count) => + _copyBeforeWrite ? super.take(count) : _list.take(count); @override - Iterable takeWhile(bool Function(E) test) => _list.takeWhile(test); + Iterable takeWhile(bool Function(E) test) => + _copyBeforeWrite ? super.takeWhile(test) : _list.takeWhile(test); @override List toList({bool growable = true}) => _list.toList(growable: growable); @@ -134,10 +151,12 @@ class CopyOnWriteList implements List { Set toSet() => _list.toSet(); @override - Iterable where(bool Function(E) test) => _list.where(test); + Iterable where(bool Function(E) test) => + _copyBeforeWrite ? super.where(test) : _list.where(test); @override - Iterable whereType() => _list.whereType(); + Iterable whereType() => + _copyBeforeWrite ? super.whereType() : _list.whereType(); // Mutating methods: copy first if needed. @@ -166,9 +185,9 @@ class CopyOnWriteList implements List { } @override - void add(E value) { + void add(E element) { _maybeCopyBeforeWrite(); - _list.add(value); + _list.add(element); } @override @@ -214,9 +233,9 @@ class CopyOnWriteList implements List { } @override - bool remove(Object? value) { + bool remove(Object? element) { _maybeCopyBeforeWrite(); - return _list.remove(value); + return _list.remove(element); } @override @@ -256,15 +275,15 @@ class CopyOnWriteList implements List { } @override - void fillRange(int start, int end, [E? fillValue]) { + void fillRange(int start, int end, [E? fill]) { _maybeCopyBeforeWrite(); - _list.fillRange(start, end, fillValue); + _list.fillRange(start, end, fill); } @override - void replaceRange(int start, int end, Iterable iterable) { + void replaceRange(int start, int end, Iterable newContents) { _maybeCopyBeforeWrite(); - _list.replaceRange(start, end, iterable); + _list.replaceRange(start, end, newContents); } @override @@ -275,6 +294,6 @@ class CopyOnWriteList implements List { void _maybeCopyBeforeWrite() { if (!_copyBeforeWrite) return; _copyBeforeWrite = false; - _list = List.from(_list, growable: _growable); + _list = List.of(_list, growable: _growable); } } diff --git a/lib/src/internal/copy_on_write_map.dart b/lib/src/internal/copy_on_write_map.dart index f9e4c5e..e868753 100644 --- a/lib/src/internal/copy_on_write_map.dart +++ b/lib/src/internal/copy_on_write_map.dart @@ -2,10 +2,8 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -typedef _MapFactory = Map Function(); - class CopyOnWriteMap implements Map { - final _MapFactory? _mapFactory; + final Map Function()? _mapFactory; bool _copyBeforeWrite; Map _map; @@ -115,6 +113,6 @@ class CopyOnWriteMap implements Map { _copyBeforeWrite = false; _map = _mapFactory != null ? (_mapFactory!()..addAll(_map)) - : Map.from(_map); + : Map.of(_map); } } diff --git a/lib/src/internal/copy_on_write_set.dart b/lib/src/internal/copy_on_write_set.dart index 42f7aaf..169b290 100644 --- a/lib/src/internal/copy_on_write_set.dart +++ b/lib/src/internal/copy_on_write_set.dart @@ -4,7 +4,7 @@ typedef _SetFactory = Set Function(); -class CopyOnWriteSet implements Set { +class CopyOnWriteSet extends Iterable implements Set { final _SetFactory? _setFactory; bool _copyBeforeWrite; Set _set; @@ -35,7 +35,7 @@ class CopyOnWriteSet implements Set { bool any(bool Function(E) test) => _set.any(test); @override - Set cast() => CopyOnWriteSet(_set.cast()); + Set cast() => Set.castFrom(this); @override bool contains(Object? element) => _set.contains(element); @@ -47,7 +47,8 @@ class CopyOnWriteSet implements Set { bool every(bool Function(E) test) => _set.every(test); @override - Iterable expand(Iterable Function(E) f) => _set.expand(f); + Iterable expand(Iterable Function(E) toElements) => + super.expand(toElements); @override E get first => _set.first; @@ -61,10 +62,7 @@ class CopyOnWriteSet implements Set { _set.fold(initialValue, combine); @override - Iterable followedBy(Iterable other) => _set.followedBy(other); - - @override - void forEach(void Function(E) f) => _set.forEach(f); + void forEach(void Function(E) action) => _set.forEach(action); @override bool get isEmpty => _set.isEmpty; @@ -73,7 +71,9 @@ class CopyOnWriteSet implements Set { bool get isNotEmpty => _set.isNotEmpty; @override - Iterator get iterator => _set.iterator; + Iterator get iterator => _copyBeforeWrite + ? _CopyOnWriteSetIterator(this, _set.iterator) + : _set.iterator; @override String join([String separator = '']) => _set.join(separator); @@ -86,7 +86,7 @@ class CopyOnWriteSet implements Set { _set.lastWhere(test, orElse: orElse); @override - Iterable map(T Function(E) f) => _set.map(f); + Iterable map(T Function(E) toElement) => super.map(toElement); @override E reduce(E Function(E, E) combine) => _set.reduce(combine); @@ -98,18 +98,6 @@ class CopyOnWriteSet implements Set { E singleWhere(bool Function(E) test, {E Function()? orElse}) => _set.singleWhere(test, orElse: orElse); - @override - Iterable skip(int count) => _set.skip(count); - - @override - Iterable skipWhile(bool Function(E) test) => _set.skipWhile(test); - - @override - Iterable take(int count) => _set.take(count); - - @override - Iterable takeWhile(bool Function(E) test) => _set.takeWhile(test); - @override List toList({bool growable = true}) => _set.toList(growable: growable); @@ -117,10 +105,7 @@ class CopyOnWriteSet implements Set { Set toSet() => _set.toSet(); @override - Iterable where(bool Function(E) test) => _set.where(test); - - @override - Iterable whereType() => _set.whereType(); + Iterable whereType() => super.whereType(); // Mutating methods: copy first if needed. @@ -180,8 +165,27 @@ class CopyOnWriteSet implements Set { void _maybeCopyBeforeWrite() { if (!_copyBeforeWrite) return; _copyBeforeWrite = false; - _set = _setFactory != null - ? (_setFactory!()..addAll(_set)) - : Set.from(_set); + _set = + _setFactory != null ? (_setFactory!()..addAll(_set)) : Set.of(_set); + } +} + +/// Iterator wrapper on the [_wrapper._set.iterator]. +/// +/// Throws concurrent modification if a copy-on-write +/// copy was made (assuming that a write was also made). +class _CopyOnWriteSetIterator implements Iterator { + final Iterator _source; + final CopyOnWriteSet _wrapper; + _CopyOnWriteSetIterator(this._wrapper, this._source); + @override + bool moveNext() { + if (_wrapper._copyBeforeWrite) { + return _source.moveNext(); + } + throw ConcurrentModificationError(_wrapper); } + + @override + E get current => _source.current; } diff --git a/lib/src/internal/test_helpers.dart b/lib/src/internal/test_helpers.dart index c05cfa0..4eb3e65 100644 --- a/lib/src/internal/test_helpers.dart +++ b/lib/src/internal/test_helpers.dart @@ -12,35 +12,35 @@ import '../set_multimap.dart'; class BuiltCollectionTestHelpers { static BuiltList overridenHashcodeBuiltList( Iterable iterable, int hashCode) => - OverriddenHashcodeBuiltList(iterable, hashCode); + OverriddenHashCodeBuiltList(iterable, hashCode); static BuiltListMultimap overridenHashcodeBuiltListMultimap( Object map, int hashCode) => - OverriddenHashcodeBuiltListMultimap(map, hashCode); + OverriddenHashCodeBuiltListMultimap(map, hashCode); static BuiltListMultimap overridenHashcodeBuiltListMultimapWithStringKeys( Object map, int hashCode) => - OverriddenHashcodeBuiltListMultimap(map, hashCode); + OverriddenHashCodeBuiltListMultimap(map, hashCode); static BuiltMap overridenHashcodeBuiltMap( Object map, int hashCode) => - OverriddenHashcodeBuiltMap(map, hashCode); + OverriddenHashCodeBuiltMap(map, hashCode); static BuiltMap overridenHashcodeBuiltMapWithStringKeys( Object map, int hashCode) => - OverriddenHashcodeBuiltMap(map, hashCode); + OverriddenHashCodeBuiltMap(map, hashCode); static BuiltSet overridenHashcodeBuiltSet( Iterable iterable, int hashCode) => - OverriddenHashcodeBuiltSet(iterable, hashCode); + OverriddenHashCodeBuiltSet(iterable, hashCode); static BuiltSetMultimap overridenHashcodeBuiltSetMultimap( Object map, int hashCode) => - OverriddenHashcodeBuiltSetMultimap(map, hashCode); + OverriddenHashCodeBuiltSetMultimap(map, hashCode); static BuiltSetMultimap overridenHashcodeBuiltSetMultimapWithStringKeys( Object map, int hashCode) => - OverriddenHashcodeBuiltSetMultimap(map, hashCode); + OverriddenHashCodeBuiltSetMultimap(map, hashCode); } diff --git a/lib/src/internal/type_helper.dart b/lib/src/internal/type_helper.dart new file mode 100644 index 0000000..f727687 --- /dev/null +++ b/lib/src/internal/type_helper.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2025, Google Inc. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/// Only used to test if a type parameter [T] is a subtype of some other type. +/// +/// The only language-level operation for that is `is` which requires an +/// object. +class TypeHelper {} + +class TypeHelper2 {} diff --git a/lib/src/list.dart b/lib/src/list.dart index fd28272..31f91f5 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -11,18 +11,19 @@ import 'internal/copy_on_write_list.dart'; import 'internal/hash.dart'; import 'internal/iterables.dart'; import 'internal/null_safety.dart'; +import 'internal/type_helper.dart'; part 'list/built_list.dart'; part 'list/list_builder.dart'; // Internal only, for testing. -class OverriddenHashcodeBuiltList extends _BuiltList { - final int _overridenHashCode; +class OverriddenHashCodeBuiltList extends _BuiltList { + final int _overriddenHashCode; - OverriddenHashcodeBuiltList(Iterable iterable, this._overridenHashCode) - : super.from(iterable); + OverriddenHashCodeBuiltList(super.iterable, this._overriddenHashCode) + : super.from(); @override // ignore: hash_and_equals - int get hashCode => _overridenHashCode; + int get hashCode => _overriddenHashCode; } diff --git a/lib/src/list/built_list.dart b/lib/src/list/built_list.dart index 2cf1d27..7bf3d5c 100644 --- a/lib/src/list/built_list.dart +++ b/lib/src/list/built_list.dart @@ -19,7 +19,7 @@ abstract class BuiltList implements Iterable, BuiltIterable { /// Instantiates with elements from an [Iterable]. factory BuiltList([Iterable iterable = const []]) { - if (iterable is _BuiltList && iterable.hasExactElementType(E)) { + if (iterable is _BuiltList && iterable.hasEquivalentElementType()) { return iterable as BuiltList; } else { return _BuiltList.from(iterable); @@ -30,7 +30,7 @@ abstract class BuiltList implements Iterable, BuiltIterable { /// /// `E` must not be `dynamic`. factory BuiltList.of(Iterable iterable) { - if (iterable is _BuiltList && iterable.hasExactElementType(E)) { + if (iterable is _BuiltList && iterable.hasEquivalentElementType()) { return iterable; } else { return _BuiltList.of(iterable); @@ -61,10 +61,7 @@ abstract class BuiltList implements Iterable, BuiltIterable { /// A `BuiltList` is only equal to another `BuiltList` with equal elements in /// the same order. Then, the `hashCode` is guaranteed to be the same. @override - int get hashCode { - _hashCode ??= hashObjects(_list); - return _hashCode!; - } + int get hashCode => _hashCode ??= hashObjects(_list); /// Deep equality. /// @@ -239,30 +236,16 @@ abstract class BuiltList implements Iterable, BuiltIterable { /// Default implementation of the public [BuiltList] interface. class _BuiltList extends BuiltList { - _BuiltList.withSafeList(List list) : super._(list); + _BuiltList.withSafeList(super.list) : super._(); _BuiltList.from([Iterable iterable = const []]) - : super._(List.from(iterable, growable: false)) { - _maybeCheckForNull(); - } + : super._(List.from(iterable, growable: false)); _BuiltList.of(Iterable iterable) - : super._(List.from(iterable, growable: false)) { - _maybeCheckForNull(); - } - - bool get _needsNullCheck => !isSoundMode && null is! E; - - void _maybeCheckForNull() { - if (!_needsNullCheck) return; - for (var element in _list) { - if (identical(element, null)) { - throw ArgumentError('iterable contained invalid element: null'); - } - } - } + : super._(List.from(iterable, growable: false)); - bool hasExactElementType(Type type) => E == type; + bool hasEquivalentElementType() => + this is _BuiltList && TypeHelper() is TypeHelper; } /// Extensions for [BuiltList] on [List]. diff --git a/lib/src/list/list_builder.dart b/lib/src/list/list_builder.dart index 36b7563..374c843 100644 --- a/lib/src/list/list_builder.dart +++ b/lib/src/list/list_builder.dart @@ -284,7 +284,7 @@ class ListBuilder { List get _safeList { if (_listOwner != null) { - _setSafeList(List.from(_list, growable: true)); + _setSafeList(List.of(_list, growable: true)); } return _list; } diff --git a/lib/src/list_multimap.dart b/lib/src/list_multimap.dart index 4cf9fbd..89f8d7d 100644 --- a/lib/src/list_multimap.dart +++ b/lib/src/list_multimap.dart @@ -6,17 +6,18 @@ import 'internal/copy_on_write_map.dart'; import 'internal/hash.dart'; import 'internal/null_safety.dart'; +import 'internal/type_helper.dart'; import 'list.dart'; part 'list_multimap/built_list_multimap.dart'; part 'list_multimap/list_multimap_builder.dart'; // Internal only, for testing. -class OverriddenHashcodeBuiltListMultimap +class OverriddenHashCodeBuiltListMultimap extends _BuiltListMultimap { final int _overridenHashCode; - OverriddenHashcodeBuiltListMultimap(map, this._overridenHashCode) + OverriddenHashCodeBuiltListMultimap(map, this._overridenHashCode) : super.copy(map.keys, (k) => map[k]); @override diff --git a/lib/src/list_multimap/built_list_multimap.dart b/lib/src/list_multimap/built_list_multimap.dart index 3092ed5..cfdb396 100644 --- a/lib/src/list_multimap/built_list_multimap.dart +++ b/lib/src/list_multimap/built_list_multimap.dart @@ -29,7 +29,7 @@ abstract class BuiltListMultimap { /// [BuiltListMultimap]. factory BuiltListMultimap([multimap = const {}]) { if (multimap is _BuiltListMultimap && - multimap.hasExactKeyAndValueTypes(K, V)) { + multimap.hasEquivalentKeyAndValueTypes()) { return multimap as BuiltListMultimap; } else if (multimap is Map) { return _BuiltListMultimap.copy(multimap.keys, (k) => multimap[k]); @@ -121,9 +121,9 @@ abstract class BuiltListMultimap { /// As [ListMultimap.forEach]. void forEach(void Function(K, V) f) { _map.forEach((key, values) { - values.forEach((value) { + for (var value in values) { f(key, value); - }); + } }); } @@ -164,7 +164,7 @@ abstract class BuiltListMultimap { /// Default implementation of the public [BuiltListMultimap] interface. class _BuiltListMultimap extends BuiltListMultimap { - _BuiltListMultimap.withSafeMap(Map> map) : super._(map); + _BuiltListMultimap.withSafeMap(super.map) : super._(); _BuiltListMultimap.copy(Iterable keys, Function lookup) : super._(>{}) { @@ -177,5 +177,7 @@ class _BuiltListMultimap extends BuiltListMultimap { } } - bool hasExactKeyAndValueTypes(Type key, Type value) => K == key && V == value; + bool hasEquivalentKeyAndValueTypes() => + this is _BuiltListMultimap && + TypeHelper2() is TypeHelper2; } diff --git a/lib/src/list_multimap/list_multimap_builder.dart b/lib/src/list_multimap/list_multimap_builder.dart index 2c4b23b..076298d 100644 --- a/lib/src/list_multimap/list_multimap_builder.dart +++ b/lib/src/list_multimap/list_multimap_builder.dart @@ -111,9 +111,9 @@ class ListMultimapBuilder { /// As [ListMultimap.addValues]. void addValues(K key, Iterable values) { // _disown is called in add. - values.forEach((value) { + for (var value in values) { add(key, value); - }); + } } /// As [ListMultimap.remove]. @@ -171,7 +171,7 @@ class ListMultimapBuilder { void _makeWriteableCopy() { if (_builtMapOwner != null) { - _builtMap = Map>.from(_builtMap); + _builtMap = Map>.of(_builtMap); _builtMapOwner = null; } } diff --git a/lib/src/map.dart b/lib/src/map.dart index ef60ba6..73b756d 100644 --- a/lib/src/map.dart +++ b/lib/src/map.dart @@ -5,18 +5,19 @@ import 'internal/copy_on_write_map.dart'; import 'internal/hash.dart'; import 'internal/null_safety.dart'; +import 'internal/type_helper.dart'; part 'map/built_map.dart'; part 'map/map_builder.dart'; // Internal only, for testing. -class OverriddenHashcodeBuiltMap extends _BuiltMap { - final int _overrridenHashCode; +class OverriddenHashCodeBuiltMap extends _BuiltMap { + final int _overriddenHashCode; - OverriddenHashcodeBuiltMap(map, this._overrridenHashCode) + OverriddenHashCodeBuiltMap(map, this._overriddenHashCode) : super.copyAndCheckTypes(map.keys, (k) => map[k]); @override // ignore: hash_and_equals - int get hashCode => _overrridenHashCode; + int get hashCode => _overriddenHashCode; } diff --git a/lib/src/map/built_map.dart b/lib/src/map/built_map.dart index 99a3ec6..5ce198a 100644 --- a/lib/src/map/built_map.dart +++ b/lib/src/map/built_map.dart @@ -4,8 +4,6 @@ part of '../map.dart'; -typedef _MapFactory = Map Function(); - /// The Built Collection [Map]. /// /// It implements the non-mutating part of the [Map] interface. Iteration over @@ -16,7 +14,7 @@ typedef _MapFactory = Map Function(); /// [Built Collection library documentation](#built_collection/built_collection) /// for the general properties of Built Collections. abstract class BuiltMap { - final _MapFactory? _mapFactory; + final Map Function()? _mapFactory; final Map _map; // Cached. @@ -26,7 +24,7 @@ abstract class BuiltMap { /// Instantiates with elements from a [Map] or [BuiltMap]. factory BuiltMap([map = const {}]) { - if (map is _BuiltMap && map.hasExactKeyAndValueTypes(K, V)) { + if (map is _BuiltMap && map.hasEquivalentKeyAndValueTypes()) { return map as BuiltMap; } else if (map is Map || map is BuiltMap) { return _BuiltMap.copyAndCheckTypes(map.keys, (k) => map[k]); @@ -44,7 +42,7 @@ abstract class BuiltMap { /// /// `K` and `V` are inferred from `map`. factory BuiltMap.of(Map map) { - return _BuiltMap.copyAndCheckForNull(map.keys, (k) => map[k] as V); + return _BuiltMap.copy(map.keys, (k) => map[k] as V); } /// Creates a [MapBuilder], applies updates to it, and builds. @@ -86,10 +84,12 @@ abstract class BuiltMap { _hashCode ??= hashObjects(_map.keys .map((key) => hash2(key.hashCode, _map[key].hashCode)) .toList(growable: false) - ..sort()); + ..sort(_compareInt)); return _hashCode!; } + static int _compareInt(int a, int b) => a - b; + /// Deep equality. /// /// A `BuiltMap` is only equal to another `BuiltMap` with equal key/value @@ -161,8 +161,7 @@ abstract class BuiltMap { /// Default implementation of the public [BuiltMap] interface. class _BuiltMap extends BuiltMap { - _BuiltMap.withSafeMap(_MapFactory? mapFactory, Map map) - : super._(mapFactory, map); + _BuiltMap.withSafeMap(super.mapFactory, super.map) : super._(); _BuiltMap.copyAndCheckTypes(Iterable keys, Function lookup) : super._(null, {}) { @@ -180,23 +179,11 @@ class _BuiltMap extends BuiltMap { } } - _BuiltMap.copyAndCheckForNull(Iterable keys, V Function(K) lookup) - : super._(null, {}) { - var checkKeys = !isSoundMode && null is! K; - var checkValues = !isSoundMode && null is! V; - for (var key in keys) { - if (checkKeys && identical(key, null)) { - throw ArgumentError('map contained invalid key: null'); - } - var value = lookup(key); - if (checkValues && value == null) { - throw ArgumentError('map contained invalid value: null'); - } - _map[key] = value; - } - } + _BuiltMap.copy(Iterable keys, V Function(K) lookup) + : super._(null, {for (var key in keys) key: lookup(key)}); - bool hasExactKeyAndValueTypes(Type key, Type value) => K == key && V == value; + bool hasEquivalentKeyAndValueTypes() => + this is BuiltMap && TypeHelper2() is TypeHelper2; } /// Extensions for [BuiltMap] on [Map]. diff --git a/lib/src/map/map_builder.dart b/lib/src/map/map_builder.dart index d44948d..9618397 100644 --- a/lib/src/map/map_builder.dart +++ b/lib/src/map/map_builder.dart @@ -13,7 +13,7 @@ part of '../map.dart'; /// for the general properties of Built Collections. class MapBuilder { /// Used by [_createMap] to instantiate [_map]. The default value is `null`. - _MapFactory? _mapFactory; + Map Function()? _mapFactory; late Map _map; _BuiltMap? _mapOwner; @@ -73,7 +73,7 @@ class MapBuilder { /// instantiate and return a new object. /// /// Use [withDefaultBase] to reset `base` to the default value. - void withBase(_MapFactory base) { + void withBase(Map Function() base) { ArgumentError.checkNotNull(base, 'base'); _mapFactory = base; _setSafeMap(_createMap()..addAll(_map)); diff --git a/lib/src/set.dart b/lib/src/set.dart index 8b4bf38..3a7d831 100644 --- a/lib/src/set.dart +++ b/lib/src/set.dart @@ -9,19 +9,20 @@ import 'internal/hash.dart'; import 'internal/copy_on_write_set.dart'; import 'internal/iterables.dart'; import 'internal/null_safety.dart'; +import 'internal/type_helper.dart'; import 'internal/unmodifiable_set.dart'; part 'set/built_set.dart'; part 'set/set_builder.dart'; // Internal only, for testing. -class OverriddenHashcodeBuiltSet extends _BuiltSet { - final int _overridenHashCode; +class OverriddenHashCodeBuiltSet extends _BuiltSet { + final int _overriddenHashCode; - OverriddenHashcodeBuiltSet(Iterable iterable, this._overridenHashCode) - : super.from(iterable); + OverriddenHashCodeBuiltSet(super.iterable, this._overriddenHashCode) + : super.from(); @override // ignore: hash_and_equals - int get hashCode => _overridenHashCode; + int get hashCode => _overriddenHashCode; } diff --git a/lib/src/set/built_set.dart b/lib/src/set/built_set.dart index 7038902..39f7b28 100644 --- a/lib/src/set/built_set.dart +++ b/lib/src/set/built_set.dart @@ -4,8 +4,6 @@ part of '../set.dart'; -typedef _SetFactory = Set Function(); - /// The Built Collection [Set]. /// /// It implements [Iterable] and the non-mutating part of the [Set] interface. @@ -16,16 +14,17 @@ typedef _SetFactory = Set Function(); /// [Built Collection library documentation](#built_collection/built_collection) /// for the general properties of Built Collections. abstract class BuiltSet implements Iterable, BuiltIterable { - final _SetFactory? _setFactory; + final Set Function()? _setFactory; final Set _set; int? _hashCode; /// Instantiates with elements from an [Iterable]. - factory BuiltSet([Iterable iterable = const []]) => BuiltSet.from(iterable); + factory BuiltSet([Iterable iterable = const []]) => + BuiltSet.from(iterable); /// Instantiates with elements from an [Iterable]. factory BuiltSet.from(Iterable iterable) { - if (iterable is _BuiltSet && iterable.hasExactElementType(E)) { + if (iterable is _BuiltSet && iterable.hasEquivalentElementType()) { return iterable as BuiltSet; } else { return _BuiltSet.from(iterable); @@ -34,7 +33,7 @@ abstract class BuiltSet implements Iterable, BuiltIterable { /// Instantiates with elements from an [Iterable]. factory BuiltSet.of(Iterable iterable) { - if (iterable is _BuiltSet && iterable.hasExactElementType(E)) { + if (iterable is _BuiltSet && iterable.hasEquivalentElementType()) { return iterable; } else { return _BuiltSet.of(iterable); @@ -228,29 +227,14 @@ abstract class BuiltSet implements Iterable, BuiltIterable { /// Default implementation of the public [BuiltSet] interface. class _BuiltSet extends BuiltSet { - _BuiltSet.withSafeSet(_SetFactory? setFactory, Set set) - : super._(setFactory, set); - - _BuiltSet.from(Iterable iterable) : super._(null, Set.from(iterable)) { - _maybeCheckForNull(); - } + _BuiltSet.withSafeSet(super.setFactory, super.set) : super._(); - _BuiltSet.of(Iterable iterable) : super._(null, {}..addAll(iterable)) { - _maybeCheckForNull(); - } + _BuiltSet.from(Iterable iterable) : super._(null, Set.from(iterable)); - bool get _needsNullCheck => !isSoundMode && null is! E; - - void _maybeCheckForNull() { - if (!_needsNullCheck) return; - for (var element in _set) { - if (identical(element, null)) { - throw ArgumentError('iterable contained invalid element: null'); - } - } - } + _BuiltSet.of(Iterable iterable) : super._(null, {}..addAll(iterable)); - bool hasExactElementType(Type type) => E == type; + bool hasEquivalentElementType() => + this is _BuiltSet && TypeHelper() is TypeHelper; } /// Extensions for [BuiltSet] on [Set]. diff --git a/lib/src/set/set_builder.dart b/lib/src/set/set_builder.dart index ef63fd0..e42ff1e 100644 --- a/lib/src/set/set_builder.dart +++ b/lib/src/set/set_builder.dart @@ -13,7 +13,7 @@ part of '../set.dart'; /// for the general properties of Built Collections. class SetBuilder { /// Used by [_createSet] to instantiate [_set]. The default value is `null`. - _SetFactory? _setFactory; + Set Function()? _setFactory; late Set _set; _BuiltSet? _setOwner; @@ -84,7 +84,7 @@ class SetBuilder { /// same type. /// /// Use [withDefaultBase] to reset `base` to the default value. - void withBase(_SetFactory base) { + void withBase(Set Function() base) { ArgumentError.checkNotNull(base, 'base'); _setFactory = base; _setSafeSet(_createSet()..addAll(_set)); diff --git a/lib/src/set_multimap.dart b/lib/src/set_multimap.dart index c40ae76..343c0c3 100644 --- a/lib/src/set_multimap.dart +++ b/lib/src/set_multimap.dart @@ -5,6 +5,7 @@ import 'internal/copy_on_write_map.dart'; import 'internal/hash.dart'; import 'internal/null_safety.dart'; +import 'internal/type_helper.dart'; import 'set.dart'; @@ -12,10 +13,10 @@ part 'set_multimap/built_set_multimap.dart'; part 'set_multimap/set_multimap_builder.dart'; // Internal only, for testing. -class OverriddenHashcodeBuiltSetMultimap extends _BuiltSetMultimap { +class OverriddenHashCodeBuiltSetMultimap extends _BuiltSetMultimap { final int _overridenHashCode; - OverriddenHashcodeBuiltSetMultimap(map, this._overridenHashCode) + OverriddenHashCodeBuiltSetMultimap(map, this._overridenHashCode) : super.copyAndCheck(map.keys, (k) => map[k]); @override diff --git a/lib/src/set_multimap/built_set_multimap.dart b/lib/src/set_multimap/built_set_multimap.dart index 469f599..eb98cd3 100644 --- a/lib/src/set_multimap/built_set_multimap.dart +++ b/lib/src/set_multimap/built_set_multimap.dart @@ -28,7 +28,7 @@ abstract class BuiltSetMultimap { /// [BuiltSetMultimap]. factory BuiltSetMultimap([multimap = const {}]) { if (multimap is _BuiltSetMultimap && - multimap.hasExactKeyAndValueTypes(K, V)) { + multimap.hasEquivalentKeyAndValueTypes()) { return multimap as BuiltSetMultimap; } else if (multimap is Map) { return _BuiltSetMultimap.copyAndCheck( @@ -121,9 +121,9 @@ abstract class BuiltSetMultimap { /// As [SetMultimap.forEach]. void forEach(void Function(K, V) f) { _map.forEach((key, values) { - values.forEach((value) { + for (var value in values) { f(key, value); - }); + } }); } @@ -164,7 +164,7 @@ abstract class BuiltSetMultimap { /// Default implementation of the public [BuiltSetMultimap] interface. class _BuiltSetMultimap extends BuiltSetMultimap { - _BuiltSetMultimap.withSafeMap(Map> map) : super._(map); + _BuiltSetMultimap.withSafeMap(super.map) : super._(); _BuiltSetMultimap.copyAndCheck(Iterable keys, Function lookup) : super._(>{}) { @@ -177,5 +177,7 @@ class _BuiltSetMultimap extends BuiltSetMultimap { } } - bool hasExactKeyAndValueTypes(Type key, Type value) => K == key && V == value; + bool hasEquivalentKeyAndValueTypes() => + this is _BuiltSetMultimap && + TypeHelper2() is TypeHelper2; } diff --git a/lib/src/set_multimap/set_multimap_builder.dart b/lib/src/set_multimap/set_multimap_builder.dart index 2d7bf3e..acf653d 100644 --- a/lib/src/set_multimap/set_multimap_builder.dart +++ b/lib/src/set_multimap/set_multimap_builder.dart @@ -106,9 +106,9 @@ class SetMultimapBuilder { /// As [SetMultimap.addValues]. void addValues(K key, Iterable values) { // _disown is called in add. - values.forEach((value) { + for (var value in values) { add(key, value); - }); + } } /// As [SetMultimap.remove] but returns nothing. @@ -155,7 +155,7 @@ class SetMultimapBuilder { void _makeWriteableCopy() { if (_builtMapOwner != null) { - _builtMap = Map>.from(_builtMap); + _builtMap = Map>.of(_builtMap); _builtMapOwner = null; } } diff --git a/pubspec.yaml b/pubspec.yaml index 645fd15..cef292c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,8 +7,8 @@ description: > repository: https://github.com/google/built_collection.dart environment: - sdk: '>=2.12.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dev_dependencies: - pedantic: ^1.4.0 - test: ^1.16.0-nullsafety + lints: ^5.0.0 + test: ^1.25.0 diff --git a/test/internal/copy_on_write_list_test.dart b/test/internal/copy_on_write_list_test.dart index b8c2418..4d7bc56 100644 --- a/test/internal/copy_on_write_list_test.dart +++ b/test/internal/copy_on_write_list_test.dart @@ -11,5 +11,55 @@ void main() { var list = [1, 2, 3]; expect(CopyOnWriteList(list, false).toString(), list.toString()); }); + + test('has safe lazy behavior', () { + var list = [1, 2, 3]; + var cowList = CopyOnWriteList(list, false); + // Once while needing to copy, once while not. + for (var i = 1; i <= 2; i++) { + var lazyCast = cowList.cast(); + var lazyMap = cowList.map((int x) => x); + var lazyExpand = cowList.expand((int x) => [x]); + var lazyWhere = cowList.where((int x) => true); + var lazyWhereType = cowList.whereType(); + var lazySkip = cowList.skip(0); + var lazyTake = cowList.take(10); + var lazyGetRange = cowList.getRange(0, cowList.length); + var lazySkipWhile = cowList.skipWhile((int x) => false); + var lazyTakeWhile = cowList.takeWhile((int x) => true); + var lazyFollowedBy = cowList.followedBy([]); + var lazyReversed = cowList.reversed; + + // Write to list. + cowList[0] += 1; + + // Iterables agree with list. + expect(lazyCast.toList(), cowList.cast().toList()); + expect(lazyMap.toList(), cowList.map((int x) => x).toList(), + reason: "map"); + expect( + lazyExpand.toList(), cowList.expand((int x) => [x]).toList(), + reason: "expand"); + expect(lazyWhere.toList(), cowList.where((int x) => true).toList(), + reason: "where"); + expect(lazyWhereType.toList(), cowList.whereType().toList(), + reason: "whereType"); + expect(lazySkip.toList(), cowList.skip(0).toList(), reason: "skip"); + expect(lazyTake.toList(), cowList.take(10).toList(), reason: "take"); + expect( + lazyGetRange.toList(), cowList.getRange(0, cowList.length).toList(), + reason: "getRange"); + expect(lazySkipWhile.toList(), + cowList.skipWhile((int x) => false).toList(), + reason: "skipWhile"); + expect( + lazyTakeWhile.toList(), cowList.takeWhile((int x) => true).toList(), + reason: "takeWhile"); + expect(lazyFollowedBy.toList(), cowList.followedBy([]).toList(), + reason: "followedBy"); + expect(lazyReversed.toList(), cowList.reversed.toList(), + reason: "reversed"); + } + }); }); } diff --git a/test/internal/copy_on_write_set_test.dart b/test/internal/copy_on_write_set_test.dart index 3d215fb..9734a38 100644 --- a/test/internal/copy_on_write_set_test.dart +++ b/test/internal/copy_on_write_set_test.dart @@ -11,5 +11,47 @@ void main() { var set = {1, 2, 3}; expect(CopyOnWriteSet(set).toString(), set.toString()); }); + + test('has safe lazy behavior', () { + var cowSet = CopyOnWriteSet({1, 2, 3}); + // Once while needing to copy, once while not. + for (var i = 1; i <= 2; i++) { + var lazyCast = cowSet.cast(); + var lazyMap = cowSet.map((int x) => x); + var lazyExpand = cowSet.expand((int x) => [x]); + var lazyWhere = cowSet.where((int x) => true); + var lazyWhereType = cowSet.whereType(); + var lazySkip = cowSet.skip(0); + var lazyTake = cowSet.take(10); + var lazySkipWhile = cowSet.skipWhile((int x) => false); + var lazyTakeWhile = cowSet.takeWhile((int x) => true); + var lazyFollowedBy = cowSet.followedBy([]); + + // Write to list. + cowSet.add(cowSet.length + 1); + + // Iterables agree with list. + expect(lazyCast.toList(), cowSet.cast().toList()); + expect(lazyMap.toList(), cowSet.map((int x) => x).toList(), + reason: "map"); + expect( + lazyExpand.toList(), cowSet.expand((int x) => [x]).toList(), + reason: "expand"); + expect(lazyWhere.toList(), cowSet.where((int x) => true).toList(), + reason: "where"); + expect(lazyWhereType.toList(), cowSet.whereType().toList(), + reason: "whereType"); + expect(lazySkip.toList(), cowSet.skip(0).toList(), reason: "skip"); + expect(lazyTake.toList(), cowSet.take(10).toList(), reason: "take"); + expect(lazySkipWhile.toList(), + cowSet.skipWhile((int x) => false).toList(), + reason: "skipWhile"); + expect( + lazyTakeWhile.toList(), cowSet.takeWhile((int x) => true).toList(), + reason: "takeWhile"); + expect(lazyFollowedBy.toList(), cowSet.followedBy([]).toList(), + reason: "followedBy"); + } + }); }); } diff --git a/test/list/built_list_test.dart b/test/list/built_list_test.dart index 852cd83..7096689 100644 --- a/test/list/built_list_test.dart +++ b/test/list/built_list_test.dart @@ -2,6 +2,8 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.s +import 'dart:async'; + import 'package:built_collection/src/list.dart'; import 'package:built_collection/src/internal/test_helpers.dart'; import 'package:test/test.dart'; @@ -68,6 +70,58 @@ void main() { ); }); + + test('can be converted to List with safe lazy behavior', () { + var list = BuiltList.of([1, 2, 3]); + var cowList = list.toList(); // Internally a CopyOnWriteList. + + // Once while needing to copy, once while not. + for (var i = 1; i <= 2; i++) { + var lazyCast = cowList.cast(); + var lazyMap = cowList.map((int x) => x); + var lazyExpand = cowList.expand((int x) => [x]); + var lazyWhere = cowList.where((int x) => true); + var lazyWhereType = cowList.whereType(); + var lazySkip = cowList.skip(0); + var lazyTake = cowList.take(10); + var lazyGetRange = cowList.getRange(0, cowList.length); + var lazySkipWhile = cowList.skipWhile((int x) => false); + var lazyTakeWhile = cowList.takeWhile((int x) => true); + var lazyFollowedBy = cowList.followedBy([]); + var lazyReversed = cowList.reversed; + + // Write to list. + cowList[0] += 1; + + // Iterables agree with list. + expect(lazyCast.toList(), cowList.cast().toList()); + expect(lazyMap.toList(), cowList.map((int x) => x).toList(), + reason: "map"); + expect( + lazyExpand.toList(), cowList.expand((int x) => [x]).toList(), + reason: "expand"); + expect(lazyWhere.toList(), cowList.where((int x) => true).toList(), + reason: "where"); + expect(lazyWhereType.toList(), cowList.whereType().toList(), + reason: "whereType"); + expect(lazySkip.toList(), cowList.skip(0).toList(), reason: "skip"); + expect(lazyTake.toList(), cowList.take(10).toList(), reason: "take"); + expect( + lazyGetRange.toList(), cowList.getRange(0, cowList.length).toList(), + reason: "getRange"); + expect(lazySkipWhile.toList(), + cowList.skipWhile((int x) => false).toList(), + reason: "skipWhile"); + expect( + lazyTakeWhile.toList(), cowList.takeWhile((int x) => true).toList(), + reason: "takeWhile"); + expect(lazyFollowedBy.toList(), cowList.followedBy([]).toList(), + reason: "followedBy"); + expect(lazyReversed.toList(), cowList.reversed.toList(), + reason: "reversed"); + } + }); + test('can be converted to an UnmodifiableListView', () { var immutableList = BuiltList().asList(); expect(immutableList, const TypeMatcher>()); @@ -187,6 +241,16 @@ void main() { expect(list1, same(list2)); }); + test('reuses BuiltSet instances of equivalent type', () { + var list1 = BuiltList(); + var list2 = BuiltList(list1); + expect(list1, same(list2)); + var list3 = BuiltList?>(list1); + expect(list1, same(list3)); + }); + + + test('does not reuse BuiltList instances with subtype element type', () { var list1 = BuiltList<_ExtendsA>(); var list2 = BuiltList<_A>(list1); @@ -200,7 +264,7 @@ void main() { }); test('converts to ListBuilder from correct type without copying', () { - var makeLongList = () => BuiltList(List.filled(1000000, 0)); + makeLongList() => BuiltList(List.filled(1000000, 0)); var longList = makeLongList(); var longListToListBuilder = longList.toBuilder; @@ -208,23 +272,23 @@ void main() { }); test('converts to ListBuilder from wrong type by copying', () { - var makeLongList = () => BuiltList(List.filled(1000000, 0)); + makeLongList() => BuiltList(List.filled(1000000, 0)); var longList = makeLongList(); - var longListToListBuilder = () => ListBuilder(longList); + longListToListBuilder() => ListBuilder(longList); expectNotMuchFaster(longListToListBuilder, makeLongList); }); test('has fast toList', () { - var makeLongList = () => BuiltList(List.filled(1000000, 0)); + makeLongList() => BuiltList(List.filled(1000000, 0)); var longList = makeLongList(); - var longListToList = () => longList.toList(); + longListToList() => longList.toList(); expectMuchFaster(longListToList, makeLongList); }); test('checks for reference identity', () { - var makeLongList = () => BuiltList(List.filled(1000000, 0)); + makeLongList() => BuiltList(List.filled(1000000, 0)); var longList = makeLongList(); var otherLongList = makeLongList(); @@ -338,7 +402,9 @@ void main() { test('implements Iterable.forEach', () { var value = 1; - BuiltList([2]).forEach((x) => value = x); + for (var x in BuiltList([2])) { + value = x; + } expect(value, 2); }); diff --git a/test/list/list_builder_test.dart b/test/list/list_builder_test.dart index 48edd4e..e818322 100644 --- a/test/list/list_builder_test.dart +++ b/test/list/list_builder_test.dart @@ -318,10 +318,9 @@ void main() { }); test('converts to BuiltList without copying', () { - var makeLongListBuilder = - () => ListBuilder(List.filled(1000000, 0)); + makeLongListBuilder() => ListBuilder(List.filled(1000000, 0)); var longListBuilder = makeLongListBuilder(); - var buildLongListBuilder = () => longListBuilder.build(); + buildLongListBuilder() => longListBuilder.build(); expectMuchFaster(buildLongListBuilder, makeLongListBuilder); }); diff --git a/test/list_multimap/built_list_multimap_test.dart b/test/list_multimap/built_list_multimap_test.dart index dde9772..e0cfa6f 100644 --- a/test/list_multimap/built_list_multimap_test.dart +++ b/test/list_multimap/built_list_multimap_test.dart @@ -386,13 +386,14 @@ void main() { test('converts to ListMultimapBuilder from correct type without copying', () { - var makeLongListMultimap = () { + makeLongListMultimap() { var result = ListMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longListMultimap = makeLongListMultimap(); var longListMultimapToListMultimapBuilder = longListMultimap.toBuilder; @@ -401,43 +402,46 @@ void main() { }); test('converts to ListMultimapBuilder from wrong type by copying', () { - var makeLongListMultimap = () { + makeLongListMultimap() { var result = ListMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longListMultimap = makeLongListMultimap(); - var longListMultimapToListMultimapBuilder = - () => ListMultimapBuilder(longListMultimap); + longListMultimapToListMultimapBuilder() => + ListMultimapBuilder(longListMultimap); expectNotMuchFaster( longListMultimapToListMultimapBuilder, makeLongListMultimap); }); test('has fast toMap', () { - var makeLongListMultimap = () { + makeLongListMultimap() { var result = ListMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longListMultimap = makeLongListMultimap(); - var longListMultimapToListMultimap = () => longListMultimap.toMap(); + longListMultimapToListMultimap() => longListMultimap.toMap(); expectMuchFaster(longListMultimapToListMultimap, makeLongListMultimap); }); test('checks for reference identity', () { - var makeLongListMultimap = () { + makeLongListMultimap() { var result = ListMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longListMultimap = makeLongListMultimap(); var otherLongListMultimap = makeLongListMultimap(); diff --git a/test/list_multimap/list_multimap_builder_test.dart b/test/list_multimap/list_multimap_builder_test.dart index 87c02cc..482238d 100644 --- a/test/list_multimap/list_multimap_builder_test.dart +++ b/test/list_multimap/list_multimap_builder_test.dart @@ -123,15 +123,16 @@ void main() { }); test('converts to BuiltListMultimap without copying', () { - var makeLongListMultimapBuilder = () { + makeLongListMultimapBuilder() { var result = ListMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(0, i); } return result; - }; + } + var longListMultimapBuilder = makeLongListMultimapBuilder(); - var buildLongListMultimapBuilder = () => longListMultimapBuilder.build(); + buildLongListMultimapBuilder() => longListMultimapBuilder.build(); expectMuchFaster( buildLongListMultimapBuilder, makeLongListMultimapBuilder); diff --git a/test/map/built_map_test.dart b/test/map/built_map_test.dart index 4437849..ea5bf3d 100644 --- a/test/map/built_map_test.dart +++ b/test/map/built_map_test.dart @@ -288,7 +288,7 @@ void main() { }); test('converts to MapBuilder from correct type without copying', () { - var makeLongMap = () => BuiltMap( + makeLongMap() => BuiltMap( Map.fromIterable(List.generate(100000, (x) => x))); var longMap = makeLongMap(); var longMapToMapBuilder = longMap.toBuilder; @@ -297,25 +297,25 @@ void main() { }); test('converts to MapBuilder from wrong type by copying', () { - var makeLongMap = () => BuiltMap( + makeLongMap() => BuiltMap( Map.fromIterable(List.generate(100000, (x) => x))); var longMap = makeLongMap(); - var longMapToMapBuilder = () => MapBuilder(longMap); + longMapToMapBuilder() => MapBuilder(longMap); expectNotMuchFaster(longMapToMapBuilder, makeLongMap); }); test('has fast toMap', () { - var makeLongMap = () => BuiltMap( + makeLongMap() => BuiltMap( Map.fromIterable(List.generate(100000, (x) => x))); var longMap = makeLongMap(); - var longMapToMap = () => longMap.toMap(); + longMapToMap() => longMap.toMap(); expectMuchFaster(longMapToMap, makeLongMap); }); test('checks for reference identity', () { - var makeLongMap = () => BuiltMap( + makeLongMap() => BuiltMap( Map.fromIterable(List.generate(100000, (x) => x))); var longMap = makeLongMap(); var otherLongMap = makeLongMap(); diff --git a/test/map/map_builder_test.dart b/test/map/map_builder_test.dart index 9c25dd6..7c08c63 100644 --- a/test/map/map_builder_test.dart +++ b/test/map/map_builder_test.dart @@ -140,7 +140,7 @@ void main() { }); test('reuses BuiltMap passed to replace if it has the same base', () { - var treeMapBase = () => SplayTreeMap(); + treeMapBase() => SplayTreeMap(); var map = BuiltMap.build((b) => b ..withBase(treeMapBase) ..addAll({1: '1', 2: '2'})); @@ -183,10 +183,10 @@ void main() { }); test('converts to BuiltMap without copying', () { - var makeLongMapBuilder = () => MapBuilder( + makeLongMapBuilder() => MapBuilder( Map.fromIterable(List.generate(100000, (x) => x))); var longMapBuilder = makeLongMapBuilder(); - var buildLongMapBuilder = () => longMapBuilder.build(); + buildLongMapBuilder() => longMapBuilder.build(); expectMuchFaster(buildLongMapBuilder, makeLongMapBuilder); }); @@ -205,8 +205,8 @@ void main() { test('has a method like Map[]', () { var mapBuilder = MapBuilder({1: '1', 2: '2'}); - mapBuilder[1] = mapBuilder[1]! + '*'; - mapBuilder[2] = mapBuilder[2]! + '**'; + mapBuilder[1] = '${mapBuilder[1]!}*'; + mapBuilder[2] = '${mapBuilder[2]!}**'; expect(mapBuilder.build().asMap(), {1: '1*', 2: '2**'}); }); @@ -319,13 +319,13 @@ void main() { test('has a method like Map.update called updateValue', () { expect( (MapBuilder({1: '1', 2: '2'}) - ..updateValue(1, (v) => v + '1', ifAbsent: () => '7')) + ..updateValue(1, (v) => '${v}1', ifAbsent: () => '7')) .build() .toMap(), {1: '11', 2: '2'}); expect( (MapBuilder({1: '1', 2: '2'}) - ..updateValue(7, (v) => v + '1', ifAbsent: () => '7')) + ..updateValue(7, (v) => '${v}1', ifAbsent: () => '7')) .build() .toMap(), {1: '1', 2: '2', 7: '7'}); diff --git a/test/set/built_set_test.dart b/test/set/built_set_test.dart index 165275d..771b03f 100644 --- a/test/set/built_set_test.dart +++ b/test/set/built_set_test.dart @@ -2,6 +2,7 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'dart:async'; import 'dart:collection' show SplayTreeSet; import 'package:built_collection/src/set.dart'; import 'package:built_collection/src/internal/test_helpers.dart'; @@ -69,6 +70,47 @@ void main() { ); }); + test('can be converted to set with safe lazy behavior', () { + var cowSet = BuiltSet.of({1, 2, 3}).toSet(); + // Once while needing to copy, once while not. + for (var i = 1; i <= 2; i++) { + var lazyCast = cowSet.cast(); + var lazyMap = cowSet.map((int x) => x); + var lazyExpand = cowSet.expand((int x) => [x]); + var lazyWhere = cowSet.where((int x) => true); + var lazyWhereType = cowSet.whereType(); + var lazySkip = cowSet.skip(0); + var lazyTake = cowSet.take(10); + var lazySkipWhile = cowSet.skipWhile((int x) => false); + var lazyTakeWhile = cowSet.takeWhile((int x) => true); + var lazyFollowedBy = cowSet.followedBy([]); + + // Write to set. + cowSet.add(cowSet.length + 1); + + // Iterables created before write agree with set after. + expect(lazyCast.toList(), cowSet.cast().toList()); + expect(lazyMap.toList(), cowSet.map((int x) => x).toList(), + reason: "map"); + expect(lazyExpand.toList(), cowSet.expand((int x) => [x]).toList(), + reason: "expand"); + expect(lazyWhere.toList(), cowSet.where((int x) => true).toList(), + reason: "where"); + expect(lazyWhereType.toList(), cowSet.whereType().toList(), + reason: "whereType"); + expect(lazySkip.toList(), cowSet.skip(0).toList(), reason: "skip"); + expect(lazyTake.toList(), cowSet.take(10).toList(), reason: "take"); + expect( + lazySkipWhile.toList(), cowSet.skipWhile((int x) => false).toList(), + reason: "skipWhile"); + expect( + lazyTakeWhile.toList(), cowSet.takeWhile((int x) => true).toList(), + reason: "takeWhile"); + expect(lazyFollowedBy.toList(), cowSet.followedBy([]).toList(), + reason: "followedBy"); + } + }); + test('uses same base when converted with toSet', () { var built = BuiltSet.build((b) => b ..withBase(() => SplayTreeSet()) @@ -220,6 +262,14 @@ void main() { expect(set1, same(set2)); }); + test('reuses BuiltSet instances of equivalent type', () { + var set1 = BuiltSet(); + var set2 = BuiltSet(set1); + expect(set1, same(set2)); + var set3 = BuiltSet?>(set1); + expect(set1, same(set3)); + }); + test('does not reuse BuiltSet instances with subtype element type', () { var set1 = BuiltSet<_ExtendsA>(); var set2 = BuiltSet<_A>(set1); @@ -233,7 +283,7 @@ void main() { }); test('converts to SetBuilder from correct type without copying', () { - var makeLongSet = () => + makeLongSet() => BuiltSet(Set.from(List.generate(100000, (x) => x))); var longSet = makeLongSet(); var longSetToSetBuilder = longSet.toBuilder; @@ -242,25 +292,25 @@ void main() { }); test('converts to SetBuilder from wrong type by copying', () { - var makeLongSet = () => + makeLongSet() => BuiltSet(Set.from(List.generate(100000, (x) => x))); var longSet = makeLongSet(); - var longSetToSetBuilder = () => SetBuilder(longSet); + longSetToSetBuilder() => SetBuilder(longSet); expectNotMuchFaster(longSetToSetBuilder, makeLongSet); }); test('has fast toSet', () { - var makeLongSet = () => + makeLongSet() => BuiltSet(Set.from(List.generate(100000, (x) => x))); var longSet = makeLongSet(); - var longSetToSet = () => longSet.toSet(); + longSetToSet() => longSet.toSet(); expectMuchFaster(longSetToSet, makeLongSet); }); test('checks for reference identity', () { - var makeLongSet = () => + makeLongSet() => BuiltSet(Set.from(List.generate(100000, (x) => x))); var longSet = makeLongSet(); var otherLongSet = makeLongSet(); @@ -342,7 +392,9 @@ void main() { test('implements Iterable.forEach', () { var value = 1; - BuiltSet([2]).forEach((x) => value = x); + for (var x in BuiltSet([2])) { + value = x; + } expect(value, 2); }); @@ -454,7 +506,7 @@ void main() { expect(BuiltSet([1, 'two', 3]).whereType(), ['two']); }); - test('can be created from`Set` using extension methods', () { + test('can be created from `Set` using extension methods', () { expect( {1, 2, 3}.build(), const TypeMatcher>(), @@ -462,7 +514,7 @@ void main() { expect({1, 2, 3}.build(), [1, 2, 3]); }); - test('can be created from`Iterable` using extension methods', () { + test('can be created from `Iterable` using extension methods', () { expect( [1, 2, 3].map((x) => x).toBuiltSet(), const TypeMatcher>(), diff --git a/test/set/set_builder_test.dart b/test/set/set_builder_test.dart index aac0a39..e02484e 100644 --- a/test/set/set_builder_test.dart +++ b/test/set/set_builder_test.dart @@ -90,7 +90,7 @@ void main() { }); test('reuses BuiltSet passed to replace if it has the same base', () { - var treeSetBase = () => SplayTreeSet(); + treeSetBase() => SplayTreeSet(); var set = BuiltSet.build((b) => b ..withBase(treeSetBase) ..addAll([1, 2])); @@ -137,10 +137,10 @@ void main() { }); test('converts to BuiltSet without copying', () { - var makeLongSetBuilder = () => + makeLongSetBuilder() => SetBuilder(Set.from(List.generate(100000, (x) => x))); var longSetBuilder = makeLongSetBuilder(); - var buildLongSetBuilder = () => longSetBuilder.build(); + buildLongSetBuilder() => longSetBuilder.build(); expectMuchFaster(buildLongSetBuilder, makeLongSetBuilder); }); diff --git a/test/set_multimap/built_set_multimap_test.dart b/test/set_multimap/built_set_multimap_test.dart index e72cdb6..1674aa0 100644 --- a/test/set_multimap/built_set_multimap_test.dart +++ b/test/set_multimap/built_set_multimap_test.dart @@ -384,13 +384,14 @@ void main() { test('converts to SetMultimapBuilder from correct type without copying', () { - var makeLongSetMultimap = () { + makeLongSetMultimap() { var result = SetMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longSetMultimap = makeLongSetMultimap(); var longSetMultimapToSetMultimapBuilder = longSetMultimap.toBuilder; @@ -399,43 +400,46 @@ void main() { }); test('converts to SetMultimapBuilder from wrong type by copying', () { - var makeLongSetMultimap = () { + makeLongSetMultimap() { var result = SetMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longSetMultimap = makeLongSetMultimap(); - var longSetMultimapToSetMultimapBuilder = - () => SetMultimapBuilder(longSetMultimap); + longSetMultimapToSetMultimapBuilder() => + SetMultimapBuilder(longSetMultimap); expectNotMuchFaster( longSetMultimapToSetMultimapBuilder, makeLongSetMultimap); }); test('has fast toMap', () { - var makeLongSetMultimap = () { + makeLongSetMultimap() { var result = SetMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longSetMultimap = makeLongSetMultimap(); - var longSetMultimapToSetMultimap = () => longSetMultimap.toMap(); + longSetMultimapToSetMultimap() => longSetMultimap.toMap(); expectMuchFaster(longSetMultimapToSetMultimap, makeLongSetMultimap); }); test('checks for reference identity', () { - var makeLongSetMultimap = () { + makeLongSetMultimap() { var result = SetMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(i, i); } return result.build(); - }; + } + var longSetMultimap = makeLongSetMultimap(); var otherLongSetMultimap = makeLongSetMultimap(); diff --git a/test/set_multimap/set_multimap_builder_test.dart b/test/set_multimap/set_multimap_builder_test.dart index b4b92f9..01b6903 100644 --- a/test/set_multimap/set_multimap_builder_test.dart +++ b/test/set_multimap/set_multimap_builder_test.dart @@ -122,15 +122,16 @@ void main() { }); test('converts to BuiltSetMultimap without copying', () { - var makeLongSetMultimapBuilder = () { + makeLongSetMultimapBuilder() { var result = SetMultimapBuilder(); for (var i = 0; i != 100000; ++i) { result.add(0, i); } return result; - }; + } + var longSetMultimapBuilder = makeLongSetMultimapBuilder(); - var buildLongSetMultimapBuilder = () => longSetMultimapBuilder.build(); + buildLongSetMultimapBuilder() => longSetMultimapBuilder.build(); expectMuchFaster(buildLongSetMultimapBuilder, makeLongSetMultimapBuilder); });