phpstan / phpstan-doctrine

Doctrine extensions for PHPStan
MIT License
573 stars 95 forks source link

PHPStan crashes if Doctrine Column is a UnitEnum (PHPStan+Doctrine extension) #522

Open Firehed opened 5 months ago

Firehed commented 5 months ago

Bug report

This indicates an error in my code, but the crash is unexpected. I'm providing the bug report and stack trace as the PHPStan output requests, but through some quick trial-and-error I was able to determine that #[Mapping\Column] on a class property that's unsupported would trigger it.

Not sure if this would happen if Doctrine had a custom type mapper set which allows this to work (seeing the stack trace, it looks like probably), but I think PHPStan, or more specifically the Doctrine extension, should catch the assertion error and translate it into an analysis warning of some kind.

Simple reproduce code:

<?php

declare(strict_types=1);

namespace App\Entities;

use Doctrine\ORM\Mapping;

#[Mapping\Entity]
class Bug
{
    #[Mapping\Column]
    #[Mapping\Id]
    public string $id;

    #[Mapping\Column]
    public Status $status;

    public function causeError(): void
    {
        $this->a = 1; // This should error, instead PHPStan crashes
    }
}

enum Status
{
    case Working;
    case Broken;
}
# $ cat phpstan.neon
includes:
    # phpstan-baseline.neon
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon
    - vendor/phpstan/phpstan-deprecation-rules/rules.neon
    - vendor/phpstan/phpstan-doctrine/extension.neon
    - vendor/phpstan/phpstan-doctrine/rules.neon
parameters:
    excludePaths:
        - rector.php
        - vendor
    level: max
    paths:
        - .
    universalObjectCratesClasses:
        - Stripe\StripeObject
