Skip to content

[Php85] Skip nested compound assignment in ArrayFirstLastRector - #8425

Merged
TomasVotruba merged 1 commit into
rectorphp:mainfrom
Soean:fix/array-first-last-nested-assign-op
Sep 1, 2026
Merged

[Php85] Skip nested compound assignment in ArrayFirstLastRector#8425
TomasVotruba merged 1 commit into
rectorphp:mainfrom
Soean:fix/array-first-last-nested-assign-op

Conversation

@Soean

@Soean Soean commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Description

ArrayFirstLastRector rewrites $array[array_key_last($array)][0] += 1 to array_last($array)[0] += 1. array_last() returns a value rather than a reference, so the compound assignment writes into a temporary and is discarded, silently:

$a = [[0, 0, 0]];
array_last($a)[2] += 5;          // $a[0][2] stays 0
$a[array_key_last($a)][2] += 5;  // $a[0][2] becomes 5

shouldSkip() checks IS_ASSIGN_OP_VAR, but AssignedToNodeVisitor sets that attribute only on the outermost node of the assignment target. For $array[$key][0] += 1 that is the outer dim fetch, while the rule matches the inner one, so the guard does not apply. The plain assignment form was already skipped through PHPStan's Scope::isInExpressionAssign(), which is why the existing skip_as_assign_op fixture did not catch this.

Fix

AssignedToNodeVisitor now marks the whole dim fetch chain, which reflects what the assignment does: $array[$key][0] += 1 writes to $array[$key] as well. ArrayDimFetchToMethodCallRector reads the same attribute and benefits too.

Fixtures for both the compound and the plain nested form are added.

$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.
@TomasVotruba
TomasVotruba merged commit 832aaf1 into rectorphp:main Sep 1, 2026
44 checks passed
@TomasVotruba

Copy link
Copy Markdown
Member

Thank you

@Soean
Soean deleted the fix/array-first-last-nested-assign-op branch September 2, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants