Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intersection types with built-in classes does not work #7520

Closed
davidbyoung opened this issue Jan 29, 2022 · 18 comments · Fixed by #10720
Closed

Intersection types with built-in classes does not work #7520

davidbyoung opened this issue Jan 29, 2022 · 18 comments · Fixed by #10720
Labels
bug causes crash intersections Issues related to intersection types

Comments

@davidbyoung
Copy link

https://psalm.dev/r/07aeb2b8c9

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/07aeb2b8c9
<?php

class Foo {}
class Bar
{
    private Foo&\DateTime $date;
}
Psalm encountered an internal error:

/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php: Could not get class storage for datetime

@AndrolGenhald
Copy link
Collaborator

AndrolGenhald commented Jan 29, 2022

It shouldn't crash like that, but intersecting two classes doesn't make much sense anyway. Either we already know one class extends the other, or we know they can't possibly intersect. It seems to work correctly for interfaces: https://psalm.dev/r/1b136de642.

Here's a better example that shouldn't break: https://psalm.dev/r/08a3bb46d9.

@psalm-github-bot
Copy link

psalm-github-bot bot commented Jan 29, 2022

I found these snippets:

https://psalm.dev/r/1b136de642
<?php

class Foo {}
class Bar
{
    private Foo&\DateTimeInterface $date;
}
Psalm output (using commit faaf769):

ERROR: MissingConstructor - 6:36 - Bar has an uninitialized property Bar::$date, but no constructor
https://psalm.dev/r/08a3bb46d9
<?php

interface Foo {}
class Bar
{
    private Foo&\DateTime $date;
}
Psalm encountered an internal error:

/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php: Could not get class storage for datetime

@davidbyoung
Copy link
Author

davidbyoung commented Jan 29, 2022

My real use-case is intersecting an interface and MockObject from PHPUnit in a unit test. In this case, my interface was SessionHandlerInterface. Here's a repro of that issue: https://psalm.dev/r/f544acf7cc

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/f544acf7cc
<?php

class Foo {}
class Bar
{
    private \SessionHandlerInterface&Foo $date;
}
Psalm encountered an internal error:

/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php: Could not get class storage for sessionhandlerinterface

@davidbyoung
Copy link
Author

(it still breaks if you make Foo an interface in my above example)

@davidbyoung
Copy link
Author

The order of the type seems to matter. For example, this works, but this does not.

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/09f17deb82
<?php

interface FooInterface {}
class Works
{
    public function __construct(private FooInterface&\SessionHandlerInterface $foo){}
}
Psalm output (using commit faaf769):

No issues!
https://psalm.dev/r/e9af3116a7
<?php

interface FooInterface {}
class Works
{
    public function __construct(private \SessionHandlerInterface&FooInterface $foo){}
}
Psalm encountered an internal error:

/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php: Could not get class storage for sessionhandlerinterface

@orklah
Copy link
Collaborator

orklah commented Jan 29, 2022

ouch, this breaks badly. I'll look into it.

I'm not sure what will happen by intersecting a mock object and an interface though... Either the mock object already implement the interface and it should already work or it doesn't and it's weird

@AndrolGenhald
Copy link
Collaborator

AndrolGenhald commented Jan 29, 2022

Either the mock object already implement the interface and it should already work or it doesn't and it's weird

Intersecting objects and interfaces makes sense when the class is non-final, since children can implement interfaces.

@davidbyoung
Copy link
Author

Fwiw PHPUnit returns an intersection type when creating a mock

@orklah
Copy link
Collaborator

orklah commented Jan 29, 2022

Thanks for feedback :)

So far, I found out that the issue happens because of Psalm didn't manage to scan SessionHandlerInterface before it tries to create the intersectionType (for that, Psalm needs to know the class storage).

I'm still digging

@AndrolGenhald
Copy link
Collaborator

@orklah Given that docblock types work I'd take a look at the PR that added support for intersection signature types, maybe there's something happening during scanning that needs to happen during analysis?

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/39c247533a
<?php

class Foo {}
class Bar
{
    /** @var Foo&\DateTime */
    private $date;
}
Psalm output (using commit faaf769):

ERROR: MissingConstructor - 7:13 - Bar has an uninitialized property Bar::$date, but no constructor

@orklah
Copy link
Collaborator

orklah commented Jan 29, 2022

That's highly possible, but I'm confused by the fact it works when you flip the intersection around. If I solve the mystery here and the answer is not right, I'll try to fix it further

@AndrolGenhald
Copy link
Collaborator

Probably related: #8387, I think we need to move some of the intersection stuff to the analysis phase instead of the scanning phase, but I'm not sure how much work that'll be.

@Forestsoft-de
Copy link

