diff --git a/CHANGELOG.md b/CHANGELOG.md index e2730fc58fa3..e2b05fd2cdf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## 3.8.0 +### Libraries + +#### `dart:core` + +- Added `Iterable.withIterator` constructor. + ## 3.7.0 ### Language diff --git a/sdk/lib/core/iterable.dart b/sdk/lib/core/iterable.dart index aee4c73279bc..8c354d0826fb 100644 --- a/sdk/lib/core/iterable.dart +++ b/sdk/lib/core/iterable.dart @@ -127,6 +127,24 @@ abstract mixin class Iterable { return _GeneratorIterable(count, generator); } + /// Creates an [Iterable] from the [Iterator] factory. + /// + /// The returned iterable creates a new iterator each time [iterator] is read, + /// by calling the provided [iteratorFactory] function. The [iteratorFactory] + /// function must return a new instance of `Iterator` on each call. + /// + /// This factory is useful when you need to create an iterable from a custom + /// iterator, or when you want to ensure a fresh iteration state on each use. + /// + /// Example: + /// ```dart + /// final numbers = Iterable.withIterator(() => [1, 2, 3].iterator); + /// print(numbers.toList()); // [1, 2, 3] + /// ``` + @Since("3.8") + factory Iterable.withIterator(Iterator Function() iteratorFactory) = + _WithIteratorIterable; + /// Creates an empty iterable. /// /// The empty iterable has no elements, and iterating it always stops @@ -915,6 +933,20 @@ class _GeneratorIterable extends ListIterable { static int _id(int n) => n; } +/// Used internally by [Iterable.withIterator]. +/// +/// This class implements [Iterable] by delegating the iterator creation +/// to the provided function. +class _WithIteratorIterable extends Iterable { + final Iterator Function() _iteratorFactory; + + /// Creates an iterable that gets its elements from iterators created by + /// the provided factory function. + _WithIteratorIterable(this._iteratorFactory); + + Iterator get iterator => _iteratorFactory(); +} + /// Convert elements of [iterable] to strings and store them in [parts]. void _iterablePartsToStrings(Iterable iterable, List parts) { // This is the complicated part of [iterableToShortString]. diff --git a/tests/corelib/iterable_with_iterator_test.dart b/tests/corelib/iterable_with_iterator_test.dart new file mode 100644 index 000000000000..b1833f3e23de --- /dev/null +++ b/tests/corelib/iterable_with_iterator_test.dart @@ -0,0 +1,95 @@ +import "package:expect/expect.dart"; + +void main() { + testIteratorFactory(); + testIteratorEquality(); +} + +void testIteratorFactory() { + Iterator? returnedIterator; + Object? thrownError; + int calls = 0; + Iterator f() { + calls++; + if (thrownError case var error?) throw error; + return returnedIterator as Iterator; + } + + var ints = Iterable.withIterator(f); + var intIterator = [].iterator; + returnedIterator = intIterator; + Expect.identical(intIterator, ints.iterator, "First iterator should match"); + Expect.equals(1, calls, "First call count"); + Expect.identical(intIterator, ints.iterator, "Second iterator should match"); + Expect.equals(2, calls, "Second call count"); + + var intIterator2 = [].iterator; + Expect.notIdentical( + intIterator, + intIterator2, + "Different iterators should not be identical", + ); + returnedIterator = intIterator2; + Expect.identical(intIterator2, ints.iterator, "Third iterator should match"); + Expect.equals(3, calls, "Third call count"); + Expect.identical(intIterator2, ints.iterator, "Fourth iterator should match"); + Expect.equals(4, calls, "Fourth call count"); + + var error = StateError("quo"); + thrownError = error; + Expect.identical( + error, + Expect.throws(() => ints.iterator), + "Should throw first error", + ); + Expect.equals(5, calls, "Fifth call count"); + Expect.identical( + error, + Expect.throws(() => ints.iterator), + "Should throw second error", + ); + Expect.equals(6, calls, "Sixth call count"); + + thrownError = null; + Expect.identical(intIterator2, ints.iterator, "Fifth iterator should match"); + Expect.equals(7, calls, "Seventh call count"); + + var objectqs = Iterable.withIterator(f); + Expect.identical( + intIterator2, + objectqs.iterator, + "Object iterator should match", + ); + Expect.equals(8, calls, "Eighth call count"); + + var nulls = Iterable.withIterator(f); + var nullIterator = [].iterator; + returnedIterator = nullIterator; + Expect.identical(nullIterator, nulls.iterator, "Null iterator should match"); + Expect.equals(9, calls, "Ninth call count"); + + var nevers = Iterable.withIterator(f); + thrownError = error; + Expect.identical( + error, + Expect.throws(() => nevers.iterator), + "Should throw third error", + ); + Expect.equals(10, calls, "Tenth call count"); + Expect.identical( + error, + Expect.throws(() => nevers.iterator), + "Should throw fourth error", + ); + Expect.equals(11, calls, "Eleventh call count"); +} + +void testIteratorEquality() { + var iterable1 = Iterable.withIterator(() => [1, 2, 3].iterator); + var iterable2 = Iterable.withIterator(() => [1, 2, 3].iterator); + Expect.listEquals( + iterable1.toList(), + iterable2.toList(), + "Iterator contents should be equal", + ); +}