parent
7768ce177e
commit
4e13e7b0ab
2 changed files with 385 additions and 0 deletions
@ -0,0 +1,281 @@ |
|||||||
|
--TEST-- |
||||||
|
ArrayAccess ??= preserves offsetExists, offsetGet, offsetSet and lazy evaluation semantics |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
final class CoalesceBag implements ArrayAccess |
||||||
|
{ |
||||||
|
public array $data = []; |
||||||
|
|
||||||
|
public function offsetExists(mixed $offset): bool |
||||||
|
{ |
||||||
|
echo "exists:$offset\n"; |
||||||
|
return array_key_exists($offset, $this->data); |
||||||
|
} |
||||||
|
|
||||||
|
public function offsetGet(mixed $offset): mixed |
||||||
|
{ |
||||||
|
echo "get:$offset\n"; |
||||||
|
return $this->data[$offset] ?? null; |
||||||
|
} |
||||||
|
|
||||||
|
public function offsetSet(mixed $offset, mixed $value): void |
||||||
|
{ |
||||||
|
echo "set:$offset=$value\n"; |
||||||
|
$this->data[$offset] = $value; |
||||||
|
} |
||||||
|
|
||||||
|
public function offsetUnset(mixed $offset): void |
||||||
|
{ |
||||||
|
unset($this->data[$offset]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final class CoalesceHolder |
||||||
|
{ |
||||||
|
public function __construct(public CoalesceBag $bag) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceRhs(string $label, int $value): int |
||||||
|
{ |
||||||
|
echo "rhs:$label\n"; |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
function throwingCoalesceRhs(): int |
||||||
|
{ |
||||||
|
echo "rhs:throw\n"; |
||||||
|
throw new RuntimeException('failed'); |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceThroughInterface(ArrayAccess $target, string $key, int $value): mixed |
||||||
|
{ |
||||||
|
return $target[$key] ??= coalesceRhs('interface', $value); |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceThroughMixed(mixed $target, string $key, int $value): array |
||||||
|
{ |
||||||
|
$result = $target[$key] ??= coalesceRhs('mixed', $value); |
||||||
|
return [$result, $target]; |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceThroughNullableObject(?ArrayAccess $target): mixed |
||||||
|
{ |
||||||
|
return $target['nullable'] ??= coalesceRhs('nullable-object', 34); |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceReceiver(CoalesceBag $target, int &$calls): CoalesceBag |
||||||
|
{ |
||||||
|
$calls++; |
||||||
|
echo "receiver:$calls\n"; |
||||||
|
return $target; |
||||||
|
} |
||||||
|
|
||||||
|
function coalesceKey(int &$calls): string |
||||||
|
{ |
||||||
|
$calls++; |
||||||
|
echo "key:$calls\n"; |
||||||
|
return 'side'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
echo "-- missing --\n"; |
||||||
|
$missing = new CoalesceBag(); |
||||||
|
var_dump($missing['service'] ??= coalesceRhs('missing', 42)); |
||||||
|
var_dump($missing->data); |
||||||
|
|
||||||
|
echo "-- present --\n"; |
||||||
|
$present = new CoalesceBag(); |
||||||
|
$present->data['service'] = 7; |
||||||
|
var_dump($present['service'] ??= coalesceRhs('present', 99)); |
||||||
|
var_dump($present->data); |
||||||
|
|
||||||
|
echo "-- null --\n"; |
||||||
|
$null = new CoalesceBag(); |
||||||
|
$null->data['service'] = null; |
||||||
|
var_dump($null['service'] ??= coalesceRhs('null', 21)); |
||||||
|
var_dump($null->data); |
||||||
|
|
||||||
|
echo "-- interface --\n"; |
||||||
|
$interface = new CoalesceBag(); |
||||||
|
var_dump(coalesceThroughInterface($interface, 'typed', 13)); |
||||||
|
var_dump($interface->data); |
||||||
|
|
||||||
|
echo "-- object property container --\n"; |
||||||
|
$holder = new CoalesceHolder(new CoalesceBag()); |
||||||
|
var_dump($holder->bag['property'] ??= coalesceRhs('property', 14)); |
||||||
|
var_dump($holder->bag->data); |
||||||
|
|
||||||
|
echo "-- mixed object --\n"; |
||||||
|
$mixedObject = new CoalesceBag(); |
||||||
|
[$mixedObjectResult] = coalesceThroughMixed($mixedObject, 'dynamic', 31); |
||||||
|
var_dump($mixedObjectResult, $mixedObject->data); |
||||||
|
|
||||||
|
echo "-- mixed object present --\n"; |
||||||
|
$mixedPresent = new CoalesceBag(); |
||||||
|
$mixedPresent->data['dynamic'] = 8; |
||||||
|
[$mixedPresentResult] = coalesceThroughMixed($mixedPresent, 'dynamic', 99); |
||||||
|
var_dump($mixedPresentResult, $mixedPresent->data); |
||||||
|
|
||||||
|
echo "-- mixed object null --\n"; |
||||||
|
$mixedNull = new CoalesceBag(); |
||||||
|
$mixedNull->data['dynamic'] = null; |
||||||
|
[$mixedNullResult] = coalesceThroughMixed($mixedNull, 'dynamic', 33); |
||||||
|
var_dump($mixedNullResult, $mixedNull->data); |
||||||
|
|
||||||
|
echo "-- mixed array --\n"; |
||||||
|
[$mixedArrayResult, $mixedArray] = coalesceThroughMixed([], 'dynamic', 32); |
||||||
|
var_dump($mixedArrayResult, $mixedArray); |
||||||
|
|
||||||
|
echo "-- nullable object currently null --\n"; |
||||||
|
var_dump(coalesceThroughNullableObject(null)); |
||||||
|
|
||||||
|
echo "-- receiver and key once --\n"; |
||||||
|
$sideEffect = new CoalesceBag(); |
||||||
|
$receiverCalls = 0; |
||||||
|
$keyCalls = 0; |
||||||
|
var_dump(coalesceReceiver($sideEffect, $receiverCalls)[coalesceKey($keyCalls)] |
||||||
|
??= coalesceRhs('side', 55)); |
||||||
|
var_dump($receiverCalls, $keyCalls, $sideEffect->data); |
||||||
|
|
||||||
|
echo "-- unused result --\n"; |
||||||
|
$unused = new CoalesceBag(); |
||||||
|
$unused['value'] ??= coalesceRhs('unused', 66); |
||||||
|
var_dump($unused->data); |
||||||
|
|
||||||
|
echo "-- throwing rhs --\n"; |
||||||
|
$throwing = new CoalesceBag(); |
||||||
|
try { |
||||||
|
$throwing['value'] ??= throwingCoalesceRhs(); |
||||||
|
} catch (RuntimeException $e) { |
||||||
|
echo "caught\n"; |
||||||
|
} |
||||||
|
var_dump($throwing->data); |
||||||
|
|
||||||
|
echo "-- ArrayObject --\n"; |
||||||
|
$arrayObject = new ArrayObject(); |
||||||
|
var_dump($arrayObject['value'] ??= coalesceRhs('array-object', 77)); |
||||||
|
var_dump($arrayObject->getArrayCopy()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
-- missing -- |
||||||
|
exists:service |
||||||
|
rhs:missing |
||||||
|
set:service=42 |
||||||
|
int(42) |
||||||
|
array(1) { |
||||||
|
["service"]=> |
||||||
|
int(42) |
||||||
|
} |
||||||
|
-- present -- |
||||||
|
exists:service |
||||||
|
get:service |
||||||
|
int(7) |
||||||
|
array(1) { |
||||||
|
["service"]=> |
||||||
|
int(7) |
||||||
|
} |
||||||
|
-- null -- |
||||||
|
exists:service |
||||||
|
get:service |
||||||
|
rhs:null |
||||||
|
set:service=21 |
||||||
|
int(21) |
||||||
|
array(1) { |
||||||
|
["service"]=> |
||||||
|
int(21) |
||||||
|
} |
||||||
|
-- interface -- |
||||||
|
exists:typed |
||||||
|
rhs:interface |
||||||
|
set:typed=13 |
||||||
|
int(13) |
||||||
|
array(1) { |
||||||
|
["typed"]=> |
||||||
|
int(13) |
||||||
|
} |
||||||
|
-- object property container -- |
||||||
|
exists:property |
||||||
|
rhs:property |
||||||
|
set:property=14 |
||||||
|
int(14) |
||||||
|
array(1) { |
||||||
|
["property"]=> |
||||||
|
int(14) |
||||||
|
} |
||||||
|
-- mixed object -- |
||||||
|
exists:dynamic |
||||||
|
rhs:mixed |
||||||
|
set:dynamic=31 |
||||||
|
int(31) |
||||||
|
array(1) { |
||||||
|
["dynamic"]=> |
||||||
|
int(31) |
||||||
|
} |
||||||
|
-- mixed object present -- |
||||||
|
exists:dynamic |
||||||
|
get:dynamic |
||||||
|
int(8) |
||||||
|
array(1) { |
||||||
|
["dynamic"]=> |
||||||
|
int(8) |
||||||
|
} |
||||||
|
-- mixed object null -- |
||||||
|
exists:dynamic |
||||||
|
get:dynamic |
||||||
|
rhs:mixed |
||||||
|
set:dynamic=33 |
||||||
|
int(33) |
||||||
|
array(1) { |
||||||
|
["dynamic"]=> |
||||||
|
int(33) |
||||||
|
} |
||||||
|
-- mixed array -- |
||||||
|
rhs:mixed |
||||||
|
int(32) |
||||||
|
array(1) { |
||||||
|
["dynamic"]=> |
||||||
|
int(32) |
||||||
|
} |
||||||
|
-- nullable object currently null -- |
||||||
|
rhs:nullable-object |
||||||
|
int(34) |
||||||
|
-- receiver and key once -- |
||||||
|
receiver:1 |
||||||
|
key:1 |
||||||
|
exists:side |
||||||
|
rhs:side |
||||||
|
set:side=55 |
||||||
|
int(55) |
||||||
|
int(1) |
||||||
|
int(1) |
||||||
|
array(1) { |
||||||
|
["side"]=> |
||||||
|
int(55) |
||||||
|
} |
||||||
|
-- unused result -- |
||||||
|
exists:value |
||||||
|
rhs:unused |
||||||
|
set:value=66 |
||||||
|
array(1) { |
||||||
|
["value"]=> |
||||||
|
int(66) |
||||||
|
} |
||||||
|
-- throwing rhs -- |
||||||
|
exists:value |
||||||
|
rhs:throw |
||||||
|
caught |
||||||
|
array(0) { |
||||||
|
} |
||||||
|
-- ArrayObject -- |
||||||
|
rhs:array-object |
||||||
|
int(77) |
||||||
|
array(1) { |
||||||
|
["value"]=> |
||||||
|
int(77) |
||||||
|
} |
||||||
Loading…
Reference in new issue