Skip to content

PHPStan 2.2.2 regression: Type narrowing $this to *NEVER* #14804

@kallesommernielsen

Description

@kallesommernielsen

Bug report

Hi

I recently got a dependabot notification[1] about upgrading to PHPStan 2.2.2, and it had 2 interesting discoveries[2] for me.

 ------ ---------------------------------------- 
  Line   src/Tuxxedo/Container/Container.php     
 ------ ---------------------------------------- 
  243    Cannot access offset *NEVER* on mixed.  
         🪪  offsetAccess.nonOffsetAccessible    
  245    Cannot access offset *NEVER* on mixed.  
         🪪  offsetAccess.nonOffsetAccessible    

Specifically this code snippet (line 238-249):

            if (isset($this->initializers[$className])) {
                /** @var TClassName $instance */
                $instance = ($this->initializers[$className])($this, $arguments);

                if ($lifecycle === Lifecycle::PERSISTENT) {
                    unset($this->initializers[$className]);

                    $this->persistentDependencies[$className] = $instance;
                }

                return $instance;
            }

The moment PHPStan goes inside the conditional on line 242, it seems to narrow $this down to *NEVER* which I assume is some sort of dead code indicator, yet the branch itself is not reported as dead, in fact when I add some \PHPStan\dumpType() calls around it, I get:

Before the if:

  242    Dumped type #1: Tuxxedo\Container\Lifecycle::PERSISTENT|Tuxxedo\Container\Lifecycle::TRANSIENT  
         🪪  phpstan.dumpType (non-ignorable)                                                            
         at src/Tuxxedo/Container/Container.php:242                                                      
  242    Dumped type #2: $this(Tuxxedo\Container\Container)                                              
         🪪  phpstan.dumpType (non-ignorable)                                                            
         at src/Tuxxedo/Container/Container.php:242                  

Inside the if:

  245    Dumped type #1: Tuxxedo\Container\Lifecycle::PERSISTENT                                         
         🪪  phpstan.dumpType (non-ignorable)                                                            
         at src/Tuxxedo/Container/Container.php:245                                                      
  245    Dumped type #2: *NEVER*                                                                         
         🪪  phpstan.dumpType (non-ignorable)                                                            
         at src/Tuxxedo/Container/Container.php:245                            

So it does narrow the $lifecycle union correctly but somehow voids $this.

I am terribly sorry but I was unable to make a short snippet on phpstan.org/try, so the easiest reproducer should be:

git clone git@github.com:tuxxedo-engine/framework.git
cd framework/
composer install
composer require --dev phpstan/phpstan:2.2.2
./vendor/bin/phpstan

The only requirement should be PHP 8.5 and nothing else out of the box besides some standard extensions.

I was unable to reproduce this on 2.2.1, please let me know what I can do to help find the issue or provide more data

[1] tuxxedo-engine/framework#104
[2] https://github.com/tuxxedo-engine/framework/actions/runs/27110363964/job/80006963227?pr=104

Code snippet that reproduces the problem

No response

Expected output

No errors

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

It always does, even in the hard times :>

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions