Skip to content

PHPStan incorrectly reports unhandled match cases when using ::class or get_class on a polymorphic variable #12882

Closed
phpstan/phpstan-src
#6748

Description

@manofearth

Bug report

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:

  1. The base class is abstract and cannot be instantiated directly
  2. All concrete child classes are handled in the match expression
  3. 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.

Code snippet that reproduces the problem

https://phpstan.org/r/8145653a-4c66-4066-86da-27df01b10dfc

Expected output

No errors!

Did PHPStan help you today? Did it make you happy in any way?

I use PHPStan in all my new projects. It helps me write fewer tests and sometimes finds bugs in my code, so yes, I'm happy that it exists.

Activity

  1. ondrejmirtes commented on Apr 16, 2025

    @ondrejmirtes
    Member

    This error would go away if you used sealed classes. Basically allowing BaseClass to be only extended by ChildA and ChildB.

    See:

  2. manofearth commented on Apr 18, 2025

    @manofearth
    Author

    @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.

  3. ondrejmirtes commented on Sep 12, 2025

    @ondrejmirtes
    Member

    This can now be reproduced with @phpstan-sealed: https://phpstan.org/r/f0d33456-ce22-44ef-b1a1-913c10844b20

  4. added this to the Easy fixes milestone on Sep 12, 2025
  5. stof commented on Sep 18, 2025

    @stof
    Contributor

    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).

  6. phpstan-bot commented on Mar 30, 2026

    @phpstan-bot
    Contributor

    @ondrejmirtes After the latest push in 2.2.x, PHPStan now reports different result with your code snippet:

    @@ @@
    -PHP 8.0 – 8.5 (2 errors)
    +PHP 8.0 – 8.5
     ==========
     
    -10: Match expression does not handle remaining value: class-string<BaseClass>&literal-string
    -17: Match expression does not handle remaining value: class-string<BaseClass>
    +No errors
     
     PHP 7.2 – 7.4 (2 errors)
     ==========
    Full report

    PHP 8.0 – 8.5

    No errors

    PHP 7.2 – 7.4 (2 errors)

    Line Error
    11 Syntax error, unexpected T_DOUBLE_ARROW on line 11
    12 Syntax error, unexpected T_DOUBLE_ARROW on line 12
  7. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions