Skip to content

Fix phpstan/phpstan#13786: incorrect type-narrowing to *NEVER* inside array row#5263

Merged
staabm merged 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-8h9krij
Mar 21, 2026
Merged

Fix phpstan/phpstan#13786: incorrect type-narrowing to *NEVER* inside array row#5263
staabm merged 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-8h9krij

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

After a foreach loop that assigns to nested array dimensions with a union key type (e.g., $total[$id][$col] = '0' where $col is 'a'|'b'|'c'), assigning a new key outside the union (e.g., $total[$id]['d'] = '0') incorrectly narrowed the array type to *NEVER*.

Changes

  • Added !$offsetValueType->hasOffsetValueType($offsetType)->no() condition in src/Analyser/ExprHandler/AssignHandler.php to prevent using setExistingOffsetValueType when the offset is definitely not present in the array
  • Updated test expectation in tests/PHPStan/Analyser/nsrt/bug-13270b-php8.php to reflect more precise type inference (hasOffsetValue instead of hasOffset)
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-13786.php

Root cause

When processing $total[$id]['d'] = '0', the code checked whether the scope tracked the expression $total[$id] and, if so, used setExistingOffsetValueType to update it. However, setExistingOffsetValueType on ArrayType does not widen the key type (it keeps 'a'|'b'|'c'). Then HasOffsetValueType('d', '0') was intersected with the result, creating a contradiction (key 'd' cannot exist in an array with key type 'a'|'b'|'c'), which collapsed to *NEVER*.

The fix adds a check: when the array is known to NOT have the offset being assigned (hasOffsetValueType returns no), the code falls through to setOffsetValueType instead, which correctly widens the key type to include the new offset.

Test

Added tests/PHPStan/Analyser/nsrt/bug-13786.php which reproduces the original issue: a foreach loop assigns to $total[$id][$col] with $col: 'a'|'b'|'c', then $total[$id]['d'] = '0' is assigned outside the loop. The test verifies the type is correctly non-empty-array<'a'|'b'|'c'|'d', '0'> instead of *NEVER*.

Fixes phpstan/phpstan#13786

…nment

- Added hasOffsetValueType check in AssignHandler to prevent using setExistingOffsetValueType when offset is known to not exist in the array
- When the offset definitely doesn't exist (hasOffsetValueType returns no), fall through to setOffsetValueType which correctly widens the key type
- Updated bug-13270b-php8 test expectation to reflect more precise type inference (hasOffsetValue instead of hasOffset)
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13786.php
@staabm staabm requested a review from VincentLanglet March 21, 2026 07:37
@staabm staabm merged commit 7cb1163 into phpstan:2.1.x Mar 21, 2026
652 checks passed
@staabm staabm deleted the create-pull-request/patch-8h9krij branch March 21, 2026 08:34
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.

3 participants