Skip to content
Open
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
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.7.0alpha1

- Core:
. A flat unkeyed list assignment from an array literal with one value per
target, such as [$a, $b] = [$b, $a], no longer builds the array when the
result is unused (GH-23048). (Artem Ukrainskiy)

- Curl:
. curl_multi_exec() now throws a TypeError when still_running is not an int.
(arshidkv12)
Expand Down
7 changes: 7 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ PHP 8.7 UPGRADE NOTES
14. Performance Improvements
========================================

- Core:
. A flat unkeyed list assignment from an array literal with one value per
target, such as [$a, $b] = [$b, $a], no longer builds the array when the
result is unused: the values are evaluated in order, a variable among them
is copied, and they are assigned to the targets directly. The values are
still released after the last assignment, as the array was.

- Standard:
. Improved performance of strcspn() and strpbrk() when searching longer strings
with a single-byte character mask.
Expand Down
97 changes: 97 additions & 0 deletions Zend/tests/list/list_from_array_literal.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
--TEST--
List assignment from an array literal: values and targets
--FILE--
<?php

$a = 1; $b = 2; $c = 3;
[$a, $b] = [$b, $a];
var_dump($a, $b);

[$a, $b, $c] = [$b, $c, $a];
var_dump($a, $b, $c);

list($a, $b) = array($b, $a);
var_dump($a, $b);

[$a, $a] = [1, 2];
var_dump($a);

$a = 1;
[$a, $b] = [2, $a];
var_dump($a, $b);

[$a, $b,] = [$b, $a,];
var_dump($a, $b);

class P { public $a = 1; public $b = 2; public static $c = 3; public static $d = 4; }
$p = new P;
[$p->a, $p->b] = [$p->b, $p->a];
[P::$c, P::$d] = [P::$d, P::$c];
var_dump($p->a, $p->b, P::$c, P::$d);

$x = [1, 2];
[$x[0], $x[1]] = [$x[1], $x[0]];
var_dump($x);

[$GLOBALS['g']] = [5];
var_dump($g);

$r = [$a, $b] = [7, 8];
var_dump($r, $a, $b);

$a = 1;
[$f, $a] = [fn() => $a, 2];
var_dump($f());

for ([$a, $b] = [1, 2], $i = 0; $i < 3; $i++, [$a, $b] = [$b, $a + $b]) {
echo "$a $b\n";
}
for ($i = 0; [$a, $b] = [$b, $a], $i < 2; $i++) {
echo "$a $b\n";
}
for ($i = 0; [$x, $y] = [$i, $i * 2]; $i++) {
if ($i > 1) break;
echo "$x $y\n";
}

?>
--EXPECT--
int(2)
int(1)
int(1)
int(3)
int(2)
int(3)
int(1)
int(2)
int(2)
int(1)
int(1)
int(2)
int(2)
int(1)
int(4)
int(3)
array(2) {
[0]=>
int(2)
[1]=>
int(1)
}
int(5)
array(2) {
[0]=>
int(7)
[1]=>
int(8)
}
int(7)
int(8)
int(1)
1 2
2 3
3 5
8 5
5 8
0 0
1 2
116 changes: 116 additions & 0 deletions Zend/tests/list/list_from_array_literal_order.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

class D {
public function __construct(public string $n) { echo "construct $this->n\n"; }
public function __destruct() { echo "destruct $this->n\n"; $GLOBALS['dead'] = true; }
public function __toString(): string { return $this->n; }
}
function v(string $n) { echo "value $n\n"; return $n; }
function k(string $n) { echo "key $n\n"; return 0; }

echo "-- values before targets\n";
$x = [];
[$x[k('a')], $x[k('b')]] = [v('1'), v('2')];

echo "-- extra values\n";
[$a] = [new D('a'), new D('x'), new D('y')];
echo "assigned\n";
unset($a);

echo "-- values nobody takes live until the assignments are done\n";
function untaken_variable() {
$x = new D('old x');
$y = new D('y');
[, $x, $y] = [$x, 1, 2];
echo "assigned\n";
}
untaken_variable();

