Commit d11cbf88561 for php
commit d11cbf885617a739b8c3a8c330d1bb0a504ee5b0
Author: Claudio Dekker <1752195+claudiodekker@users.noreply.github.com>
Date: Wed Oct 14 18:31:31 2026 +0200
Fix array_map() optimization not resolving the class name of a static FCC (#24244)
diff --git a/NEWS b/NEWS
index e8826f49a1f..3e42a4c1d09 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@ PHP NEWS
. Fixed skipped catch block when switching fibers during exception unwinding
(Nicolas Grekas)
. Fix escape analysis for objects implementing ArrayAccess. (ndossche, Arnaud)
+ . Fixed array_map() optimization not resolving the class name of a static
+ first-class callable. (Claudio Dekker)
- Fileinfo:
. Fixed bug GH-24289 (finfo::file() reports the wrong argument number for
diff --git a/Zend/tests/array_map_foreach_optimization_class_name.phpt b/Zend/tests/array_map_foreach_optimization_class_name.phpt
new file mode 100644
index 00000000000..fab9c51ee55
--- /dev/null
+++ b/Zend/tests/array_map_foreach_optimization_class_name.phpt
@@ -0,0 +1,74 @@
+--TEST--
+array_map(): foreach optimization resolves the class name of a static FCC
+--FILE--
+<?php
+
+namespace Foo {
+ class Bar {
+ public static function double(int $x): int {
+ return $x * 2;
+ }
+ }
+
+ class Parent_ {
+ public static function triple(int $x): int {
+ return $x * 3;
+ }
+ }
+
+ class Child extends Parent_ {
+ public static function scoped(): void {
+ var_dump(\array_map(self::triple(...), [1]));
+ var_dump(\array_map(static::triple(...), [1]));
+ var_dump(\array_map(parent::triple(...), [1]));
+ }
+ }
+
+ var_dump(\array_map(Bar::double(...), [1]));
+ Child::scoped();
+}
+
+namespace {
+ use Foo\Bar;
+ use Foo\Bar as Baz;
+
+ var_dump(array_map(Bar::double(...), [1]));
+ var_dump(array_map(Baz::double(...), [1]));
+ var_dump(array_map(\Foo\Bar::double(...), [1]));
+ var_dump(array_map(namespace\Foo\Bar::double(...), [1]));
+}
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ int(2)
+}
+array(1) {
+ [0]=>
+ int(3)
+}
+array(1) {
+ [0]=>
+ int(3)
+}
+array(1) {
+ [0]=>
+ int(3)
+}
+array(1) {
+ [0]=>
+ int(2)
+}
+array(1) {
+ [0]=>
+ int(2)
+}
+array(1) {
+ [0]=>
+ int(2)
+}
+array(1) {
+ [0]=>
+ int(2)
+}
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index fe8f978ff59..40f90e38475 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -5375,7 +5375,15 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
/* Evaluate class name */
znode class_node;
- if (callback->kind == ZEND_AST_STATIC_CALL) {
+ if (callback->kind == ZEND_AST_STATIC_CALL
+ && callback->child[0]->kind == ZEND_AST_ZVAL
+ && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING
+ && zend_get_class_fetch_type(zend_ast_get_str(callback->child[0])) == ZEND_FETCH_CLASS_DEFAULT) {
+ /* Resolve the name against the namespace and imports, as compiling it
+ * as an expression would yield the unresolved string. */
+ class_node.op_type = IS_CONST;
+ ZVAL_STR(&class_node.u.constant, zend_resolve_class_name_ast(callback->child[0]));
+ } else if (callback->kind == ZEND_AST_STATIC_CALL) {
znode result;
zend_compile_expr(&result, callback->child[0]);
if (result.op_type == IS_CONST || result.op_type == IS_TMP_VAR) {