Merge pull request 'fix(parser): 修复数组元素引用赋值写回问题' (#33) from parser-fix-array-element-ref-assign-writeback into master

Reviewed-on: #33
pull/40/head
韩天峰 1 month ago
commit 80c52e0ed5
  1. 22
      src/Parser/AssignOpTrait.php
  2. 29
      tests/compiler/ref/array-ref-assign-001.phpt
  3. 51
      tests/compiler/ref/array-ref-assign-002.phpt
  4. 37
      tests/compiler/ref/array-ref-assign-003.phpt

@ -42,11 +42,27 @@ trait AssignOpTrait
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, Type::VAR);
// item(dim, true) updates an existing reference's value, while offsetSet()
// replaces the array bucket and breaks the reference. Keep offsetSet() for
// ArrayAccess objects; dynamically typed/reference containers need a
// runtime array check because either representation is possible.
$arrayType = $this->getVarType($array);
if ($left->dim === null) {
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$tmp})" . '), ' . $tmp . ')';
}
$dim = $this->parseIdentifier($left->dim);
if ($arrayType === Type::ARRAY) {
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.item({$dim}, true) = {$tmp}" . '), ' . $tmp . ')';
}
if ($arrayType === Type::VAR || $arrayType === Type::REF) {
$writeArray = "static_cast<void>({$array}.item({$dim}, true) = {$tmp})";
$writeOther = "{$array}.offsetSet({$dim}, {$tmp})";
return $code . '((' . $tmp . ' = ' . $value . ', '
. "({$array}.isArray() ? {$writeArray} : {$writeOther})"
. '), ' . $tmp . ')';
}
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')';
}
@ -746,6 +762,9 @@ trait AssignOpTrait
}
$left = $this->parseWritableIdentifier($expr->var);
// Keep this write-context form for every RHS kind. Re-parsing it as a
// read later breaks append and missing-key targets such as
// `$array[] =& $source`.
if ($this->isVarExpr($expr->var)) {
if (!$this->hasVar($left)) {
@ -822,13 +841,10 @@ trait AssignOpTrait
}
}
} elseif ($this->isPropertyFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$rightExpr = $tmpVar . ' = ' . $this->emitDynamicPropertyFetchRef($expr->expr, $expr);
} elseif ($this->isStaticPropertyFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$rightExpr = $tmpVar . ' = ' . $this->emitStaticPropertyFetchRef($expr->expr, $expr);
} elseif ($this->isArrayDimFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$array = $this->parseWritableIdentifier($expr->expr->var);
if ($expr->expr->dim == null) {
$this->fatalError($expr, 'Cannot assign reference to array dim fetch without dim');

@ -0,0 +1,29 @@
--TEST--
array reference assignment: append and element assignment write back through reference
--FILE--
<?php
function main()
{
$arr1 = [1, 2, 3];
$arr2 = [&$arr1[0]];
$arr2[0] = 123;
$arr2[] = &$arr1[1];
$arr2[1] = 456;
var_dump($arr1, $arr2);
}
?>
--EXPECT--
array(3) {
[0]=>
&int(123)
[1]=>
&int(456)
[2]=>
int(3)
}
array(2) {
[0]=>
&int(123)
[1]=>
&int(456)
}

@ -0,0 +1,51 @@
--TEST--
array reference assignment to element: $arr[$k] = &$v writes back through reference
--FILE--
<?php
class RefSource
{
public $value = 30;
public static $staticValue = 40;
}
function main()
{
$x = 10;
$y = 20;
$arr = [1, 2, 3];
$arr[0] = &$x; // 覆盖已有元素为引用
$arr[5] = &$y; // 新建元素为引用
$x = 100;
$y = 200;
var_dump($arr[0], $arr[5]); // 100, 200
// 通过元素引用写回
$arr[0] = 111;
$arr[5] = 222;
var_dump($x, $y); // 111, 222
// 嵌套:引用赋值到多维数组元素
$z = 7;
$m = [[1], [2]];
$m[0][0] = &$z;
$z = 77;
var_dump($m[0][0]); // 77
// 左侧数组追加/元素写入不可因右侧是属性引用而被重新按读取解析
$source = new RefSource();
$propertyRefs = [];
$propertyRefs[] = &$source->value;
$propertyRefs[2] = &RefSource::$staticValue;
$propertyRefs[0] = 333;
$propertyRefs[2] = 444;
var_dump($source->value, RefSource::$staticValue);
}
?>
--EXPECT--
int(100)
int(200)
int(111)
int(222)
int(77)
int(333)
int(444)

@ -0,0 +1,37 @@
--TEST--
dynamically typed array element assignment preserves references and ArrayAccess writes
--FILE--
<?php
function writeElement(mixed $container, mixed $key, mixed $value): void
{
$container[$key] = $value;
}
function writeReferencedContainer(mixed &$container, mixed $value): void
{
$container[0] = $value;
}
function main()
{
$referenced = 10;
$array = [&$referenced];
writeElement($array, 0, 123);
var_dump($referenced, $array[0]);
$referencedAgain = 20;
$arrayByReference = [&$referencedAgain];
writeReferencedContainer($arrayByReference, 234);
var_dump($referencedAgain, $arrayByReference[0]);
$object = new ArrayObject();
writeElement($object, 'key', 456);
var_dump($object['key']);
}
?>
--EXPECT--
int(123)
int(123)
int(234)
int(234)
int(456)
Loading…
Cancel
Save