Skip to content

Commit 465f851

Browse files
committed
fix nesting iterations over the same Accumulate instance
1 parent 35a9eba commit 465f851

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

proofs/accumulate.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
use Innmind\Immutable\Accumulate;
4+
use Innmind\BlackBox\Set;
5+
6+
return static function() {
7+
yield proof(
8+
"Accumulate partial access doesn't skip values",
9+
given(
10+
Set\Sequence::of(Set\Type::any()),
11+
Set\Sequence::of(Set\Type::any()),
12+
),
13+
static function($assert, $prefix, $suffix) {
14+
$accumulate = new Accumulate((static function() use ($prefix, $suffix) {
15+
yield from $prefix;
16+
yield from $suffix;
17+
})());
18+
19+
foreach ($accumulate as $i => $_) {
20+
if ($i === (\count($prefix) - 1)) {
21+
break;
22+
}
23+
}
24+
25+
$all = [];
26+
27+
foreach ($accumulate as $value) {
28+
$all[] = $value;
29+
}
30+
31+
$assert->same(
32+
[...$prefix, ...$suffix],
33+
$all,
34+
);
35+
},
36+
);
37+
38+
yield test(
39+
'Accumuluate nested iteration',
40+
static function($assert) {
41+
$accumulate = new Accumulate((static function() {
42+
yield 1;
43+
yield 2;
44+
yield 3;
45+
})());
46+
47+
$pairs = [];
48+
49+
foreach ($accumulate as $i) {
50+
foreach ($accumulate as $j) {
51+
$pairs[] = [$i, $j];
52+
}
53+
}
54+
55+
$assert->same(
56+
[
57+
[1, 1],
58+
[1, 2],
59+
[1, 3],
60+
[2, 1],
61+
[2, 2],
62+
[2, 3],
63+
[3, 1],
64+
[3, 2],
65+
[3, 3],
66+
],
67+
$pairs,
68+
);
69+
},
70+
);
71+
};

src/Accumulate.php

+21-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ final class Accumulate implements \Iterator
1818
private \Generator $generator;
1919
/** @var list<S> */
2020
private array $values = [];
21+
/** @var list<int<0, max>> */
22+
private array $cursors = [];
2123
private bool $started = false;
2224

2325
/**
@@ -70,6 +72,11 @@ public function next(): void
7072

7173
public function rewind(): void
7274
{
75+
if (\is_int($cursor = \key($this->values))) {
76+
/** @psalm-suppress InaccessibleProperty */
77+
$this->cursors[] = $cursor;
78+
}
79+
7380
/** @psalm-suppress InaccessibleProperty */
7481
$this->started = true;
7582
/** @psalm-suppress InaccessibleProperty */
@@ -84,10 +91,20 @@ public function valid(): bool
8491
$valid = !$this->reachedCacheEnd() || $this->generator->valid();
8592

8693
if (!$valid) {
87-
// once the "true" end has been reached we automatically rewind this
88-
// iterator so it is always in a clean state
89-
/** @psalm-suppress UnusedMethodCall */
90-
$this->rewind();
94+
// once the "true" end has been reached we automatically rewind to
95+
// the previous cursor position to allow correct iteration when
96+
// nesting loops on the same iterator
97+
/** @psalm-suppress InaccessibleProperty */
98+
$previousCursor = \array_pop($this->cursors);
99+
/** @psalm-suppress InaccessibleProperty */
100+
\reset($this->values);
101+
102+
if (\is_int($previousCursor)) {
103+
while (\is_int(\key($this->values)) && \key($this->values) !== $previousCursor) {
104+
/** @psalm-suppress InaccessibleProperty */
105+
\next($this->values);
106+
}
107+
}
91108
}
92109

93110
return $valid;

0 commit comments

Comments
 (0)