Skip to content

Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors#5070

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

Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors#5070
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-wq026g0

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When passing $GLOBALS (which has a BenevolentUnionType(int|string) key type) to a function expecting array<TKeyType of string, TValueType>, PHPStan incorrectly reported two false positive errors:

  1. "Unable to resolve the template type TKeyType" (argument.templateType)
  2. "Parameter expects array<string, mixed>, array given" (argument.type)

Changes

  • src/Type/ArrayType.php: Changed the StrictMixedType key normalization in the constructor to produce BenevolentUnionType instead of regular UnionType. This preserves benevolent semantics when RuleLevelHelper::transformAcceptedType() traverses array types and converts implicit MixedType to StrictMixedType.
  • src/Type/Generic/TemplateTypeTrait.php: Added handling for BenevolentUnionType in inferTemplateTypes(). 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: Added testBug9732 test 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 ArrayType constructor normalizes BenevolentUnionType(int, string) keys to MixedType for storage, and getIterableKeyType() reconstructs them. However, during RuleLevelHelper::transformAcceptedType(), the transformCommonType traversal converts the stored MixedType key to StrictMixedType (when checkImplicitMixed is true). When ArrayType::traverse creates a new ArrayType with the StrictMixedType key, the constructor was converting it to a regular UnionType([StringType, IntegerType]) instead of BenevolentUnionType. This caused the accepts() check to fail because a regular UnionType(int, string) is not fully accepted by StringType when checkForUnion is true.

Issue 2 (argument.templateType): TemplateTypeTrait::inferTemplateTypes() checks if the template bound (string) is a supertype of the received type (BenevolentUnionType(int, string)). Since StringType->isSuperTypeOf(BenevolentUnionType(int, string)) returns Maybe (not Yes), the template type was not resolved. The fix filters the benevolent union to only the types matching the bound (string in this case) and uses the filtered type for template resolution.

Test

Added regression test testBug9732 in CallStaticMethodsRuleTest that passes $GLOBALS to a static method with template types TKeyType of string and TValueType, expecting no errors.

Fixes phpstan/phpstan#9732

github-actions bot and others added 2 commits February 27, 2026 14:33
- 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
@VincentLanglet VincentLanglet self-assigned this Feb 28, 2026
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-wq026g0 branch from 2ff059d to 2f36068 Compare February 28, 2026 10:38
]))->union($map);
}

if ($receivedType instanceof UnionType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we drop the previous if on line 285 than? seems like its redundant now?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not redundant, the previous if will works

  • For any non UnionType
  • For BenevolentUnionType
  • For UnionType in which the whole type is superTypeOf

@staabm staabm changed the title Fix #9732: BenevolentUnionType with generics produce confusing errors Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors Feb 28, 2026
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-wq026g0 branch from aafdc3c to 2f36068 Compare February 28, 2026 12:29
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