Commit 365d450baa9 for php

commit 365d450baa93f93336516bae9e574e53ae98d3c4
Author: ChrisAbner <48162172+ChrisAbner@users.noreply.github.com>
Date:   Mon Nov 9 06:18:42 2026 -0500

    Fix array_map() optimization not resolving the function name of an FCC (#25286)

diff --git a/Zend/tests/array_map_foreach_optimization_function_name.phpt b/Zend/tests/array_map_foreach_optimization_function_name.phpt
new file mode 100644
index 00000000000..ae9ea191dac
--- /dev/null
+++ b/Zend/tests/array_map_foreach_optimization_function_name.phpt
@@ -0,0 +1,127 @@
+--TEST--
+array_map(): foreach optimization resolves the function name of an FCC
+--FILE--
+<?php
+
+namespace {
+    function label($x) { return "global:$x"; }
+    function only_global($x) { return "only_global:$x"; }
+}
+
+namespace Lib {
+    function fmt($x) { return "Lib\\fmt:$x"; }
+}
+
+namespace App {
+    function label($x) { return "App\\label:$x"; }
+    function strtoupper($x) { return "App\\strtoupper:$x"; }
+    function pad($x, $y) { return "App\\pad:$x$y"; }
+
+    use function Lib\fmt;
+    use function App\label as lbl;
+
+    echo "namespaced function:\n";
+    var_dump(\array_map(label(...), [1]));
+    echo "namespaced function shadowing an internal one:\n";
+    var_dump(\array_map(strtoupper(...), ['a']));
+    echo "use function:\n";
+    var_dump(\array_map(fmt(...), [2]));
+    echo "use function with alias:\n";
+    var_dump(\array_map(lbl(...), [3]));
+    echo "namespace-relative name:\n";
+    var_dump(\array_map(namespace\label(...), [4]));
+    echo "fully qualified name:\n";
+    var_dump(\array_map(\label(...), [5]));
+    echo "fallback to the global function:\n";
+    var_dump(\array_map(only_global(...), [6]));
+    echo "fallback to an internal function:\n";
+    var_dump(\array_map(ucfirst(...), ['b']));
+    echo "partial application:\n";
+    var_dump(\array_map(pad(?, '!'), [7]));
+
+    if (true) {
+        function declared_at_runtime($x) { return "App\\declared_at_runtime:$x"; }
+    }
+    echo "function declared at runtime:\n";
+    var_dump(\array_map(declared_at_runtime(...), [8]));
+}
+
+namespace {
+    use function Lib\fmt;
+    use function Lib\fmt as libfmt;
+
+    echo "global namespace, use function:\n";
+    var_dump(array_map(fmt(...), [9]));
+    echo "global namespace, use function with alias:\n";
+    var_dump(array_map(libfmt(...), [10]));
+    echo "global namespace, unqualified:\n";
+    var_dump(array_map(label(...), [11]));
+}
+
+?>
+--EXPECT--
+namespaced function:
+array(1) {
+  [0]=>
+  string(11) "App\label:1"
+}
+namespaced function shadowing an internal one:
+array(1) {
+  [0]=>
+  string(16) "App\strtoupper:a"
+}
+use function:
+array(1) {
+  [0]=>
+  string(9) "Lib\fmt:2"
+}
+use function with alias:
+array(1) {
+  [0]=>
+  string(11) "App\label:3"
+}
+namespace-relative name:
+array(1) {
+  [0]=>
+  string(11) "App\label:4"
+}
+fully qualified name:
+array(1) {
+  [0]=>
+  string(8) "global:5"
+}
+fallback to the global function:
+array(1) {
+  [0]=>
+  string(13) "only_global:6"
+}
+fallback to an internal function:
+array(1) {
+  [0]=>
+  string(1) "B"
+}
+partial application:
+array(1) {
+  [0]=>
+  string(10) "App\pad:7!"
+}
+function declared at runtime:
+array(1) {
+  [0]=>
+  string(25) "App\declared_at_runtime:8"
+}
+global namespace, use function:
+array(1) {
+  [0]=>
+  string(9) "Lib\fmt:9"
+}
+global namespace, use function with alias:
+array(1) {
+  [0]=>
+  string(10) "Lib\fmt:10"
+}
+global namespace, unqualified:
+array(1) {
+  [0]=>
+  string(9) "global:11"
+}
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 4de326b3677..9a0d330f04b 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -5399,9 +5399,19 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg

 	/* Evaluate function name */
 	znode func_node;
-	{
+	zend_ast *func_name_ast = callback->child[callback->kind == ZEND_AST_CALL ? 0 : 1];
+	if (callback->kind == ZEND_AST_CALL
+	 && func_name_ast->kind == ZEND_AST_ZVAL
+	 && Z_TYPE_P(zend_ast_get_zval(func_name_ast)) == IS_STRING) {
+		/* A literal function name has no side effects and must not be memoized:
+		 * compiling it as an expression would yield the unresolved string, which
+		 * ignores the current namespace, "use function" imports and the runtime
+		 * fallback to the global function. Compile the call in the loop body with
+		 * the original name instead, like a regular call. */
+		func_node.op_type = IS_UNUSED;
+	} else {
 		znode result;
-		zend_compile_expr(&result, callback->child[callback->kind == ZEND_AST_CALL ? 0 : 1]);
+		zend_compile_expr(&result, func_name_ast);
 		if (result.op_type == IS_CONST || result.op_type == IS_TMP_VAR) {
 			func_node = result;
 		} else {
@@ -5459,7 +5469,9 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
 	/* loop body */
 	znode call_result;
 	zend_ast *func_ast;
-	if (func_node.op_type == IS_CONST) {
+	if (func_node.op_type == IS_UNUSED) {
+		func_ast = func_name_ast;
+	} else if (func_node.op_type == IS_CONST) {
 		func_ast = zend_ast_create_znode(&func_node);
 	} else {
 		znode copy_node;
@@ -5486,7 +5498,9 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
 			break;
 		}
 	}
-	zend_ast_destroy(func_ast);
+	if (func_ast != func_name_ast) {
+		zend_ast_destroy(func_ast);
+	}
 	opline = zend_emit_op(NULL, ZEND_ADD_ARRAY_ELEMENT, &call_result, &key);
 	SET_NODE(opline->result, result);
 	/* end loop body */
@@ -5501,7 +5515,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg

 	zend_end_loop(opnum_fetch, &reset_node);
 	zend_emit_op(NULL, ZEND_FE_FREE, &reset_node, NULL);
-	if (func_node.op_type != IS_CONST) {
+	if (func_node.op_type != IS_UNUSED && func_node.op_type != IS_CONST) {
 		zend_emit_op(NULL, ZEND_FREE, &func_node, NULL);
 	}
 	if (class_node.op_type != IS_UNUSED && class_node.op_type != IS_CONST) {