Skip to content

Fix phpstan/phpstan#13247: iterable is equal to array|Traversable#5142

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

Fix phpstan/phpstan#13247: iterable is equal to array|Traversable#5142
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-sq1cag6

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When passing an iterable<K, V> to a function expecting array<K, V>|Traversable<K, V>, PHPStan incorrectly reported a type error and failed to resolve template types, even though iterable<K, V> is semantically equivalent to array<K, V>|Traversable<K, V>.

Changes

  • src/Type/IterableType.php: Changed isSubTypeOf() to decompose iterable<K,V> into array<K,V> | Traversable<K,V> (using GenericObjectType) instead of array<K,V> | (ObjectType(Traversable) & iterable<K,V>). The old intersection-based decomposition lost generic type information because the unparameterized ObjectType(Traversable) couldn't match against GenericObjectType(Traversable<K,V>).
  • src/Type/UnionType.php: Added early decomposition of IterableType in inferTemplateTypes() — when the received type is iterable<K,V>, it is decomposed into array<K,V> | Traversable<K,V> before template inference, allowing each union member to correctly infer template types from its matching component.
  • phpstan-baseline.neon: Updated instanceof IterableType ignore count from 1 to 2 for UnionType.php.
  • tests/PHPStan/Rules/Functions/data/bug-13247.php: New regression test data file.
  • tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php: Added testBug13247().

Root cause

Two separate issues caused the bug:

  1. Template inference failure: UnionType::inferTemplateTypes() tried to infer template types by delegating to each union member (ArrayType and GenericObjectType(Traversable)). Neither could infer templates from an IterableTypeArrayType::inferTemplateTypes rejects non-array types, and GenericObjectType::inferTemplateTypes requires TypeWithClassName. With templates unresolved, the parameter type fell back to template bounds.

  2. Subtype check failure: IterableType::isSubTypeOf() decomposed iterable<K,V> into array<K,V> | (ObjectType(Traversable) & iterable<K,V>). The intersection used an unparameterized ObjectType(Traversable), which couldn't properly match against GenericObjectType(Traversable<K,V>) in the target union, causing the subtype check to fail.

Test

Added a regression test testBug13247 that verifies passing iterable<K, V> to a function expecting array<K, V>|Traversable<K, V> produces no errors.

Fixes phpstan/phpstan#13247

- Fixed IterableType::isSubTypeOf to decompose iterable<K,V> into
  array<K,V>|GenericObjectType(Traversable<K,V>) instead of
  array<K,V>|(ObjectType(Traversable)&iterable<K,V>), preserving
  generic type information for proper subtype checking
- Fixed UnionType::inferTemplateTypes to decompose IterableType into
  array|Traversable union before template inference, allowing template
  types to be correctly resolved from iterable arguments
- Added regression test for phpstan/phpstan#13247

Closes phpstan/phpstan#13247
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-sq1cag6 branch March 8, 2026 12:16
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.

2 participants