parent
974d65a0d1
commit
0e6d65c6e1
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