Permalink
Please sign in to comment.
Browse files
Implement ??= operator
RFC: https://wiki.php.net/rfc/null_coalesce_equal_operator $a ??= $b is $a ?? ($a = $b), with the difference that $a is only evaluated once, to the degree that this is possible. In particular in $a[foo()] ?? $b function foo() is only ever called once. However, the variable access themselves will be reevaluated.
- Loading branch information...
Showing
with
1,068 additions
and 517 deletions.
- +9 −0 UPGRADING
- +127 −0 Zend/tests/assign_coalesce_001.phpt
- +84 −0 Zend/tests/assign_coalesce_002.phpt
- +70 −0 Zend/tests/assign_coalesce_003.phpt
- +11 −0 Zend/tests/assign_coalesce_004.phpt
- +14 −0 Zend/tests/assign_coalesce_005.phpt
- +1 −0 Zend/zend_ast.c
- +1 −0 Zend/zend_ast.h
- +142 −0 Zend/zend_compile.c
- +2 −0 Zend/zend_globals.h
- +4 −1 Zend/zend_language_parser.y
- +4 −0 Zend/zend_language_scanner.l
- +56 −13 Zend/zend_opcode.c
- +10 −0 Zend/zend_vm_def.h
- +60 −43 Zend/zend_vm_execute.h
- +457 −456 Zend/zend_vm_handlers.h
- +4 −2 Zend/zend_vm_opcodes.c
- +2 −1 Zend/zend_vm_opcodes.h
- +2 −1 ext/opcache/Optimizer/block_pass.c
- +1 −0 ext/opcache/Optimizer/sccp.c
- +3 −0 ext/opcache/Optimizer/zend_inference.c
- +1 −0 ext/opcache/Optimizer/zend_optimizer.c
- +3 −0 ext/tokenizer/tokenizer_data.c
| @@ -0,0 +1,127 @@ | |||
| --TEST-- | |||
| Coalesce assign (??=): Basic behavior | |||
| --FILE-- | |||
| <?php | |||
| // Refcounted values | |||
| $foo = "fo"; | |||
| $foo .= "o"; | |||
| $bar = "ba"; | |||
| $bar .= "r"; | |||
| // Identity function used to track single-evaluation | |||
| function id($arg) { | |||
| echo "id($arg)\n"; | |||
| return $arg; | |||
| } | |||
| echo "Simple variables:\n"; | |||
| $a = 123; | |||
| $a ??= 456; | |||
| var_dump($a); | |||
| $b = null; | |||
| $b ??= $foo; | |||
| var_dump($b); | |||
| $c = $foo; | |||
| $c ??= $bar; | |||
| var_dump($c); | |||
| $d ??= $foo; | |||
| var_dump($c); | |||
| echo "\nArrays:\n"; | |||
| $ary = []; | |||
| $ary["foo"] ??= 123; | |||
| $ary[$foo] ??= $bar; | |||
| $ary[$bar] ??= $foo; | |||
| var_dump($ary); | |||
| $ary = []; | |||
| $ary[id($foo)] ??= 123; | |||
| $ary[id($foo)] ??= $bar; | |||
| $ary[id($bar)] ??= $foo; | |||
| var_dump($ary); | |||
| echo "\nObjects:\n"; | |||
| $obj = new stdClass; | |||
| $obj->foo ??= 123; | |||
| $obj->$foo ??= $bar; | |||
| $obj->$bar ??= $foo; | |||
| var_dump($obj); | |||
| $obj = new stdClass; | |||
| $obj->{id($foo)} ??= 123; | |||
| $obj->{id($foo)} ??= $bar; | |||
| $obj->{id($bar)} ??= $foo; | |||
| var_dump($obj); | |||
| class Test { | |||
| public static $foo; | |||
| public static $bar; | |||
| } | |||
| echo "\nStatic props:\n"; | |||
| Test::$foo ??= 123; | |||
| Test::$$foo ??= $bar; | |||
| Test::$$bar ??= $foo; | |||
| var_dump(Test::$foo, Test::$bar); | |||
| Test::$foo = null; | |||
| Test::$bar = null; | |||
| Test::${id($foo)} ??= 123; | |||
| Test::${id($foo)} ??= $bar; | |||
| Test::${id($bar)} ??= $foo; | |||
| var_dump(Test::$foo, Test::$bar); | |||
| ?> | |||
| --EXPECT-- | |||
| Simple variables: | |||
| int(123) | |||
| string(3) "foo" | |||
| string(3) "foo" | |||
| string(3) "foo" | |||
|
|
|||
| Arrays: | |||
| array(2) { | |||
| ["foo"]=> | |||
| int(123) | |||
| ["bar"]=> | |||
| string(3) "foo" | |||
| } | |||
| id(foo) | |||
| id(foo) | |||
| id(bar) | |||
| array(2) { | |||
| ["foo"]=> | |||
| int(123) | |||
| ["bar"]=> | |||
| string(3) "foo" | |||
| } | |||
|
|
|||
| Objects: | |||
| object(stdClass)#1 (2) { | |||
| ["foo"]=> | |||
| int(123) | |||
| ["bar"]=> | |||
| string(3) "foo" | |||
| } | |||
| id(foo) | |||
| id(foo) | |||
| id(bar) | |||
| object(stdClass)#2 (2) { | |||
| ["foo"]=> | |||
| int(123) | |||
| ["bar"]=> | |||
| string(3) "foo" | |||
| } | |||
|
|
|||
| Static props: | |||
| int(123) | |||
| string(3) "foo" | |||
| id(foo) | |||
| id(foo) | |||
| id(bar) | |||
| int(123) | |||
| string(3) "foo" | |||
| @@ -0,0 +1,84 @@ | |||
| --TEST-- | |||
| Coalesce assign (??=): Exception handling | |||
| --FILE-- | |||
| <?php | |||
| $foo = "fo"; | |||
| $foo .= "o"; | |||
| $bar = "ba"; | |||
| $bar .= "r"; | |||
| function id($arg) { | |||
| echo "id($arg)\n"; | |||
| return $arg; | |||
| } | |||
| function do_throw($msg) { | |||
| throw new Exception($msg); | |||
| } | |||
| $ary = []; | |||
| try { | |||
| $ary[id($foo)] ??= do_throw("ex1"); | |||
| } catch (Exception $e) { | |||
| echo $e->getMessage(), "\n"; | |||
| } | |||
| var_dump($ary); | |||
| class AA implements ArrayAccess { | |||
| public function offsetExists($k) { | |||
| return true; | |||
| } | |||
| public function &offsetGet($k) { | |||
| $var = ["foo" => "bar"]; | |||
| return $var; | |||
| } | |||
| public function offsetSet($k,$v) {} | |||
| public function offsetUnset($k) {} | |||
| } | |||
| class Dtor { | |||
| public function __destruct() { | |||
| throw new Exception("dtor"); | |||
| } | |||
| } | |||
| $ary = new AA; | |||
| try { | |||
| $ary[new Dtor][id($foo)] ??= $bar; | |||
| } catch (Exception $e) { | |||
| echo $e->getMessage(), "\n"; | |||
| } | |||
| var_dump($foo); | |||
| class AA2 implements ArrayAccess { | |||
| public function offsetExists($k) { | |||
| return false; | |||
| } | |||
| public function offsetGet($k) { | |||
| return null; | |||
| } | |||
| public function offsetSet($k,$v) {} | |||
| public function offsetUnset($k) {} | |||
| } | |||
| $ary = ["foo" => new AA2]; | |||
| try { | |||
| $ary[id($foo)][new Dtor] ??= $bar; | |||
| } catch (Exception $e) { | |||
| echo $e->getMessage(), "\n"; | |||
| } | |||
| var_dump($foo); | |||
| ?> | |||
| --EXPECT-- | |||
| id(foo) | |||
| ex1 | |||
| array(0) { | |||
| } | |||
| id(foo) | |||
| dtor | |||
| string(3) "foo" | |||
| id(foo) | |||
| dtor | |||
| string(3) "foo" | |||
| @@ -0,0 +1,70 @@ | |||
| --TEST-- | |||
| Coalesce assign (??=): ArrayAccess handling | |||
| --FILE-- | |||
| <?php | |||
| function id($arg) { | |||
| echo "id($arg)\n"; | |||
| return $arg; | |||
| } | |||
| class AA implements ArrayAccess { | |||
| public $data; | |||
| public function __construct($data = []) { | |||
| $this->data = $data; | |||
| } | |||
| public function &offsetGet($k) { | |||
| echo "offsetGet($k)\n"; | |||
| return $this->data[$k]; | |||
| } | |||
| public function offsetExists($k) { | |||
| echo "offsetExists($k)\n"; | |||
| return array_key_exists($k, $this->data); | |||
| } | |||
| public function offsetSet($k,$v) { | |||
| echo "offsetSet($k,$v)\n"; | |||
| $this->data[$k] = $v; | |||
| } | |||
| public function offsetUnset($k) { } | |||
| } | |||
| $ary = new AA(["foo" => new AA, "null" => null]); | |||
| echo "[foo]\n"; | |||
| $ary["foo"] ??= "bar"; | |||
| echo "[bar]\n"; | |||
| $ary["bar"] ??= "foo"; | |||
| echo "[null]\n"; | |||
| $ary["null"] ??= "baz"; | |||
| echo "[foo][bar]\n"; | |||
| $ary["foo"]["bar"] ??= "abc"; | |||
| echo "[foo][bar]\n"; | |||
| $ary["foo"]["bar"] ??= "def"; | |||
| ?> | |||
| --EXPECT-- | |||
| [foo] | |||
| offsetExists(foo) | |||
| offsetGet(foo) | |||
| [bar] | |||
| offsetExists(bar) | |||
| offsetSet(bar,foo) | |||
| [null] | |||
| offsetExists(null) | |||
| offsetGet(null) | |||
| offsetSet(null,baz) | |||
| [foo][bar] | |||
| offsetExists(foo) | |||
| offsetGet(foo) | |||
| offsetExists(bar) | |||
| offsetGet(foo) | |||
| offsetSet(bar,abc) | |||
| [foo][bar] | |||
| offsetExists(foo) | |||
| offsetGet(foo) | |||
| offsetExists(bar) | |||
| offsetGet(bar) | |||
| @@ -0,0 +1,11 @@ | |||
| --TEST-- | |||
| Coalesce assign (??=): Non-writable variable | |||
| --FILE-- | |||
| <?php | |||
| function foo() { return 123; } | |||
| foo() ??= 456; | |||
| ?> | |||
| --EXPECTF-- | |||
| Fatal error: Can't use function return value in write context in %s on line %d | |||
| @@ -0,0 +1,14 @@ | |||
| --TEST-- | |||
| Coalesce assign (??=): Cannot reassign $this | |||
| --FILE-- | |||
| <?php | |||
| class Test { | |||
| public function foobar() { | |||
| $this ??= 123; | |||
| } | |||
| } | |||
| ?> | |||
| --EXPECTF-- | |||
| Fatal error: Cannot re-assign $this in %s on line %d | |||
Oops, something went wrong.
0 comments on commit
a50198d