echo "-- a value the target does not keep lives until the assignments are done\n";
final class Drop implements ArrayAccess {
public function offsetExists($o): bool { return true; }
public function offsetGet($o): mixed { return null; }
public function offsetSet($o, $v): void { echo "set $o\n"; }
public function offsetUnset($o): void {}
}
$drop = new Drop;
[$drop['k'], $drop['l']] = [new D('k'), new D('l')];
echo "assigned\n";
$b = new D('old b');
[$a, $a, $b] = [new D('first'), new D('second'), 3];
echo "assigned\n";
unset($a);

echo "-- the same with a destructor that throws\n";
final class Boom { public function __destruct() { throw new Exception('boom'); } }
$b = 0;
try {
[$a, $a, $b] = [new Boom, new stdClass, 42];
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
var_dump($b);
unset($a);

echo "-- two targets that are references to each other\n";
$a = null; $b = null; $c = 0;
$a = &$b;
try {
[$a, $b, $c] = [new Boom, new stdClass, 42];
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
var_dump($c);
unset($a, $b);

echo "-- a variable that is a reference to a typed property\n";
final class S { public string $s = ''; }
$obj = new S;
$r = &$obj->s;
[$r, $drop['y']] = [new D('coerced'), 1];
echo "assigned\n";
unset($r, $obj);

echo "-- pending values when a target expression throws\n";
function boom() { throw new Exception('boom'); }
try {
[$arr[boom()], $b, $c] = [new D('v1'), new D('v2'), new D('v3')];
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}

echo "-- a variable is copied before any assignment\n";
$o = new D('o');
[$o, $p] = [new D('new o'), $o];
echo "assigned\n";
var_dump($o->n, $p->n);
unset($o, $p);

echo "-- exception in a later value\n";
try {
[$a, $b, $c] = [new D('first'), new D('second'), boom()];
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}

echo "-- exception in an assignment\n";
class T { public int $i = 1; }
$t = new T;
try {
[$t->i, $b] = ['not an int', new D('after the throw')];
} catch (TypeError $e) {
echo get_class($e), "\n";
}
var_dump($t->i);

echo "-- a destructor that throws during the release after the assignments\n";
final class BoomNamed extends D {
public function __destruct() { echo "destruct $this->n throws\n"; throw new Exception("boom $this->n"); }
}
try {
[$drop['a'], $drop['b']] = [new BoomNamed('a'), new D('b')];
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}

echo "done\n";
88 changes: 88 additions & 0 deletions Zend/tests/list/list_from_array_literal_order.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
--TEST--
List assignment from an array literal: evaluation order and lifetime of the values
--FILE--
<?php
require __DIR__ . '/list_from_array_literal_order.inc';
?>
--EXPECT--
-- values before targets
value 1
value 2
key a
key b
-- extra values
construct a
construct x
construct y
destruct x
destruct y
assigned
destruct a
-- values nobody takes live until the assignments are done
construct old x
construct y
destruct y
destruct old x
assigned
-- a value the target does not keep lives until the assignments are done
construct k
construct l
set k
set l
destruct k
destruct l
assigned
construct old b
construct first
construct second
destruct old b
destruct first
assigned
destruct second
-- the same with a destructor that throws
boom
int(42)
-- two targets that are references to each other
boom
int(42)
-- a variable that is a reference to a typed property
construct coerced
set y
destruct coerced
assigned
-- pending values when a target expression throws
construct v1
construct v2
construct v3
destruct v2
destruct v3
destruct v1
boom
-- a variable is copied before any assignment
construct o
construct new o
assigned
string(5) "new o"
string(1) "o"
destruct new o
destruct o
-- exception in a later value
construct first
construct second
destruct first
destruct second
boom
-- exception in an assignment
construct after the throw
destruct after the throw
TypeError
int(1)
-- a destructor that throws during the release after the assignments
construct a
construct b
set a
set b
destruct a throws
destruct b
boom a
done
Loading
Loading