Skip to content

[CoreLib] Iterable.withIterator #59908

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## 3.8.0

### Libraries

#### `dart:core`

- Added `Iterable.withIterator` constructor.

## 3.7.0

### Language
Expand Down
32 changes: 32 additions & 0 deletions sdk/lib/core/iterable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ abstract mixin class Iterable<E> {
return _GeneratorIterable<E>(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<E>` 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<E> Function() iteratorFactory) =
_WithIteratorIterable<E>;

/// Creates an empty iterable.
///
/// The empty iterable has no elements, and iterating it always stops
Expand Down Expand Up @@ -915,6 +933,20 @@ class _GeneratorIterable<E> extends ListIterable<E> {
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<E> extends Iterable<E> {
final Iterator<E> Function() _iteratorFactory;

/// Creates an iterable that gets its elements from iterators created by
/// the provided factory function.
_WithIteratorIterable(this._iteratorFactory);

Iterator<E> get iterator => _iteratorFactory();
}

/// Convert elements of [iterable] to strings and store them in [parts].
void _iterablePartsToStrings(Iterable<Object?> iterable, List<String> parts) {
// This is the complicated part of [iterableToShortString].
Expand Down
95 changes: 95 additions & 0 deletions tests/corelib/iterable_with_iterator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import "package:expect/expect.dart";

void main() {
testIteratorFactory();
testIteratorEquality();
}

void testIteratorFactory() {
Iterator<Object?>? returnedIterator;
Object? thrownError;
int calls = 0;
Iterator<T> f<T>() {
calls++;
if (thrownError case var error?) throw error;
return returnedIterator as Iterator<T>;
}

var ints = Iterable.withIterator(f<int>);
var intIterator = <int>[].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 = <int>[].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<Object?>);
Expect.identical(
intIterator2,
objectqs.iterator,
"Object iterator should match",
);
Expect.equals(8, calls, "Eighth call count");

var nulls = Iterable.withIterator(f<Null>);
var nullIterator = <Null>[].iterator;
returnedIterator = nullIterator;
Expect.identical(nullIterator, nulls.iterator, "Null iterator should match");
Expect.equals(9, calls, "Ninth call count");

var nevers = Iterable.withIterator(f<Never>);
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",
);
}