Roave/BetterReflection

CodeLocationMissing is thrown, also unable to compile initializer

DanielBadura opened this issue ยท 11 comments

Hello there ๐Ÿ‘‹

Since some time now the CI job for the BC-Check is failing on our Repo. This happened after merging this PR here: patchlevel/event-sourcing#297

After merging we got this action output: https://github.com/patchlevel/event-sourcing/actions/runs/3278010376/jobs/5395925767

I check it out locally and run it with verbose settings and some dump here and there.

....
string(75) "Patchlevel\EventSourcing\Projection\ProjectorStore\ProjectorStateCollection"
string(71) "Patchlevel\EventSourcing\Projection\ProjectorStore\DuplicateProjectorId"
string(57) "Patchlevel\EventSourcing\Projection\SyncProjectorListener"
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\SyncProjectorListener::__construct() in file /src/Projection/SyncProjectorListener.php (line 14)
string(55) "Patchlevel\EventSourcing\Projection\ProjectorRepository"
string(61) "Patchlevel\EventSourcing\Projection\MetadataProjectorResolver"
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\MetadataProjectorResolver::__construct() in file /src/Projection/MetadataProjectorResolver.php (line 17)
string(56) "Patchlevel\EventSourcing\Projection\DefaultProjectionist"
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\DefaultProjectionist::__construct() in file /src/Projection/DefaultProjectionist.php (line 23)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\DefaultProjectionist::boot() in file /src/Projection/DefaultProjectionist.php (line 28)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\DefaultProjectionist::run() in file /src/Projection/DefaultProjectionist.php (line 75)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\DefaultProjectionist::teardown() in file /src/Projection/DefaultProjectionist.php (line 117)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\DefaultProjectionist::remove() in file /src/Projection/DefaultProjectionist.php (line 148)
string(49) "Patchlevel\EventSourcing\Projection\Projectionist"
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\Projectionist::boot() in file /src/Projection/Projectionist.php (line 11)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\Projectionist::run() in file /src/Projection/Projectionist.php (line 16)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\Projectionist::teardown() in file /src/Projection/Projectionist.php (line 18)
[BC] SKIPPED: Unable to compile initializer in method Patchlevel\EventSourcing\Projection\Projectionist::remove() in file /src/Projection/Projectionist.php (line 20)
string(51) "Patchlevel\EventSourcing\Projection\ProjectorStatus"
string(4) "name"
string(51) "Patchlevel\EventSourcing\Projection\ProjectorStatus"
string(51) "Patchlevel\EventSourcing\Projection\ProjectorStatus"
string(6) "string"
NULL
NULL

In CodeLocationMissing.php line 13:
                                                                     
  [Roave\BetterReflection\Reflection\Exception\CodeLocationMissing]  
  Code location is missing                                           
                                                                     

Exception trace:
  at /app/tools/vendor/roave/better-reflection/src/Reflection/Exception/CodeLocationMissing.php:13
 Roave\BetterReflection\Reflection\Exception\CodeLocationMissing::create() at /app/tools/vendor/roave/better-reflection/src/Reflection/ReflectionProperty.php:367
 Roave\BetterReflection\Reflection\ReflectionProperty->getStartLine() at /app/tools/vendor/roave/backward-compatibility-check/src/DetectChanges/BCBreak/PropertyBased/MultipleChecksOnAProperty.php:31
 Roave\BackwardCompatibility\DetectChanges\BCBreak\PropertyBased\MultipleChecksOnAProperty->multipleChecks() at /app/tools/vendor/roave/backward-compatibility-check/src/Changes.php:81
 Roave\BackwardCompatibility\Changes->getIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/DetectChanges/BCBreak/ClassBased/PropertyChanged.php:38
 Roave\BackwardCompatibility\DetectChanges\BCBreak\ClassBased\PropertyChanged->checkSymbols() at /app/tools/vendor/roave/backward-compatibility-check/src/Changes.php:81
 Roave\BackwardCompatibility\Changes->getIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/DetectChanges/BCBreak/ClassBased/MultipleChecksOnAClass.php:35
 Roave\BackwardCompatibility\DetectChanges\BCBreak\ClassBased\MultipleChecksOnAClass->multipleChecks() at /app/tools/vendor/roave/backward-compatibility-check/src/Changes.php:81
 Roave\BackwardCompatibility\Changes->getIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/DetectChanges/BCBreak/ClassBased/MultipleChecksOnAClass.php:35
 Roave\BackwardCompatibility\DetectChanges\BCBreak\ClassBased\MultipleChecksOnAClass->multipleChecks() at /app/tools/vendor/roave/backward-compatibility-check/src/Changes.php:81
 Roave\BackwardCompatibility\Changes->getIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/CompareClasses.php:94
 Roave\BackwardCompatibility\CompareClasses->examineSymbol() at /app/tools/vendor/roave/backward-compatibility-check/src/CompareClasses.php:63
 Roave\BackwardCompatibility\CompareClasses->makeSymbolsIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/Changes.php:81
 Roave\BackwardCompatibility\Changes->getIterator() at /app/tools/vendor/roave/backward-compatibility-check/src/Formatter/SymfonyConsoleTextFormatter.php:18
 Roave\BackwardCompatibility\Formatter\SymfonyConsoleTextFormatter->write() at /app/tools/vendor/roave/backward-compatibility-check/src/Command/AssertBackwardsCompatible.php:168
 Roave\BackwardCompatibility\Command\AssertBackwardsCompatible->execute() at /app/tools/vendor/symfony/console/Command/Command.php:308
 Symfony\Component\Console\Command\Command->run() at /app/tools/vendor/symfony/console/Application.php:1014
 Symfony\Component\Console\Application->doRunCommand() at /app/tools/vendor/symfony/console/Application.php:301
 Symfony\Component\Console\Application->doRun() at /app/tools/vendor/symfony/console/Application.php:171
 Symfony\Component\Console\Application->run() at /app/tools/vendor/roave/backward-compatibility-check/bin/roave-backward-compatibility-check.php:354
 Roave\ApiCompareCli\{closure}() at /app/tools/vendor/roave/backward-compatibility-check/bin/roave-backward-compatibility-check.php:355
 require_once() at /app/tools/vendor/roave/backward-compatibility-check/bin/roave-backward-compatibility-check:6
 include() at /app/tools/vendor/bin/roave-backward-compatibility-check:120

So i tried to figure out what is happening but couldnt reproduce this in BetterReflection. I tried to write some tests to get a minimal reproducing fail but without success. I think this has something to do with initliazing a default value in the method and our new enum.

    public function testClassWithNewInitializerOfEnum(): void
    {
        $php = <<<'PHP'
            <?php
            enum Foo: string
            {
                case New = 'new';
            }
            class Bar {
                public function __construct(private Foo $foo = Foo::New) {}
            }
        PHP;

        $reflector       = new DefaultReflector(new StringSourceLocator($php, $this->astLocator));
        $classReflection = $reflector->reflectClass('Bar');
        $method = $classReflection->getMethod('__construct');
        $property = $method->getParameter('foo');
        $property->getStartLine();
    }

    public function testClassWithNestedNewInitializer(): void
    {
        $php = <<<'PHP'
            <?php
            final class Foo {
                public function __construct() {}
            }
            class Bar {
                public function __construct(private Foo $foo = new Foo()) {}
            }
            class Baz {
                public function __construct(private Bar $bar = new Bar()) {}
            }
        PHP;

        $reflector       = new DefaultReflector(new StringSourceLocator($php, $this->astLocator));
        $classReflection = $reflector->reflectClass('Baz');
        $method = $classReflection->getMethod('__construct');
        $property = $method->getParameter('bar');
        $property->getStartLine();
    }

    public function testInterfaceWithNewInitializer(): void
    {
        $php = <<<'PHP'
            <?php
            class Foo {
                public function __construct(private ?array $bro = null) {}
            }
            interface Bar {
                public function __invoke(Foo $foo = new Foo()): void;
            }
            class Baz implements Bar {
                public function __invoke(Foo $foo = new Foo()): void {}
            }
        PHP;

        $reflector       = new DefaultReflector(new StringSourceLocator($php, $this->astLocator));
        $classReflection = $reflector->reflectClass('Baz');
        $method = $classReflection->getMethod('__invoke');
        $property = $method->getParameter('foo');
        $property->getStartLine();

        $property->getType()->getClass()->getMethod('__construct')->getParameter('bro')->getStartLine();
    }

Maybe you could give me a hint in the right direction how to get the culprit out of this? Is this maybe a bug in the underlying php-parser?

@DanielBadura in the above tests, do you experience any kind of failure?

It seems to fail here: Roave\BetterReflection\Reflection\ReflectionProperty->getStartLine()

Perhaps it cannot find the value reflection property for an ENUM? value is declared in php-src, so it may be that ๐Ÿค”

I think I replicated this issue in phpstan/phpstan-src#2026

Yep, seems like it's really about the core-provided reflection symbol.

In the adapters, we can try-catch this stuff.

