diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index dabef684..745f9be2 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -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({$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'); diff --git a/tests/compiler/ref/array-ref-assign-001.phpt b/tests/compiler/ref/array-ref-assign-001.phpt new file mode 100644 index 00000000..3273f29e --- /dev/null +++ b/tests/compiler/ref/array-ref-assign-001.phpt @@ -0,0 +1,29 @@ +--TEST-- +array reference assignment: append and element assignment write back through reference +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + &int(123) + [1]=> + &int(456) + [2]=> + int(3) +} +array(2) { + [0]=> + &int(123) + [1]=> + &int(456) +} diff --git a/tests/compiler/ref/array-ref-assign-002.phpt b/tests/compiler/ref/array-ref-assign-002.phpt new file mode 100644 index 00000000..ac441ca0 --- /dev/null +++ b/tests/compiler/ref/array-ref-assign-002.phpt @@ -0,0 +1,51 @@ +--TEST-- +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-- +int(100) +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)