You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PHPStan incorrectly reports unhandled match cases when using ::class or get_class on a polymorphic variable #12882
When using a match expression with $object::class or get_class where $object is typed as a base class, PHPStan incorrectly reports an unhandled match error even when all potential concrete subclasses are handled.
A minimal reproduction case is attached:
The error for $object::class is: "Match expression does not handle remaining value: class-string&literal-string"
For get_class($object) error is similar: "Match expression does not handle remaining value: class-string"
This occurs even though:
The base class is abstract and cannot be instantiated directly
All concrete child classes are handled in the match expression
Adding the base class to the match arms does not resolve the error. Since the base class is abstract, it should not need to be included in the match cases at all.
My main concern is that I want PHPStan to help me maintain exhaustive matching - when new subclasses of the base class are added, I want PHPStan to warn me that I need to add a new match case. If I add a default branch, I lose this very useful exhaustiveness checking capability.
@ondrejmirtes Thank you for your quick response, though I'm a bit disappointed to see this issue closed so quickly.
I've actually tried the suggested solution - I installed the jiripudil/phpstan-sealed-classes extension and properly configured it in my project. After marking my base class as sealed, I did receive several new instanceof.alwaysTrue warnings, confirming the extension is working correctly.
However, this did not resolve the original issue. I still get the same error with the match expression:
"Match expression does not handle remaining value: class-string&literal-string"
The sealed classes extension seems to handle instanceof checks well, but doesn't properly inform the match expression analyzer that all possible subclasses are accounted for.
I believe this is still a legitimate issue with PHPStan's match expression analysis that should be addressed. Even with sealed classes properly configured, PHPStan should be able to determine that all possible concrete implementations have been handled in the match expression.
Could you please reconsider this issue? The suggested workaround doesn't actually solve the problem.
In the case of sealed classes/interfaces, $v::class should produce V|class-string<VChild1>|class-string<VChild2> in case V is not abstract, and class-string<VChild1>|class-string<VChild2> if the class is abstract (my example assumes that V has 2 allowed subtypes VChild1 and VChild2), as those will be the only values of class-string<V> corresponding to concrete classes.
This replacement need to be recursive in case some of those child types are also sealed.
Combined with another simplification resolving class-string<T> to T when T is a final class, this would produce the right behavior for match (this already exists, as hinted by https://phpstan.org/r/7e473786-a462-4ad3-91bb-78293eb68a2d where I replaced the BaseClass type by a union type of all its sealed types, which is equivalent as BaseClass is abstract).
Activity
ondrejmirtes commentedon Apr 16, 2025
This error would go away if you used sealed classes. Basically allowing BaseClass to be only extended by ChildA and ChildB.
See:
manofearth commentedon Apr 18, 2025
@ondrejmirtes Thank you for your quick response, though I'm a bit disappointed to see this issue closed so quickly.
I've actually tried the suggested solution - I installed the
jiripudil/phpstan-sealed-classesextension and properly configured it in my project. After marking my base class as sealed, I did receive several newinstanceof.alwaysTruewarnings, confirming the extension is working correctly.However, this did not resolve the original issue. I still get the same error with the match expression:
"Match expression does not handle remaining value: class-string&literal-string"
The sealed classes extension seems to handle instanceof checks well, but doesn't properly inform the match expression analyzer that all possible subclasses are accounted for.
I believe this is still a legitimate issue with PHPStan's match expression analysis that should be addressed. Even with sealed classes properly configured, PHPStan should be able to determine that all possible concrete implementations have been handled in the match expression.
Could you please reconsider this issue? The suggested workaround doesn't actually solve the problem.
ondrejmirtes commentedon Sep 12, 2025
This can now be reproduced with
@phpstan-sealed: https://phpstan.org/r/f0d33456-ce22-44ef-b1a1-913c10844b20stof commentedon Sep 18, 2025
In the case of sealed classes/interfaces,
$v::classshould produceV|class-string<VChild1>|class-string<VChild2>in caseVis not abstract, andclass-string<VChild1>|class-string<VChild2>if the class is abstract (my example assumes thatVhas 2 allowed subtypesVChild1andVChild2), as those will be the only values ofclass-string<V>corresponding to concrete classes.This replacement need to be recursive in case some of those child types are also sealed.
Combined with another simplification resolving
class-string<T>toTwhenTis a final class, this would produce the right behavior for match (this already exists, as hinted by https://phpstan.org/r/7e473786-a462-4ad3-91bb-78293eb68a2d where I replaced theBaseClasstype by a union type of all its sealed types, which is equivalent asBaseClassis abstract).phpstan-bot commentedon Mar 30, 2026
@ondrejmirtes After the latest push in 2.2.x, PHPStan now reports different result with your code snippet:
Full report
PHP 8.0 – 8.5
No errors
PHP 7.2 – 7.4 (2 errors)
Syntax error, unexpected T_DOUBLE_ARROW on line 11Syntax error, unexpected T_DOUBLE_ARROW on line 12Add regression test for #12882
Updated PHPStan to commit 61f6fe5fbab140e63d914412c221c40400c0207b