In this component (where we don't use the adapters) we either catch {}, or we have to provide a code location for ENUMs (unlikely)

@DanielBadura in the above tests, do you experience any kind of failure?

It seems to fail here: Roave\BetterReflection\Reflection\ReflectionProperty->getStartLine()

Perhaps it cannot find the value reflection property for an ENUM? value is declared in php-src, so it may be that ๐Ÿค”

No, the testcases all did not throw, thats why i could not find the real issue.

Tried now this here based on the linked issue:

    public function testEnumStartLine(): void
    {
        $php = <<<'PHP'
            <?php
            enum Foo: string
            {
                case New = 'new';
            }
        PHP;

        $reflector       = new DefaultReflector(new StringSourceLocator($php, $this->astLocator));
        $classReflection = $reflector->reflectClass('Foo');

        foreach ($classReflection->getMethods() as $method) {
            $method->getStartLine();
        }
    }

Which results into an error which seems a different one than the CodeLocationMissing, no?:

1) Roave\BetterReflectionTest\Reflection\ReflectionClassTest::testEnumStartLine
Roave\BetterReflection\Reflector\Exception\IdentifierNotFound: Roave\BetterReflection\Reflection\ReflectionClass "UnitEnum" could not be found in the located source

/app/src/Reflector/Exception/IdentifierNotFound.php:26
/app/src/Reflector/DefaultReflector.php:35
/app/src/Reflection/ReflectionClass.php:1257
/app/src/Reflection/ReflectionClass.php:1465
/app/src/Reflection/ReflectionClass.php:938
/app/src/Reflection/ReflectionClass.php:906
/app/test/unit/Reflection/ReflectionClassTest.php:2896

@DanielBadura in your example, your DefaultReflector only knows about the StringSourceLocator: you will need to provide a source locator for internal PHP symbols too :)

Yes, thanks. This was missing, now i get with this

    public function testEnumStartLine(): void
    {
        $php = <<<'PHP'
            <?php
            enum Foo: string
            {
                case New = 'new';
            }
        PHP;

        $reflector       = new DefaultReflector(
            new AggregateSourceLocator([
                new StringSourceLocator($php, $this->astLocator),
                new PhpInternalSourceLocator($this->astLocator, BetterReflectionSingleton::instance()->sourceStubber()),
            ])
        );
        $classReflection = $reflector->reflectClass('Foo');

        foreach ($classReflection->getProperties() as $property) {
            $property->getStartLine();
        }
    }

This error:

There was 1 error:

1) Roave\BetterReflectionTest\Reflection\ReflectionClassTest::testEnumStartLine
Roave\BetterReflection\Reflection\Exception\CodeLocationMissing: Code location is missing

/app/src/Reflection/Exception/CodeLocationMissing.php:13
/app/src/Reflection/ReflectionProperty.php:367
/app/test/unit/Reflection/ReflectionClassTest.php:2903

Yeah, that's indeed the reproducer.

The exception is correct/expected here.

What needs to happen in BetterReflection (in the adapter) is a try-catch.

In BackwardCompatibilityCheck, this needs to be hardened: https://github.com/Roave/BackwardCompatibilityCheck/blob/5e31508bae4c8d3f90f7dfbc2a3cbca735fa254e/src/DetectChanges/BCBreak/FunctionBased/MultipleChecksOnAFunction.php#L42-L43

I would say that SymbolStartLocation should be expanded to handle that.

I'll prepare PR for adapters so it should solve PHPStan issue.

In BackwardCompatibilityCheck, this needs to be hardened: https://github.com/Roave/BackwardCompatibilityCheck/blob/5e31508bae4c8d3f90f7dfbc2a3cbca735fa254e/src/DetectChanges/BCBreak/FunctionBased/MultipleChecksOnAFunction.php#L42-L43

I would say that SymbolStartLocation should be expanded to handle that.

I think you linked the wrong place since the exception in this case right now i thrown from the PropertyBased one. https://github.com/Roave/BackwardCompatibilityCheck/blob/5e31508bae4c8d3f90f7dfbc2a3cbca735fa254e/src/DetectChanges/BCBreak/PropertyBased/MultipleChecksOnAProperty.php#L30

So i could try to harden it there, but i think i should wait until the PR mentioned by @kukulich is finished, right?

@DanielBadura I will fix Roave\BetterReflection\Reflection\Adapter\ReflectionProperty (adapter that mimics native reflection) but "Backward Compatibility Check" uses Roave\BetterReflection\Reflection\ReflectionProperty

@DanielBadura Roave/BackwardCompatibilityCheck#697 does not need to wait for roave/better-reflection changes