Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors#5070
Open
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Open
Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors#5070phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Conversation
- Fixed ArrayType constructor converting StrictMixedType key to regular UnionType instead of BenevolentUnionType, which caused loss of benevolent semantics during RuleLevelHelper type transformation - Added BenevolentUnionType handling in TemplateTypeTrait::inferTemplateTypes() to filter matching types from a benevolent union when resolving template bounds - New regression test in tests/PHPStan/Rules/Methods/data/bug-9732.php Closes phpstan/phpstan#9732
2ff059d to
2f36068
Compare
staabm
reviewed
Feb 28, 2026
| ]))->union($map); | ||
| } | ||
|
|
||
| if ($receivedType instanceof UnionType) { |
Contributor
There was a problem hiding this comment.
can we drop the previous if on line 285 than? seems like its redundant now?
Contributor
There was a problem hiding this comment.
It's not redundant, the previous if will works
- For any non UnionType
- For BenevolentUnionType
- For UnionType in which the whole type is superTypeOf
aafdc3c to
2f36068
Compare
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 passing
$GLOBALS(which has aBenevolentUnionType(int|string)key type) to a function expectingarray<TKeyType of string, TValueType>, PHPStan incorrectly reported two false positive errors:argument.templateType)argument.type)Changes
src/Type/ArrayType.php: Changed theStrictMixedTypekey normalization in the constructor to produceBenevolentUnionTypeinstead of regularUnionType. This preserves benevolent semantics whenRuleLevelHelper::transformAcceptedType()traverses array types and converts implicitMixedTypetoStrictMixedType.src/Type/Generic/TemplateTypeTrait.php: Added handling forBenevolentUnionTypeininferTemplateTypes(). When the received type is a benevolent union that doesn't fully satisfy the template bound, we filter to only the inner types that match the bound and use those for template resolution.tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php: AddedtestBug9732test method.tests/PHPStan/Rules/Methods/data/bug-9732.php: Added regression test data file.Root cause
Two separate issues combined to produce the false positives:
Issue 1 (argument.type): The
ArrayTypeconstructor normalizesBenevolentUnionType(int, string)keys toMixedTypefor storage, andgetIterableKeyType()reconstructs them. However, duringRuleLevelHelper::transformAcceptedType(), thetransformCommonTypetraversal converts the storedMixedTypekey toStrictMixedType(whencheckImplicitMixedis true). WhenArrayType::traversecreates a newArrayTypewith theStrictMixedTypekey, the constructor was converting it to a regularUnionType([StringType, IntegerType])instead ofBenevolentUnionType. This caused theaccepts()check to fail because a regularUnionType(int, string)is not fully accepted byStringTypewhencheckForUnionis true.Issue 2 (argument.templateType):
TemplateTypeTrait::inferTemplateTypes()checks if the template bound (string) is a supertype of the received type (BenevolentUnionType(int, string)). SinceStringType->isSuperTypeOf(BenevolentUnionType(int, string))returnsMaybe(notYes), the template type was not resolved. The fix filters the benevolent union to only the types matching the bound (stringin this case) and uses the filtered type for template resolution.Test
Added regression test
testBug9732inCallStaticMethodsRuleTestthat passes$GLOBALSto a static method with template typesTKeyType of stringandTValueType, expecting no errors.Fixes phpstan/phpstan#9732