Forestsoft-de commented Sep 28, 2023

Hey here is another example. Its not possible to scan a symfony project right now.

Deep scanning /var/www/html/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php Uncaught InvalidArgumentException: Could not get class storage for sessionhandlerinterface in /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php:45 Stack trace: #0 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php(723): Psalm\Internal\Provider\ClassLikeStorageProvider->get('sessionhandleri...') #1 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/ClassLikes.php(713): Psalm\Internal\Codebase\ClassLikes->getParentInterfaces('sessionhandleri...') #2 /var/www/html/vendor/vimeo/psalm/src/Psalm/Codebase.php(761): Psalm\Internal\Codebase\ClassLikes->interfaceExtends('sessionhandleri...', 'sessionupdateti...') #3 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php(357): Psalm\Codebase->interfaceExtends('sessionhandleri...', 'sessionupdateti...') #4 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Type/Comparator/ObjectComparator.php(117): Psalm\Internal\Type\Comparator\ObjectComparator::isIntersectionShallowlyContainedBy(Object(Psalm\Codebase), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Type\Atomic\TNamedObject), 'sessionupdateti...', false, false, NULL) #5 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php(354): Psalm\Internal\Type\Comparator\ObjectComparator::isShallowlyContainedBy(Object(Psalm\Codebase), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Type\Atomic\TNamedObject), false, NULL) #6 /var/www/html/vendor/vimeo/psalm/src/Psalm/Type.php(869): Psalm\Internal\Type\Comparator\AtomicTypeComparator::isContainedBy(Object(Psalm\Codebase), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Type\Atomic\TNamedObject)) #7 /var/www/html/vendor/vimeo/psalm/src/Psalm/Type.php(750): Psalm\Type::intersectAtomicTypes(Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Codebase), false) #8 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php(112): Psalm\Type::intersectUnionTypes(Object(Psalm\Type\Union), Object(Psalm\Type\Union), Object(Psalm\Codebase)) #9 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php(1600): Psalm\Internal\PhpVisitor\Reflector\TypeHintResolver::resolve(Object(PhpParser\Node\IntersectionType), Object(Psalm\CodeLocation), Object(Psalm\Codebase), Object(Psalm\Storage\FileStorage), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Aliases), 80210) #10 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php(766): Psalm\Internal\PhpVisitor\Reflector\ClassLikeNodeScanner->visitPropertyDeclaration(Object(PhpParser\Node\Stmt\Property), Object(Psalm\Config), Object(Psalm\Storage\ClassLikeStorage), 'Symfony\\Compone...') #11 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php(157): Psalm\Internal\PhpVisitor\Reflector\ClassLikeNodeScanner->start(Object(PhpParser\Node\Stmt\Class_)) #12 /var/www/html/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(200): Psalm\Internal\PhpVisitor\ReflectorVisitor->enterNode(Object(PhpParser\Node\Stmt\Class_)) #13 /var/www/html/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray(Array) #14 /var/www/html/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode(Object(PhpParser\Node\Stmt\Namespace_)) #15 /var/www/html/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(91): PhpParser\NodeTraverser->traverseArray(Array) #16 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Scanner/FileScanner.php(79): PhpParser\NodeTraverser->traverse(Array) #17 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php(554): Psalm\Internal\Scanner\FileScanner->scan(Object(Psalm\Codebase), Object(Psalm\Storage\FileStorage), false, Object(Psalm\Progress\DebugProgress)) #18 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php(782): Psalm\Internal\Codebase\Scanner->scanFile('/var/www/html/v...', Array, true) #19 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php(428): Psalm\Internal\Codebase\Scanner->scanAPath(1132, '/var/www/html/v...') #20 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Scanner.php(280): Psalm\Internal\Codebase\Scanner->scanFilePaths(1) #21 /var/www/html/vendor/vimeo/psalm/src/Psalm/Codebase.php(505): Psalm\Internal\Codebase\Scanner->scanFiles(Object(Psalm\Internal\Codebase\ClassLikes), 1) #22 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(1076): Psalm\Codebase->scanFiles(1) #23 /var/www/html/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(381): Psalm\Internal\Analyzer\ProjectAnalyzer->checkPaths(Array) #24 /var/www/html/vendor/vimeo/psalm/psalm(9): Psalm\Internal\Cli\Psalm::run(Array) #25 {main} (Psalm 5.15.0@5c774aca4746caf3d239d9c8cadb9f882ca29352 crashed due to an uncaught Throwable)

@Ultra-Code
Copy link

I just stumbled upon this problem myself. It would be great if the psalm team fixes this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug causes crash intersections Issues related to intersection types
Projects
None yet
6 participants