Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions Zend/tests/array_map_foreach_optimization_function_name.phpt
Original file line number Diff line number Diff line change
@@ -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"
}
24 changes: 19 additions & 5 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand All @@ -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) {
Expand Down
Loading