-
Notifications
You must be signed in to change notification settings - Fork 70
Description
I've come across a rare case where the behavior of \iter\isEmpty() can indirectly cause unexpected consumption from iterators in normal use cases.
Specifically, in the case where isEmpty is provided an object of \IteratorAggregate, the object's getIterator() method is called. Any further uses of this object may call getIterator() a second time, so if this method is not a pure function then the first item(s?) we might expect from the iterator could be lost to the void.
I've put together what I believe is a minimal reproduction case here:
https://gist.github.com/athrawes/e451484fdb4d0646f9aa03c9e47b566a
Basically, if the item passed to isEmpty looks like this:
class MyClass implements \IteratorAggregate {
public function getIterator(): \Traversable {
// some logic which consumes some resource
// or otherwise mutates global mutable state
while ($item = array_pop($someGlobalState)) { yield $item; }
}
}then the following does not behave as expected:
$iterable = new MyClass();
\iter\isEmpty($iterable); // <- initially appears to behave as expected, items have not technically been consumed yet
foreach ($iterable as $item) { ... } // <- missing item(s?) from the front as we got a 'new' iterable with said items missingThen interacting with that item in the future with any foreach constructs or any methods in this library will be missing some item(s?) from the front of the iterable.
I'm not sure if this is really a bug in this library per se, but it is rather unexpected - especially as the docblock on isEmpty claims to not consume items from iterators. Would this simply be a matter of warning users about this edge case, or is there some way to handle this here?