Skip to content

Preserve TemplateType in IntersectionType::shuffleArray() when intersection is already a list#5694

Merged
ondrejmirtes merged 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-p7yl6bd
May 18, 2026
Merged

Preserve TemplateType in IntersectionType::shuffleArray() when intersection is already a list#5694
ondrejmirtes merged 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-p7yl6bd

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When an intersection type like T&list<int> was passed through shuffleArray() (used for sort, rsort, usort, shuffle), the template type T was lost because TemplateArrayType::shuffleArray() (inherited from ArrayType) constructs a new plain ArrayType, discarding the template identity. Since sorting a list produces another list with the same key structure, T should be preserved.

Changes

  • src/Type/IntersectionType.php: Modified shuffleArray() to check whether the intersection is already a list. If so, TemplateType members are passed through unchanged (since list→list preserves key structure). Non-list intersections still drop template types correctly.
  • tests/PHPStan/Analyser/nsrt/bug-14631.php: New regression test covering:
    • usort/sort/rsort/shuffle on T&list<int> → T preserved
    • usort/sort/rsort/shuffle on T&array<string, int> → T correctly dropped
    • uasort/asort/ksort on both list and non-list intersections → T always preserved (key-preserving sorts)
  • tests/PHPStan/Analyser/nsrt/sort.php: Updated existing assertion from buggy expectation (T dropped) to correct expectation (T preserved).

Root cause

IntersectionType::shuffleArray() called shuffleArray() on every member type unconditionally. For TemplateArrayType (which extends ArrayType), shuffleArray() creates a fresh ArrayType + AccessoryArrayListType intersection, losing the template identity. The fix recognizes that when the intersection is already a list, shuffling doesn't change the key structure, so template types can safely be kept.

Analogous cases probed

  • reverseArray: ArrayType::reverseArray() returns $this, so template types are already preserved. No bug.
  • makeListMaybe (key-preserving sorts): ArrayType::makeListMaybe() returns $this, so template types are already preserved. No bug.
  • mapValueType, popArray, shiftArray, sliceArray, spliceArray: These operations genuinely change the array's values or structure, so dropping template types is correct behavior. No bug.

Test

The regression test in bug-14631.php covers all combinations of sort type (list-making vs key-preserving) and input type (list intersection vs non-list intersection), verifying template type preservation/dropping in each case.

Fixes phpstan/phpstan#14631

…tersection is already a list

- In `IntersectionType::shuffleArray()`, skip calling `shuffleArray()` on
  `TemplateType` members when the intersection is already a list, since
  shuffling/sorting a list produces another list with the same structure,
  so the template identity is preserved.
- When the intersection is not a list (e.g. `T&array<string, int>`),
  template types are still correctly dropped because sorting reindexes
  keys to list format, changing the array structure.
- Update existing test in `sort.php` that asserted the buggy behavior
  (T being dropped from `T&list<...>` after usort).
- Key-preserving sorts (`uasort`, `asort`, `ksort`, etc.) already
  preserve template types correctly via `makeListMaybe()` since
  `ArrayType::makeListMaybe()` returns `$this`.
- Probed analogous operations (`reverseArray`, `mapValueType`,
  `popArray`, `shiftArray`, `sliceArray`, `spliceArray`) and confirmed
  they either already preserve template types or correctly drop them
  (when the operation genuinely changes array structure).
@staabm
Copy link
Copy Markdown
Contributor

staabm commented May 18, 2026

@phpstan-bot do we have a similar bug in one of the other Type::*array methods - e.g. Type::shiftArray etc. ?

@ondrejmirtes ondrejmirtes merged commit f0f92bc into phpstan:2.1.x May 18, 2026
654 of 656 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-p7yl6bd branch May 18, 2026 09:16
@ondrejmirtes
Copy link
Copy Markdown
Member

@staabm Sorry, I was faster, feel free to research it 😊

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