From a43828446c1a7c2c30ad643ade9341fc5b8e181e Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 24 Jul 2026 17:42:29 +0800 Subject: [PATCH] fix(parser): preserve references across all array write paths --- src/Parser/AssignOpTrait.php | 31 ++++++++-------- tests/compiler/ref/array-ref-assign-002.phpt | 17 +++++++++ tests/compiler/ref/array-ref-assign-003.phpt | 37 ++++++++++++++++++++ 3 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 tests/compiler/ref/array-ref-assign-003.phpt diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 5fdb32b4..745f9be2 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -42,24 +42,27 @@ trait AssignOpTrait $tmp = $this->genTmpVarName(); $this->addLocalVar($tmp, Type::VAR); - // 仅当目标是 php::Array 时使用 item/newItem: - // - item(dim, true) 直接返回元素 zval 地址,赋值时能穿透 IS_REFERENCE 写回, - // 修复 $arr = [&$x] / $arr[] = &$x 这类数组元素引用的写回问题; - // - 对于 ArrayAccess 对象(如 ArrayObject)或类型未知(VAR)的变量,item 不存在或语义不符, - // 必须继续使用 offsetSet(对象数组元素的引用写回由对象自身保证,编译器不负责)。 - $isPhpArray = $this->getVarType($array) === Type::ARRAY; + // 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) { - if ($isPhpArray) { - return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.newItem() = {$tmp}" . '), ' . $tmp . ')'; - } return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$tmp})" . '), ' . $tmp . ')'; } $dim = $this->parseIdentifier($left->dim); - if ($isPhpArray) { + if ($arrayType === Type::ARRAY) { return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.item({$dim}, true) = {$tmp}" . '), ' . $tmp . ')'; } + if ($arrayType === Type::VAR || $arrayType === Type::REF) { + $writeArray = "static_cast({$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 . ')'; } @@ -759,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)) { @@ -835,15 +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 已在函数开头通过 parseWritableIdentifier($expr->var) 正确计算, - // 这里不可再用 parseIdentifier() 覆盖,否则当左值是数组追加($arr[] = &$x) - // 或数组元素($arr[$k] = &$x)时会被当作读取而报错 "Cannot use [] for reading"。 $array = $this->parseWritableIdentifier($expr->expr->var); if ($expr->expr->dim == null) { $this->fatalError($expr, 'Cannot assign reference to array dim fetch without dim'); diff --git a/tests/compiler/ref/array-ref-assign-002.phpt b/tests/compiler/ref/array-ref-assign-002.phpt index 025e6bad..ac441ca0 100644 --- a/tests/compiler/ref/array-ref-assign-002.phpt +++ b/tests/compiler/ref/array-ref-assign-002.phpt @@ -2,6 +2,12 @@ array reference assignment to element: $arr[$k] = &$v writes back through reference --FILE-- value; + $propertyRefs[2] = &RefSource::$staticValue; + $propertyRefs[0] = 333; + $propertyRefs[2] = 444; + var_dump($source->value, RefSource::$staticValue); } ?> --EXPECT-- @@ -32,3 +47,5 @@ int(200) int(111) int(222) int(77) +int(333) +int(444) diff --git a/tests/compiler/ref/array-ref-assign-003.phpt b/tests/compiler/ref/array-ref-assign-003.phpt new file mode 100644 index 00000000..6e9f8ca4 --- /dev/null +++ b/tests/compiler/ref/array-ref-assign-003.phpt @@ -0,0 +1,37 @@ +--TEST-- +dynamically typed array element assignment preserves references and ArrayAccess writes +--FILE-- + +--EXPECT-- +int(123) +int(123) +int(234) +int(234) +int(456)