Skip to content

Fix phpstan/phpstan#12601: Array keys cannot be narrowed to non-empty-string when used in templates#5314

Open
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fi1y9zi
Open

Fix phpstan/phpstan#12601: Array keys cannot be narrowed to non-empty-string when used in templates#5314
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fi1y9zi

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When an array<non-empty-string, non-empty-string> was passed to a generic constructor like new ArrayIterator(...), the key type non-empty-string was incorrectly widened to string. Similarly, int<0, max> keys were widened to int. This fix preserves non-constant scalar subtypes during template type inference.

Changes

  • Modified src/Type/Generic/TemplateTypeHelper.php: Removed the overly broad first condition in generalizeInferredTemplateType() that generalized all scalar types when the template bound was array-key. The remaining condition already handles constant values correctly.
  • Updated tests/PHPStan/Analyser/nsrt/generics-do-not-generalize.php: Changed expected types from ArrayIterator<int, string> to ArrayIterator<int<0, max>, string> since list keys (int<0, max>) should no longer be generalized.
  • Added tests/PHPStan/Analyser/nsrt/bug-12601.php: Regression test covering both non-empty-string and int<0, max> key types with ArrayIterator.

Root cause

In TemplateTypeHelper::generalizeInferredTemplateType(), there were two conditions for generalizing inferred template types. The first condition ($type->isScalar()->yes() && $isArrayKey) was too broad — it generalized ALL scalar types when the template bound was array-key (int|string). This caused non-constant scalar subtypes like non-empty-string and int<0, max> to be generalized to string and int respectively. The fix removes this condition, leaving only the second condition that correctly targets constant values for generalization.

Test

Added tests/PHPStan/Analyser/nsrt/bug-12601.php with two test classes implementing IteratorAggregate:

  • One with array<non-empty-string, non-empty-string> keys — verifies ArrayIterator preserves non-empty-string key type
  • One with array<int<0, max>, non-empty-string> keys — verifies ArrayIterator preserves int<0, max> key type

Fixes phpstan/phpstan#12601

- Stop generalizing non-constant scalar subtypes (non-empty-string, int<0, max>) when inferring template types bounded by array-key
- The previous logic generalized ALL scalars for array-key bounds, but only constant values need generalization
- Updated existing test expectations in generics-do-not-generalize.php to reflect correct behavior
- New regression test in tests/PHPStan/Analyser/nsrt/bug-12601.php
Copy link
Copy Markdown
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a test case from phpstan.org/r/c448c867 verifying that when a
list<string> is passed to new ArrayIterator(), the inferred key type
is int<0, max> (not generalized to int).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Already handled — make tests passed (11677 tests, 78915 assertions). The commit with the requested test case has been pushed.

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