Skip to content

Fix phpstan/phpstan#14412: Match on ::class reports unhandled values for generic sealed class hierarchy#5369

Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-s0pzr5f
Mar 31, 2026
Merged

Fix phpstan/phpstan#14412: Match on ::class reports unhandled values for generic sealed class hierarchy#5369
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-s0pzr5f

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Match expressions on $foo::class against @phpstan-sealed class hierarchies with generic type parameters (@template) falsely reported "Match expression does not handle remaining values" even when all allowed subtypes were covered. The non-generic case was already fixed in #5305.

Changes

  • Modified GenericObjectType::changeSubtractedType() in src/Type/Generic/GenericObjectType.php to delegate to the parent ObjectType::changeSubtractedType() which contains the sealed type exhaustiveness logic
  • Updated phpstan-baseline.neon to account for the new instanceof ObjectType usage
  • Added regression test in tests/PHPStan/Rules/Comparison/data/bug-14412.php with test method in MatchExpressionRuleTest.php

Root cause

GenericObjectType::changeSubtractedType() was overriding the parent ObjectType::changeSubtractedType() without including its sealed type logic. When all allowed subtypes of a sealed hierarchy were subtracted from a generic object type (e.g., FooCov<string> minus BarCov minus BazCov), the parent's code would correctly return NeverType (marking the type as exhausted), but GenericObjectType simply created a new instance with the subtracted type set — never checking if the sealed hierarchy was fully covered. The fix delegates to the parent first and returns its result when it indicates exhaustiveness (NeverType) or reduces to a single remaining sealed subtype.

Test

Added a regression test with both covariant and invariant generic sealed hierarchies. Each hierarchy has two sealed subtypes, and the match expression covers both. The test expects no errors (empty expected-errors array), confirming the match is recognized as exhaustive.

Fixes phpstan/phpstan#14412

…rchies

- GenericObjectType::changeSubtractedType() now delegates to parent's sealed
  type logic, returning NeverType when all allowed subtypes are subtracted
- This fixes match expressions on $foo::class reporting "unhandled values"
  for generic @phpstan-sealed class hierarchies
- New regression test in tests/PHPStan/Rules/Comparison/data/bug-14412.php

Closes phpstan/phpstan#14412
@ondrejmirtes ondrejmirtes merged commit b70fb0f into phpstan:2.1.x Mar 31, 2026
651 of 653 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-s0pzr5f branch March 31, 2026 15:24
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