Skip to content

Fix phpstan/phpstan#14347: Regression: Setting array-key in loop doesn't persist with other actions in between#5287

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-8aipe1y
Open

Fix phpstan/phpstan#14347: Regression: Setting array-key in loop doesn't persist with other actions in between#5287
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-8aipe1y

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When initializing an array entry with ??= in a loop, modifying a different array key in between would cause PHPStan to lose track of the initialized key, resulting in a false positive offsetAccess.notFound error.

Changes

  • Added getDynamicArrayDimFetchExpressionTypes() method to src/Analyser/MutatingScope.php to collect expression types for array dim fetches with non-scalar (dynamic) dim expressions
  • Added restoreExpressionTypes() method to src/Analyser/MutatingScope.php to directly restore expression type holders without triggering parent-type side effects
  • Modified src/Analyser/ExprHandler/AssignHandler.php to save and restore dynamic array dim fetch expression types around assignVariable calls during array dim fetch assignments
  • New regression test in tests/PHPStan/Rules/Arrays/data/bug-14347.php

Root cause

When processing an array dim fetch assignment like $arr['all']++, PHPStan computes the new array type and calls assignVariable(), which internally calls invalidateExpression() on the variable. This invalidation removes ALL expression types containing that variable, including $arr[$dynamicKey] which was set by a prior ??= operation.

The NonexistentOffsetInArrayDimFetchRule checks $scope->hasExpressionType($node) at line 72 to skip the error when the expression was previously assigned. With the expression type invalidated, this check fails and the rule reports a false positive.

The fix preserves expression types for dynamic (non-scalar) array dim fetches before the assignVariable call and restores them afterward. Only dynamic dim fetches are preserved to avoid interfering with condition-narrowed types on constant keys (like those from array_key_exists checks).

Test

Added tests/PHPStan/Rules/Arrays/data/bug-14347.php with two functions: countCards (working order) and countCardsBroken (broken order where ??= comes before modifying another key). Both should analyze cleanly with reportPossiblyNonexistentConstantArrayOffset = true.

Fixes phpstan/phpstan#14347

… different key

- When an array dim fetch assignment (e.g. $arr['all']++) reassigns the root variable,
  expression types for dynamic array dim fetches (e.g. $arr[$card->value]) are now preserved
- Added getDynamicArrayDimFetchExpressionTypes() and restoreExpressionTypes() to MutatingScope
- New regression test in tests/PHPStan/Rules/Arrays/data/bug-14347.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant