From c50972b1585b046192418d2735ddf0d84aae8f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20W=C3=BCnsch?= Date: Tue, 1 Sep 2026 11:27:54 +0200 Subject: [PATCH] [Php85] Skip nested compound assignment in ArrayFirstLastRector $array[array_key_last($array)][0] += 1 was rewritten to array_last($array)[0] += 1. That reads the last element into a temporary and writes to the copy, so the assignment is lost. PHP reports nothing, which makes the change silent. IS_ASSIGN_OP_VAR was only set on the outermost node of the assignment target, so the rule saw the inner dim fetch as an ordinary read. Marking the whole chain reflects what happens: $array[$key][0] += 1 writes to $array[$key] too. The plain assignment form was already skipped through PHPStan's Scope::isInExpressionAssign(); a fixture now covers it so that stays true. --- .../Fixture/skip_as_nested_assign.php.inc | 13 +++++++++++++ .../Fixture/skip_as_nested_assign_op.php.inc | 13 +++++++++++++ .../NodeVisitor/AssignedToNodeVisitor.php | 19 ++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 rules-tests/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector/Fixture/skip_as_nested_assign.php.inc create mode 100644 rules-tests/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector/Fixture/skip_as_nested_assign_op.php.inc diff --git a/rules-tests/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector/Fixture/skip_as_nested_assign.php.inc b/rules-tests/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector/Fixture/skip_as_nested_assign.php.inc new file mode 100644 index 00000000000..2c5efa8170a --- /dev/null +++ b/rules-tests/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector/Fixture/skip_as_nested_assign.php.inc @@ -0,0 +1,13 @@ +var->setAttribute(AttributeKey::IS_ASSIGN_OP_VAR, true); + $this->markAssignOpVar($node->var); return null; } @@ -54,4 +56,19 @@ public function enterNode(Node $node): ?Node return null; } + + /** + * Marks the whole dim fetch chain, not just the outermost node: $array[$key][0] += 1 writes + * to $array[$key] as well, so a rule that rewrites the inner fetch would change a write + * into a read of a temporary value. + */ + private function markAssignOpVar(Expr $expr): void + { + $expr->setAttribute(AttributeKey::IS_ASSIGN_OP_VAR, true); + + while ($expr instanceof ArrayDimFetch) { + $expr = $expr->var; + $expr->setAttribute(AttributeKey::IS_ASSIGN_OP_VAR, true); + } + } }