[php-src] master: Merge branch 'PHP-8.4' into PHP-8.5

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-12T06:34:06-04:00

Commit: https://github.com/php/php-src/commit/a465822c3da1dfd5b72aa1d2aab18bc04df54082
Raw diff: https://github.com/php/php-src/commit/a465822c3da1dfd5b72aa1d2aab18bc04df54082.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix GH-15375: nested "yield from" skips items after valid()/next()

Changed paths:
  A  Zend/tests/generators/gh15375.phpt
  M  NEWS
  M  Zend/zend_generators.c


Diff:

diff --git a/NEWS b/NEWS
index c18dfda62aa5..ab010e5a0ec7 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.5.11
 
+- Core:
+  . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
+    next() call on the inner generator). (iliaal)
+
 - Readline:
   . Fixed the interactive shell not waiting for the pager process to exit.
     (Weilin Du)
diff --git a/Zend/tests/generators/gh15375.phpt b/Zend/tests/generators/gh15375.phpt
new file mode 100644
index 000000000000..48919083f227
--- /dev/null
+++ b/Zend/tests/generators/gh15375.phpt
@@ -0,0 +1,89 @@
+--TEST--
+GH-15375 (Nested "yield from" skips items after valid()/next() on the inner generator)
+--FILE--
+<?php
+
+function arrayProvider() {
+    yield ['one', 'two', 'three'];
+    yield ['four', 'five', 'six'];
+    yield ['seven', 'eight', 'nine'];
+}
+
+function iterateValues(array $array) {
+    foreach ($array as $value) {
+        yield $value;
+    }
+}
+
+function withValid() {
+    foreach (arrayProvider() as $array) {
+        $iterator = iterateValues($array);
+        if ($iterator->valid()) {
+            yield from $iterator;
+        }
+    }
+}
+
+function withNext() {
+    foreach (arrayProvider() as $array) {
+        $iterator = iterateValues($array);
+        $iterator->next();
+        yield from $iterator;
+    }
+}
+
+function outer(Generator $inner) {
+    yield from $inner;
+}
+
+echo "valid():\n";
+foreach (outer(withValid()) as $s) {
+    echo $s, "\n";
+}
+
+echo "next():\n";
+foreach (outer(withNext()) as $s) {
+    echo $s, "\n";
+}
+
+// A shared, pre-primed generator consumed through two nested "yield from"
+// levels must still present its current value once to each consumer (the fix
+// must not over-clear the middle level's first-touch).
+echo "shared primed:\n";
+function counter() {
+    yield 1;
+    yield 2;
+}
+$gen1 = counter();
+$gen1->valid();
+$gen2 = outer($gen1);
+$gen3 = outer($gen2);
+echo "gen3 current: ", $gen3->current(), "\n";
+$gen2->next();
+echo "gen2 current: ", $gen2->current(), "\n";
+$gen2->next();
+echo "gen2 current: ", $gen2->current(), "\n";
+
+?>
+--EXPECT--
+valid():
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+next():
+two
+three
+five
+six
+eight
+nine
+shared primed:
+gen3 current: 1
+gen2 current: 1
+gen2 current: 2
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index f7bcf01881dc..83815773b912 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -768,13 +768,15 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 		return;
 	}
 
+	zend_generator *delegator = orig_generator;
+
 try_again:
 	if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
 		zend_throw_error(NULL, "Cannot resume an already running generator");
 		return;
 	}
 
-	if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
+	if (UNEXPECTED((delegator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
 		/* We must not advance Generator if we yield from a Generator being currently run */
 		orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
 		return;
@@ -881,12 +883,19 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 			generator = zend_generator_get_current(orig_generator);
 			zend_generator_throw_exception(generator, NULL);
 			orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
+			delegator = orig_generator;
 			goto try_again;
 		}
 	}
 
 	/* yield from was used, try another resume. */
-	if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM))) {
+	if (UNEXPECTED(generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM)) {
+		delegator = generator;
+		generator = zend_generator_get_current(orig_generator);
+		goto try_again;
+	}
+	if (UNEXPECTED(generator != orig_generator && !Z_ISUNDEF(generator->retval))) {
+		delegator = orig_generator;
 		generator = zend_generator_get_current(orig_generator);
 		goto try_again;
 	}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.