Skip to content

Commit da33780

Browse files
committed
Always memoize calls in lhs of coalesce assignment
We don't want to invoke calls twice, even if they are considered "variables", i.e. might be writable if returning a reference. Function calls behave the same in all BP contexts so they don't need to be invoked twice. The singular exception to this is nullsafe coalesce in isset/empty, because it needs to return false/true respectively when short-circuited. However, since nullsafe calls are not allwed in write context we may ignore this problem.
1 parent 149fb8f commit da33780

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

Zend/tests/assign_coalesce_008.phpt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
Assign coalesce: All calls should be memoized
3+
--FILE--
4+
<?php
5+
class Foo {
6+
public $prop;
7+
8+
public function foo() {
9+
echo __METHOD__, "\n";
10+
return $this;
11+
}
12+
13+
public function bar() {
14+
echo __METHOD__, "\n";
15+
return 'prop';
16+
}
17+
18+
public function __isset($name) {
19+
echo __METHOD__, "\n";
20+
return false;
21+
}
22+
23+
public function __set($name, $value) {
24+
echo __METHOD__, "\n";
25+
var_dump($value);
26+
}
27+
}
28+
29+
function &foo() {
30+
global $foo;
31+
echo __FUNCTION__, "\n";
32+
return $foo;
33+
}
34+
function bar() {
35+
echo __FUNCTION__, "\n";
36+
}
37+
38+
foo(bar())['bar'] ??= 42;
39+
var_dump($foo);
40+
41+
$foo = new Foo();
42+
$foo->foo()->foo()->{$foo->bar()} ??= 42;
43+
var_dump($foo);
44+
$foo->foo()->baz ??= 42;
45+
46+
?>
47+
--EXPECT--
48+
bar
49+
foo
50+
array(1) {
51+
["bar"]=>
52+
int(42)
53+
}
54+
Foo::foo
55+
Foo::foo
56+
Foo::bar
57+
object(Foo)#1 (1) {
58+
["prop"]=>
59+
int(42)
60+
}
61+
Foo::foo
62+
Foo::__isset
63+
Foo::__set
64+
int(42)

Zend/zend_compile.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10558,6 +10558,17 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
1055810558
{
1055910559
CG(zend_lineno) = zend_ast_get_lineno(ast);
1056010560

10561+
if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) {
10562+
switch (ast->kind) {
10563+
case ZEND_AST_CALL:
10564+
case ZEND_AST_METHOD_CALL:
10565+
case ZEND_AST_NULLSAFE_METHOD_CALL:
10566+
case ZEND_AST_STATIC_CALL:
10567+
zend_compile_memoized_expr(result, ast);
10568+
return &CG(active_op_array)->opcodes[CG(active_op_array)->last - 1];
10569+
}
10570+
}
10571+
1056110572
switch (ast->kind) {
1056210573
case ZEND_AST_VAR:
1056310574
return zend_compile_simple_var(result, ast, type, 0);

0 commit comments

Comments
 (0)