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

Arnaud Le Blanc <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-20T10:31:29+02:00

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

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix GH-23301: nested "yield from" repeats a value after "yield from []" (#23302)

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


Diff:

diff --git a/Zend/tests/generators/backtrace_multi_yield_from.phpt b/Zend/tests/generators/backtrace_multi_yield_from.phpt
index 4fbaa2f05bb4..2cb898b3aac2 100644
--- a/Zend/tests/generators/backtrace_multi_yield_from.phpt
+++ b/Zend/tests/generators/backtrace_multi_yield_from.phpt
@@ -25,8 +25,8 @@ var_dump($gen2->current());
 ?>
 --EXPECTF--
 int(1)
-int(1)
 #0 %s(10): gen()
 #1 [internal function]: from(Object(Generator))
-#2 %s(19): Generator->next()
+#2 %s(17): Generator->next()
 int(2)
+NULL
diff --git a/Zend/tests/generators/gh15375.phpt b/Zend/tests/generators/gh15375.phpt
index 48919083f227..f0a2fe56c16c 100644
--- a/Zend/tests/generators/gh15375.phpt
+++ b/Zend/tests/generators/gh15375.phpt
@@ -46,9 +46,9 @@ 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).
+// Reading a pre-primed generator through two nested "yield from" levels primes
+// the whole chain but advances nothing. A later next() on a middle level then
+// advances the shared generator, like any other next() would.
 echo "shared primed:\n";
 function counter() {
     yield 1;
@@ -58,11 +58,11 @@ $gen1 = counter();
 $gen1->valid();
 $gen2 = outer($gen1);
 $gen3 = outer($gen2);
-echo "gen3 current: ", $gen3->current(), "\n";
+var_dump($gen3->current());
 $gen2->next();
-echo "gen2 current: ", $gen2->current(), "\n";
+var_dump($gen2->current());
 $gen2->next();
-echo "gen2 current: ", $gen2->current(), "\n";
+var_dump($gen2->current());
 
 ?>
 --EXPECT--
@@ -84,6 +84,6 @@ six
 eight
 nine
 shared primed:
-gen3 current: 1
-gen2 current: 1
-gen2 current: 2
+int(1)
+int(2)
+NULL
diff --git a/Zend/tests/generators/gh23301.phpt b/Zend/tests/generators/gh23301.phpt
new file mode 100644
index 000000000000..02aaf2fe2022
--- /dev/null
+++ b/Zend/tests/generators/gh23301.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GH-23301 (Nested "yield from" yields a value twice when the middle generator delegates again)
+--FILE--
+<?php
+
+function inner() {
+    yield "B";
+}
+
+function middle() {
+    yield "A";
+    yield from inner();
+    yield "C";
+    yield from ["D"];
+}
+
+function delegate($gen) {
+    yield from $gen;
+}
+
+foreach (delegate(middle()) as $value) {
+    echo $value, "\n";
+}
+
+function tail($inner) {
+    yield from $inner;
+    yield from ["E"];
+}
+
+$middle = tail(inner());
+$outer = delegate($middle);
+
+var_dump($outer->current());
+$middle->next();
+var_dump($middle->current());
+
+?>
+--EXPECT--
+A
+B
+C
+D
+string(1) "B"
+string(1) "E"
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index 2bfbbf687d3c..b9c77e4f0f6a 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -776,10 +776,12 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 		return;
 	}
 
-	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;
+	if (UNEXPECTED((delegator->flags & ZEND_GENERATOR_DO_INIT) != 0)) {
+		delegator->flags &= ~ZEND_GENERATOR_DO_INIT;
+		if (UNEXPECTED(!Z_ISUNDEF(generator->value))) {
+			/* We must not advance an already initialized delegate on first resumption */
+			return;
+		}
 	}
 
 	if (EG(active_fiber)) {
@@ -819,7 +821,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 			EG(current_execute_data) = original_execute_data;
 			EG(jit_trace_num) = original_jit_trace_num;
 
-			orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
+			orig_generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
 			generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER);
 			return;
 		}
@@ -882,7 +884,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 		} else {
 			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;
 		}
@@ -900,7 +901,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
 		goto try_again;
 	}
 
-	orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
+	orig_generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
 }
 /* }}} */
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.