Commit 6a17febe6c9 for php.net

commit 6a17febe6c92119f079969e6b3ab09d03686e65e
Author: Tim Düsterhus <tim@tideways-gmbh.com>
Date:   Mon Jan 26 08:59:30 2026 +0100

    zend_compile: Fix array_map() optimization (#21016)

    * zend_compile: Fix handling of PFA syntax in array_map() optimization

    PFA is not implemented and the syntax is rejected at compile-time, thus it was
    assumed the assertion would be unreachable. However the check for PFA syntax
    happens after compiling special functions, making it reachable. Fix this by
    gracefully returning FAILURE which will then correctly emit the error during
    the compilation of the normal call.

    Fixes php/php-src#20991.

    * zend_compile: Fix array_map() optimization for dynamic function names

    Fixes php/php-src#20990.

    * zend_compile: Adjust array_map() optimization after review feedback

diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 916d8eebd89..1ce921d4fec 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -5054,25 +5054,23 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
 	/* Bail out if the callback is assert() due to the AST stringification logic
 	 * breaking for the generated call.
 	 */
-	if (callback->kind == ZEND_AST_CALL && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) {
+	if (callback->kind == ZEND_AST_CALL
+	 && callback->child[0]->kind == ZEND_AST_ZVAL
+	 && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING
+	 && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) {
+		return FAILURE;
+	}
+
+	zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
+	if (callback_args->children != 1 || callback_args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
+		/* Full PFA is not yet implemented, will fail in zend_compile_call_common(). */
 		return FAILURE;
 	}

 	znode value;
 	value.op_type = IS_TMP_VAR;
 	value.u.op.var = get_temporary_variable();
-
-	zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
-	zend_ast *call_args = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
-	for (uint32_t i = 0; i < callback_args->children; i++) {
-		zend_ast *child = callback_args->child[i];
-		if (child->kind == ZEND_AST_PLACEHOLDER_ARG) {
-			call_args = zend_ast_list_add(call_args, zend_ast_create_znode(&value));
-		} else {
-			ZEND_ASSERT(0 && "not implemented");
-			call_args = zend_ast_list_add(call_args, child);
-		}
-	}
+	zend_ast *call_args = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&value));

 	zend_op *opline;

diff --git a/ext/opcache/tests/array_map_foreach_optimization_006.phpt b/ext/opcache/tests/array_map_foreach_optimization_006.phpt
new file mode 100644
index 00000000000..a3b1c534e34
--- /dev/null
+++ b/ext/opcache/tests/array_map_foreach_optimization_006.phpt
@@ -0,0 +1,84 @@
+--TEST--
+array_map(): foreach optimization - dynamic name
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.opt_debug_level=0x20000
+--FILE--
+<?php
+
+function plus1($x) {
+    return $x + 1;
+}
+
+$array = range(1, 10);
+
+$plus1 = 'plus1';
+$foo = array_map($plus1(...), $array);
+
+var_dump($foo);
+
+?>
+--EXPECTF--
+$_main:
+     ; (lines=%d, args=0, vars=%d, tmps=%d)
+     ; (after optimizer)
+     ; %s
+0000 INIT_FCALL 2 %d string("range")
+0001 SEND_VAL int(1) 1
+0002 SEND_VAL int(10) 2
+0003 V3 = DO_ICALL
+0004 ASSIGN CV0($array) V3
+0005 ASSIGN CV1($plus1) string("plus1")
+0006 TYPE_ASSERT 131079 string("array_map") CV0($array)
+0007 T3 = INIT_ARRAY 0 (packed) NEXT
+0008 V4 = FE_RESET_R CV0($array) 0015
+0009 T6 = FE_FETCH_R V4 T5 0015
+0010 INIT_DYNAMIC_CALL 1 CV1($plus1)
+0011 SEND_VAL_EX T5 1
+0012 V5 = DO_FCALL
+0013 T3 = ADD_ARRAY_ELEMENT V5 T6
+0014 JMP 0009
+0015 FE_FREE V4
+0016 ASSIGN CV2($foo) T3
+0017 INIT_FCALL 1 %d string("var_dump")
+0018 SEND_VAR CV2($foo) 1
+0019 DO_ICALL
+0020 RETURN int(1)
+LIVE RANGES:
+     3: 0008 - 0016 (tmp/var)
+     4: 0009 - 0015 (loop)
+     5: 0010 - 0011 (tmp/var)
+     6: 0010 - 0013 (tmp/var)
+
+plus1:
+     ; (lines=3, args=1, vars=1, tmps=%d)
+     ; (after optimizer)
+     ; %s
+0000 CV0($x) = RECV 1
+0001 T1 = ADD CV0($x) int(1)
+0002 RETURN T1
+array(10) {
+  [0]=>
+  int(2)
+  [1]=>
+  int(3)
+  [2]=>
+  int(4)
+  [3]=>
+  int(5)
+  [4]=>
+  int(6)
+  [5]=>
+  int(7)
+  [6]=>
+  int(8)
+  [7]=>
+  int(9)
+  [8]=>
+  int(10)
+  [9]=>
+  int(11)
+}