Fix phpstan/phpstan#11507: Type mismatch when explicitly setting an array key#5317
Open
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Open
Fix phpstan/phpstan#11507: Type mismatch when explicitly setting an array key#5317phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Conversation
… HasOffsetValueType - When setting an explicit array key on a generic array type (e.g. $item['foo'] = '100'), PHPStan adds HasOffsetValueType to the intersection, making the type structurally unequal to the base array type even though they describe the same type to the user - The invariance check in TemplateTypeVariance::isValidVariance used strict equals(), which failed due to the extra HasOffsetValueType accessory type - Added a fallback that strips HasOffsetValueType/HasOffsetType before comparing, so types differing only in offset tracking are treated as equal for invariance - New regression test in tests/PHPStan/Rules/Functions/data/bug-11507.php
staabm
reviewed
Mar 29, 2026
|
|
||
| if ($this->invariant()) { | ||
| $result = $a->equals($b); | ||
| if (!$result) { |
Contributor
There was a problem hiding this comment.
Adjust the corresponding equals-method implementation instead
Collaborator
Author
There was a problem hiding this comment.
And make phpstan also confirmed passing (exit code 0). Both verification runs for the final solution completed successfully. The commit has already been pushed to the PR branch.
…redTemplateType Instead of working around the equals check in TemplateTypeVariance::isValidVariance(), strip HasOffsetValueType and HasOffsetType during template type generalization in TemplateTypeHelper::generalizeInferredTemplateType(). This normalizes inferred template types before they reach the invariance check, so equals() naturally returns true without needing special handling. Changing IntersectionType::equals() globally was investigated but breaks scope tracking and type narrowing (introduces false positives in PHPStan self-analysis). The generalization approach is more targeted: it only affects inferred template type arguments for non-covariant templates, which is the correct normalization point for this precision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When mapping over a
Collection<non-empty-array<string>>and setting an explicit array key inside the callback ($item['foo'] = '100'), PHPStan incorrectly reported a type mismatch error: "should return Collection<non-empty-array> but returns Collection<non-empty-array>". The error showed identical types but still failed the invariance check.Changes
src/Type/Generic/TemplateTypeVariance.php: Added a fallback inisValidVariance()for invariant templates. Whenequals()fails, it stripsHasOffsetValueTypeandHasOffsetTypeaccessory types from both sides and re-checks equality. This ensures types that differ only in offset tracking information are treated as equal for invariance purposes.tests/PHPStan/Rules/Functions/data/bug-11507.php: Regression test with a Collection class using an invariant template parameter and amap()callback that sets an explicit array key.testBug11507intests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php.Root cause
When
$item['foo'] = '100'is executed on anon-empty-array<string, string>,ArrayType::setOffsetValueType()adds aHasOffsetValueType('foo', '100')to the type's intersection. This makes the typenon-empty-array<string, string>&hasOffsetValue('foo', '100')which is structurally different fromnon-empty-array<string, string>even though both describe asnon-empty-array<string>in error messages (AccessoryType instances are hidden in normal descriptions).The invariance check in
TemplateTypeVariance::isValidVariance()used$a->equals($b)which performs strict structural comparison. The extraHasOffsetValueTypecaused this to return false, triggering a false positive error about template covariance.The fix uses
TypeTraverserto replaceHasOffsetValueTypeandHasOffsetTypewithMixedType(which gets absorbed in intersections) before comparing, so types that only differ in offset tracking are considered equal for invariance.Test
Added a regression test (
testBug11507) that creates a Collection class with an invariantTValuetemplate parameter and amap()method. The test verifies that mapping overCollection<non-empty-array<string>>with a callback that sets$item['foo'] = '100'and returns the item does not produce a false positive return type error.Fixes phpstan/phpstan#11507