diff --git a/composer.json b/composer.json index 4126f9e2..4118192a 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "phpstan/phpstan-phpunit": "^0.11", "symfony/framework-bundle": "^3.0 || ^4.0", "squizlabs/php_codesniffer": "^3.3.2", - "symfony/serializer": "^3|^4" + "symfony/serializer": "^3|^4", + "symfony/messenger": "^4.2" }, "conflict": { "symfony/framework-bundle": "<3.0" diff --git a/extension.neon b/extension.neon index 07424feb..ca7288f2 100644 --- a/extension.neon +++ b/extension.neon @@ -12,41 +12,46 @@ services: class: PHPStan\Symfony\ServiceMapFactory factory: PHPStan\Symfony\XmlServiceMapFactory(%symfony.container_xml_path%) - - class: @symfony.serviceMapFactory::create() + factory: @symfony.serviceMapFactory::create() # ControllerTrait::get()/has() return type - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constant_hassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constant_hassers%) + factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # ControllerTrait::has() type specification - - class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Component\DependencyInjection\ContainerInterface) + factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Component\DependencyInjection\ContainerInterface) tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension] - - class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller) + factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller) tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension] - - class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController) + factory: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController) tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension] # Request::getContent() return type - - class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension + factory: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # HeaderBag::get() return type - - class: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension + factory: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # SerializerInterface::deserialize() return type - - class: PHPStan\Type\Symfony\SerializerInterfaceDynamicReturnTypeExtension + factory: PHPStan\Type\Symfony\SerializerInterfaceDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # Envelope::all() return type + - + factory: PHPStan\Type\Symfony\EnvelopeReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/Symfony/EnvelopeReturnTypeExtension.php b/src/Type/Symfony/EnvelopeReturnTypeExtension.php new file mode 100644 index 00000000..bf0712fc --- /dev/null +++ b/src/Type/Symfony/EnvelopeReturnTypeExtension.php @@ -0,0 +1,46 @@ +getName() === 'all'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type + { + if (count($methodCall->args) === 0) { + return new ArrayType(new MixedType(), new ArrayType(new MixedType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface'))); + } + + $argType = $scope->getType($methodCall->args[0]->value); + if (!$argType instanceof ConstantStringType) { + return new ArrayType(new MixedType(), new ObjectType('Symfony\Component\Messenger\Stamp\StampInterface')); + } + + return new ArrayType(new MixedType(), new ObjectType($argType->getValue())); + } + +} diff --git a/tests/Symfony/NeonTest.php b/tests/Symfony/NeonTest.php index e14ef8f2..a189b043 100644 --- a/tests/Symfony/NeonTest.php +++ b/tests/Symfony/NeonTest.php @@ -40,7 +40,7 @@ public function testExtensionNeon(): void ], $container->getParameters()); self::assertCount(2, $container->findByTag('phpstan.rules.rule')); - self::assertCount(6, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension')); + self::assertCount(7, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension')); self::assertCount(3, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension')); self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class)); } diff --git a/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php b/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php new file mode 100644 index 00000000..0a3f9a2b --- /dev/null +++ b/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php @@ -0,0 +1,32 @@ +processFile( + __DIR__ . '/envelope_all.php', + $expression, + $type, + new EnvelopeReturnTypeExtension() + ); + } + + public function getProvider(): Iterator + { + yield ['$test1', 'array<' . ReceivedStamp::class . '>']; + yield ['$test2', 'array<' . StampInterface::class . '>']; + yield ['$test3', 'array>']; + } + +} diff --git a/tests/Type/Symfony/ExtensionTestCase.php b/tests/Type/Symfony/ExtensionTestCase.php index c0c8ec93..a1cef2bf 100644 --- a/tests/Type/Symfony/ExtensionTestCase.php +++ b/tests/Type/Symfony/ExtensionTestCase.php @@ -10,7 +10,7 @@ use PHPStan\Broker\AnonymousClassNameHelper; use PHPStan\Cache\Cache; use PHPStan\File\FileHelper; -use PHPStan\Node\InClassMethodNode; +use PHPStan\Node\VirtualNode; use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Testing\TestCase; @@ -61,7 +61,10 @@ protected function processFile( $parser->parseFile($file), $this->createScopeFactory($broker, $typeSpecifier)->create(ScopeContext::create($file)), function (Node $node, Scope $scope) use ($expression, $type, &$run): void { - if ((new Standard())->prettyPrint([$node instanceof InClassMethodNode ? $node->getOriginalNode() : $node]) !== 'die') { + if ($node instanceof VirtualNode) { + return; + } + if ((new Standard())->prettyPrint([$node]) !== 'die') { return; } /** @var \PhpParser\Node\Stmt\Expression $expNode */ diff --git a/tests/Type/Symfony/envelope_all.php b/tests/Type/Symfony/envelope_all.php new file mode 100644 index 00000000..f2bd3ec4 --- /dev/null +++ b/tests/Type/Symfony/envelope_all.php @@ -0,0 +1,9 @@ +all(\Symfony\Component\Messenger\Stamp\ReceivedStamp::class); +$test2 = $envelope->all(random_bytes(1)); +$test3 = $envelope->all(); + +die;