Internal error: Internal error: assert($type instanceof ReflectionNamedType) while analysing file (...)

     ## (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/DefaultTypedFieldMapper.php(62)
     #0 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/DefaultTypedFieldMapper.php(62): assert(false, 'assert($type in...')
     phpstan/phpstan#1 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php(1594): Doctrine\ORM\Mapping\DefaultTypedFieldMapper->validateAndComplete(Array, Object(ReflectionProperty))
     phpstan/phpstan#2 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php(1644): Doctrine\ORM\Mapping\ClassMetadataInfo->validateAndCompleteTypedFieldMapping(Array)
     phpstan/phpstan#3 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php(2723): Doctrine\ORM\Mapping\ClassMetadataInfo->validateAndCompleteFieldMapping(Array)
     phpstan/phpstan#4 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php(369): Doctrine\ORM\Mapping\ClassMetadataInfo->mapField(Array)
     phpstan/phpstan#5 (path)/vendor/phpstan/phpstan-doctrine/src/Doctrine/Mapping/MappingDriverChain.php(31): Doctrine\ORM\Mapping\Driver\AttributeDriver->loadMetadataForClass('App\\Entities\\In...', Object(Doctrine\ORM\Mapping\ClassMetadata))
     phpstan/phpstan#6 (path)/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(149): PHPStan\Doctrine\Mapping\MappingDriverChain->loadMetadataForClass('App\\Entities\\In...', Object(Doctrine\ORM\Mapping\ClassMetadata))
     phpstan/phpstan#7 (path)/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(343): Doctrine\ORM\Mapping\ClassMetadataFactory->doLoadMetadata(Object(Doctrine\ORM\Mapping\ClassMetadata), NULL, false, Array)
     phpstan/phpstan#8 (path)/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(225): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata('App\\Entities\\In...')
     phpstan/phpstan#9 (path)/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/ObjectMetadataResolver.php(129): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor('App\\Entities\\In...')
     phpstan/phpstan#10 (path)/vendor/phpstan/phpstan-doctrine/src/Rules/Doctrine/ORM/PropertiesExtension.php(27): PHPStan\Type\Doctrine\ObjectMetadataResolver->getClassMetadata('App\\Entities\\In...')
     phpstan/phpstan#11 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Rules/DeadCode/UnusedPrivatePropertyRule.php(100): PHPStan\Rules\Doctrine\ORM\PropertiesExtension->isAlwaysRead(Object(PHPStan\Reflection\Php\PhpPropertyReflection), 'amount')
     phpstan/phpstan#12 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(107): PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule->processNode(Object(PHPStan\Node\ClassPropertiesNode), Object(PHPStan\Analyser\MutatingScope))
     phpstan/phpstan#13 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(669): PHPStan\Analyser\FileAnalyser->PHPStan\Analyser\{closure}(Object(PHPStan\Node\ClassPropertiesNode), Object(PHPStan\Analyser\MutatingScope))
     phpstan/phpstan#14 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(401): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Class_), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
     phpstan/phpstan#15 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(640): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Namespace_), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure),
     Object(PHPStan\Analyser\StatementContext))
     phpstan/phpstan#16 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(370): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Namespace_), Object(PHPStan\Analyser\MutatingScope), Object(Closure),
     Object(PHPStan\Analyser\StatementContext))
     phpstan/phpstan#17 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(166): PHPStan\Analyser\NodeScopeResolver->processNodes(Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure))
     phpstan/phpstan#18 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(132): PHPStan\Analyser\FileAnalyser->analyseFile('/Users/firehed/...', Array, Object(PHPStan\Rules\LazyRegistry), Object(PHPStan\Collectors\Registry), NULL)
     phpstan/phpstan#19 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97): PHPStan\Command\WorkerCommand->PHPStan\Command\{closure}(Array)
     phpstan/phpstan#20 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/clue/ndjson-react/src/Decoder.php(117): _PHPStan_3d4486d07\Evenement\EventEmitter->emit('data', Array)
     phpstan/phpstan#21 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97): _PHPStan_3d4486d07\Clue\React\NDJson\Decoder->handleData(Array)
     phpstan/phpstan#22 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/Util.php(62): _PHPStan_3d4486d07\Evenement\EventEmitter->emit('data', Array)
     phpstan/phpstan#23 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php(97): _PHPStan_3d4486d07\React\Stream\Util::_PHPStan_3d4486d07\React\Stream\{closure}('{"action":"anal...')
     phpstan/phpstan#24 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/DuplexResourceStream.php(154): _PHPStan_3d4486d07\Evenement\EventEmitter->emit('data', Array)
     phpstan/phpstan#25 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(201): _PHPStan_3d4486d07\React\Stream\DuplexResourceStream->handleData(Resource id phpstan/phpstan#6555)
     phpstan/phpstan#26 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(173): _PHPStan_3d4486d07\React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
     phpstan/phpstan#27 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(98): _PHPStan_3d4486d07\React\EventLoop\StreamSelectLoop->run()
     phpstan/phpstan#28 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Command/Command.php(259): PHPStan\Command\WorkerCommand->execute(Object(_PHPStan_3d4486d07\Symfony\Component\Console\Input\ArgvInput),
     Object(_PHPStan_3d4486d07\Symfony\Component\Console\Output\ConsoleOutput))
     phpstan/phpstan#29 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(870): _PHPStan_3d4486d07\Symfony\Component\Console\Command\Command->run(Object(_PHPStan_3d4486d07\Symfony\Component\Console\Input\ArgvInput),
     Object(_PHPStan_3d4486d07\Symfony\Component\Console\Output\ConsoleOutput))
     phpstan/phpstan#30 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(261): _PHPStan_3d4486d07\Symfony\Component\Console\Application->doRunCommand(Object(PHPStan\Command\WorkerCommand), Object(_PHPStan_3d4486d07\Symfony\Component\Console\Input\ArgvInput),
     Object(_PHPStan_3d4486d07\Symfony\Component\Console\Output\ConsoleOutput))
     phpstan/phpstan#31 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(157): _PHPStan_3d4486d07\Symfony\Component\Console\Application->doRun(Object(_PHPStan_3d4486d07\Symfony\Component\Console\Input\ArgvInput),
     Object(_PHPStan_3d4486d07\Symfony\Component\Console\Output\ConsoleOutput))
     phpstan/phpstan#32 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(124): _PHPStan_3d4486d07\Symfony\Component\Console\Application->run()
     phpstan/phpstan#33 phar://(path)/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(125): _PHPStan_3d4486d07\{closure}()
     phpstan/phpstan#34 (path)/vendor/phpstan/phpstan/phpstan(8): require('phar:///Users/f...')
     phpstan/phpstan#35 (path)/vendor/bin/phpstan(119): include('/Users/firehed/...')
     phpstan/phpstan#36 {main}

Code snippet that reproduces the problem

No response

Expected output

No crash, possibly an indication of an invalid column mapping.

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

No response

mergeable[bot] commented 5 months ago

This bug report is missing a link to reproduction at phpstan.org/try.

It will most likely be closed after manual review.

stof commented 2 months ago

The crash is actually not in phpstan but in doctrine/orm based on the stack trace