parent
f2db98b7da
commit
d11637faf0
20 changed files with 646 additions and 92 deletions
@ -0,0 +1,45 @@ |
||||
--TEST-- |
||||
Dynamic Closures accept positional arguments explicitly marked with refval |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function main(): void |
||||
{ |
||||
$fixed = static function (&$value): void { |
||||
$value .= '!'; |
||||
}; |
||||
$text = 'fixed'; |
||||
$fixed(refval($text)); |
||||
var_dump($text); |
||||
|
||||
$optional = static function (&$value = null): void { |
||||
var_dump($value); |
||||
$value = 'private-default'; |
||||
}; |
||||
$optional(); |
||||
|
||||
$arrow = static fn (&$value): int => ++$value; |
||||
$number = 40; |
||||
var_dump($arrow(refval($number)), $number); |
||||
|
||||
$typed = static function (int &$value): void { |
||||
$value++; |
||||
}; |
||||
$typed(refval($number)); |
||||
var_dump($number); |
||||
|
||||
$invalid = any('not-an-int'); |
||||
try { |
||||
$typed(refval($invalid)); |
||||
} catch (TypeError $error) { |
||||
echo "typed reference rejected\n"; |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(6) "fixed!" |
||||
NULL |
||||
int(41) |
||||
int(41) |
||||
int(42) |
||||
typed reference rejected |
||||
@ -0,0 +1,111 @@ |
||||
--TEST-- |
||||
By-reference variadic parameters preserve direct, named and method arguments |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function suffix(string $suffix, &...$values): array |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value .= $suffix; |
||||
} |
||||
unset($value); |
||||
return array_keys($values); |
||||
} |
||||
|
||||
class VariadicReferenceMutator |
||||
{ |
||||
public static function increment(&...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value++; |
||||
} |
||||
unset($value); |
||||
} |
||||
|
||||
public function double(&...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value *= 2; |
||||
} |
||||
unset($value); |
||||
} |
||||
} |
||||
|
||||
class VariadicReferenceTarget |
||||
{ |
||||
public int $value = 10; |
||||
public static int $staticValue = 20; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(suffix('!')); |
||||
|
||||
$first = 'first'; |
||||
$second = 'second'; |
||||
var_dump(suffix('!', $first, $second)); |
||||
var_dump($first, $second); |
||||
|
||||
$left = 'left'; |
||||
$right = 'right'; |
||||
var_dump(suffix(suffix: '?', left: $left, right: $right)); |
||||
var_dump($left, $right); |
||||
|
||||
$one = 1; |
||||
$two = 2; |
||||
VariadicReferenceMutator::increment($one, $two); |
||||
var_dump($one, $two); |
||||
|
||||
$mutator = new VariadicReferenceMutator(); |
||||
$mutator->double($one, $two); |
||||
var_dump($one, $two); |
||||
|
||||
$array = [7]; |
||||
$target = new VariadicReferenceTarget(); |
||||
VariadicReferenceMutator::increment( |
||||
$array[0], |
||||
$target->value, |
||||
VariadicReferenceTarget::$staticValue, |
||||
); |
||||
var_dump($array, $target->value, VariadicReferenceTarget::$staticValue); |
||||
|
||||
// As in PHP, passing an undefined variable by reference creates it. |
||||
VariadicReferenceMutator::increment($createdByReference); |
||||
var_dump($createdByReference); |
||||
|
||||
$parameter = (new ReflectionFunction('suffix'))->getParameters()[1]; |
||||
var_dump($parameter->isVariadic(), $parameter->isPassedByReference()); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(0) { |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(0) |
||||
[1]=> |
||||
int(1) |
||||
} |
||||
string(6) "first!" |
||||
string(7) "second!" |
||||
array(2) { |
||||
[0]=> |
||||
string(4) "left" |
||||
[1]=> |
||||
string(5) "right" |
||||
} |
||||
string(5) "left?" |
||||
string(6) "right?" |
||||
int(2) |
||||
int(3) |
||||
int(4) |
||||
int(6) |
||||
array(1) { |
||||
[0]=> |
||||
int(8) |
||||
} |
||||
int(11) |
||||
int(21) |
||||
int(1) |
||||
bool(true) |
||||
bool(true) |
||||
@ -0,0 +1,49 @@ |
||||
--TEST-- |
||||
Reference Closure parameters work at Zend callback boundaries used by Symfony mbstring polyfill |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function convert_values(string $suffix, &...$vars): bool |
||||
{ |
||||
$ok = true; |
||||
array_walk_recursive($vars, static function (&$value, $key) use (&$ok, $suffix): void { |
||||
if (!is_string($value)) { |
||||
$ok = false; |
||||
return; |
||||
} |
||||
$value .= $suffix . ':' . $key; |
||||
}); |
||||
return $ok; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$first = ['a' => 'one', 'nested' => ['b' => 'two']]; |
||||
$second = 'three'; |
||||
var_dump(convert_values('!', $first, $second)); |
||||
var_dump($first, $second); |
||||
|
||||
$invalid = ['ok', 42]; |
||||
var_dump(convert_values('?', $invalid)); |
||||
var_dump($invalid); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
array(2) { |
||||
["a"]=> |
||||
string(6) "one!:a" |
||||
["nested"]=> |
||||
array(1) { |
||||
["b"]=> |
||||
string(6) "two!:b" |
||||
} |
||||
} |
||||
string(8) "three!:1" |
||||
bool(false) |
||||
array(2) { |
||||
[0]=> |
||||
string(5) "ok?:0" |
||||
[1]=> |
||||
int(42) |
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
--TEST-- |
||||
Dynamic calls require explicit refval for by-reference arguments |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function dynamic_increment(&...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value++; |
||||
} |
||||
unset($value); |
||||
} |
||||
|
||||
class DynamicReferenceMutator |
||||
{ |
||||
public function suffix(string $suffix, &...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value .= $suffix; |
||||
} |
||||
unset($value); |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$function = 'dynamic_increment'; |
||||
$number = 40; |
||||
$function(refval($number)); |
||||
var_dump($number); |
||||
|
||||
$mutator = new DynamicReferenceMutator(); |
||||
$method = [$mutator, 'suffix']; |
||||
$first = 'one'; |
||||
$second = 'two'; |
||||
$method('!', refval($first), refval($second)); |
||||
var_dump($first, $second); |
||||
|
||||
$closure = static function (&$value): void { |
||||
$value .= '?'; |
||||
}; |
||||
$closure(refval($second)); |
||||
var_dump($second); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(41) |
||||
string(4) "one!" |
||||
string(4) "two!" |
||||
string(5) "two!?" |
||||
@ -0,0 +1,39 @@ |
||||
--TEST-- |
||||
By-reference variadic signatures remain compatible across interfaces and inheritance |
||||
--FILE-- |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
interface IncrementContract |
||||
{ |
||||
public function increment(int &...$values): void; |
||||
} |
||||
|
||||
abstract class IncrementBase implements IncrementContract |
||||
{ |
||||
abstract public function increment(int &...$values): void; |
||||
} |
||||
|
||||
final class Incrementer extends IncrementBase |
||||
{ |
||||
public function increment(int &...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value++; |
||||
} |
||||
unset($value); |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$incrementer = new Incrementer(); |
||||
$first = 10; |
||||
$second = 20; |
||||
$incrementer->increment($first, $second); |
||||
var_dump($first, $second); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(11) |
||||
int(21) |
||||
@ -0,0 +1,93 @@ |
||||
--TEST-- |
||||
Typed by-reference variadics validate, widen float arguments and write through unions and objects |
||||
--FILE-- |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
class Counter |
||||
{ |
||||
public function __construct(public int $value) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
function scale(float &...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value *= 1.5; |
||||
} |
||||
unset($value); |
||||
} |
||||
|
||||
function normalize(int|string &...$values): void |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value = is_int($value) ? $value + 1 : strtoupper($value); |
||||
} |
||||
unset($value); |
||||
} |
||||
|
||||
function bump_objects(Counter &...$values): void |
||||
{ |
||||
foreach ($values as $value) { |
||||
$value->value++; |
||||
} |
||||
} |
||||
|
||||
function require_ints(int &...$values): void |
||||
{ |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$integer = 2; |
||||
$float = 2.5; |
||||
scale($integer, $float); |
||||
var_dump($integer, $float); |
||||
|
||||
$values = [4, 6.0]; |
||||
scale(...$values); |
||||
var_dump($values); |
||||
|
||||
$number = 10; |
||||
$text = 'hello'; |
||||
normalize($number, $text); |
||||
var_dump($number, $text); |
||||
|
||||
$first = new Counter(1); |
||||
$second = new Counter(5); |
||||
bump_objects($first, $second); |
||||
var_dump($first->value, $second->value); |
||||
|
||||
$invalid = any('not-an-int'); |
||||
try { |
||||
require_ints($invalid); |
||||
} catch (TypeError $error) { |
||||
echo get_class($error), ': ', $error->getMessage(), PHP_EOL; |
||||
} |
||||
|
||||
$invalidUnpack = ['still-not-an-int']; |
||||
try { |
||||
require_ints(...$invalidUnpack); |
||||
} catch (TypeError $error) { |
||||
echo "unpack rejected\n"; |
||||
} |
||||
var_dump(ReflectionReference::fromArrayElement($invalidUnpack, 0)); |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
float(3) |
||||
float(3.75) |
||||
array(2) { |
||||
[0]=> |
||||
float(6) |
||||
[1]=> |
||||
float(9) |
||||
} |
||||
int(11) |
||||
string(5) "HELLO" |
||||
int(2) |
||||
int(6) |
||||
TypeError: require_ints(): Argument #1 ($values) must be of type int, string given |
||||
unpack rejected |
||||
NULL |
||||
@ -0,0 +1,99 @@ |
||||
--TEST-- |
||||
By-reference variadic unpack preserves writeback, COW separation, keys and existing references |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function increment_all(&...$values): array |
||||
{ |
||||
foreach ($values as &$value) { |
||||
$value++; |
||||
} |
||||
unset($value); |
||||
return array_keys($values); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$source = [1, 2]; |
||||
$copy = $source; |
||||
var_dump(increment_all(...$source)); |
||||
var_dump($source, $copy); |
||||
|
||||
$named = ['left' => 10, 'right' => 20]; |
||||
var_dump(increment_all(...$named)); |
||||
var_dump($named); |
||||
|
||||
$first = [30]; |
||||
$second = [40, 50]; |
||||
var_dump(increment_all(...$first, ...$second)); |
||||
var_dump($first, $second); |
||||
|
||||
$external = 60; |
||||
$references = [&$external]; |
||||
increment_all(...$references); |
||||
var_dump($external, $references); |
||||
|
||||
// A temporary has no caller-visible slots, but remains a valid unpack. |
||||
var_dump(increment_all(...[70, 80])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(2) { |
||||
[0]=> |
||||
int(0) |
||||
[1]=> |
||||
int(1) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(2) |
||||
[1]=> |
||||
int(3) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(1) |
||||
[1]=> |
||||
int(2) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
string(4) "left" |
||||
[1]=> |
||||
string(5) "right" |
||||
} |
||||
array(2) { |
||||
["left"]=> |
||||
int(11) |
||||
["right"]=> |
||||
int(21) |
||||
} |
||||
array(3) { |
||||
[0]=> |
||||
int(0) |
||||
[1]=> |
||||
int(1) |
||||
[2]=> |
||||
int(2) |
||||
} |
||||
array(1) { |
||||
[0]=> |
||||
int(31) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(41) |
||||
[1]=> |
||||
int(51) |
||||
} |
||||
int(61) |
||||
array(1) { |
||||
[0]=> |
||||
&int(61) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(0) |
||||
[1]=> |
||||
int(1) |
||||
} |
||||
Loading…
Reference in new issue