Skip to content

Do not assign ErrorType back to array when assign-op result is an error#5476

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

Do not assign ErrorType back to array when assign-op result is an error#5476
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-k7riy8g

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When multiple assign-ops (e.g., +=) are performed on different keys of the same array, only the first operation was flagged as an error. Subsequent operations on sibling keys were silently missed because the first assign-op's ErrorType result corrupted the array's value type.

Changes

  • Modified src/Analyser/ExprHandler/AssignHandler.php to detect when an assign-op produces ErrorType and preserve the original element type instead of assigning ErrorType back:

    • ArrayDimFetch branch (line ~421): Recovers original type by traversing the offset chain from the base variable type
    • ExistingArrayDimFetch branch (line ~779): Same recovery pattern for the existing-offset path
    • Variable branch (line ~242): Preserves the variable's original type when assign-op result is ErrorType
  • Added rule test tests/PHPStan/Rules/Operators/data/bug-10349.php covering:

    • Multiple += in a foreach loop (issue 1-A from the report)
    • Mixed narrowed/erroneous += in a foreach loop (issue 1-B)
    • Multiple += outside a loop
    • Simple (non-nested) array with multiple +=
    • Other assign-ops: -=, *=, /=, %=, .=, <<=
  • Added NSRT test tests/PHPStan/Analyser/nsrt/bug-10349.php verifying the array element type is preserved after an error-producing assign-op

Root cause

ErrorType extends MixedType. When an assign-op like (bool|float|int|string) += int produces ErrorType as its result type, this ErrorType was assigned back to the array element via setOffsetValueType/setExistingOffsetValueType. Inside these methods, the new value type is unioned with the existing value type via TypeCombinator::union. Since ErrorType (as a MixedType) absorbs all other types in a union, the array's entire value type became MixedType. All subsequent accesses to any key of that array then returned MixedType instead of the original declared type, suppressing further error reports.

The fix preserves the original element type when the assign-op result is ErrorType, since at runtime a failed operation (TypeError) would leave the variable unchanged.

Analogous cases probed

  • Property assign-ops ($obj->prop += $x): Not vulnerable — properties don't use nested offset type unwinding, so ErrorType doesn't propagate to sibling properties.
  • InvalidComparisonOperationRule / InvalidUnaryOperationRule: Not affected — these rules only report errors without assigning types back.
  • OffsetAccessAssignOpRule: Not affected — only validates and reports, doesn't assign types.

Test

  • Rule test verifies all assign-op variants (+=, -=, *=, /=, %=, .=, <<=) report errors on every array key, not just the first
  • NSRT test verifies sibling array element types are preserved after an error-producing assign-op

Fixes phpstan/phpstan#10349

…rror

- When an assign-op like `+=` on an array element produces ErrorType
  (e.g., `(bool|float|int|string) + int`), the ErrorType was assigned back
  to the array via `setExistingOffsetValueType`/`setOffsetValueType`. Since
  ErrorType extends MixedType, `TypeCombinator::union` absorbed the original
  value type, corrupting the array type to MixedType for ALL elements.
- This caused subsequent assign-ops on different keys of the same array to
  see MixedType instead of the original type, suppressing error reports.
- Fix: in `AssignHandler::processAssignVar()`, when the assign-op result is
  ErrorType, preserve the original element type by traversing the offset
  chain to recover it.
- Applied fix in three locations: ArrayDimFetch branch, ExistingArrayDimFetch
  branch, and Variable branch.
- Covers all assign-op types: `+=`, `-=`, `*=`, `/=`, `%=`, `.=`, `<<=`,
  `>>=`, `&=`, `|=`, `^=`, `**=`.
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