From 07384771b01f048edd6945bc4a8f261bc95a5caa Mon Sep 17 00:00:00 2001 From: Michael Telgmann Date: Fri, 7 Jan 2022 22:35:38 +0100 Subject: [PATCH] Introduce DynamicReturnTypeExtension for 'Symfony\Component\Form\FormInterface::getErrors' fixes https://github.com/phpstan/phpstan-symfony/issues/166 --- composer.json | 1 + extension.neon | 5 ++ ...ormInterfaceDynamicReturnTypeExtension.php | 64 +++++++++++++++++++ tests/Type/Symfony/ExtensionTest.php | 2 + .../Symfony/data/FormInterface_getErrors.php | 20 ++++++ 5 files changed, 92 insertions(+) create mode 100644 src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php create mode 100644 tests/Type/Symfony/data/FormInterface_getErrors.php diff --git a/composer.json b/composer.json index 06adbfbf..3cddfd1b 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "phpunit/phpunit": "^9.5", "symfony/config": "^4.2 || ^5.0", "symfony/console": "^4.0 || ^5.0", + "symfony/form": "^4.0 || ^5.0", "symfony/framework-bundle": "^4.4 || ^5.0", "symfony/http-foundation": "^4.0 || ^5.0", "symfony/messenger": "^4.2 || ^5.0", diff --git a/extension.neon b/extension.neon index 4b36a06c..47746a0b 100644 --- a/extension.neon +++ b/extension.neon @@ -249,3 +249,8 @@ services: class: PHPStan\Symfony\InputBagStubFilesExtension tags: - phpstan.stubFilesExtension + + # FormInterface::getErrors() return type + - + factory: PHPStan\Type\Symfony\Form\FormInterfaceDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php new file mode 100644 index 00000000..bdbc95ee --- /dev/null +++ b/src/Type/Symfony/Form/FormInterfaceDynamicReturnTypeExtension.php @@ -0,0 +1,64 @@ +getName() === 'getErrors'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type + { + if (!isset($methodCall->getArgs()[1])) { + return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]); + } + + $firstArgType = $scope->getType($methodCall->getArgs()[0]->value); + $secondArgType = $scope->getType($methodCall->getArgs()[1]->value); + + $firstIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType); + $firstIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType); + $secondIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($secondArgType); + $secondIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($secondArgType); + + $firstCompareType = $firstIsTrueType->compareTo($firstIsFalseType); + $secondCompareType = $secondIsTrueType->compareTo($secondIsFalseType); + + if ($firstCompareType === $firstIsTrueType && $secondCompareType === $secondIsFalseType) { + return new GenericObjectType(FormErrorIterator::class, [ + new UnionType([ + new ObjectType(FormError::class), + new ObjectType(FormErrorIterator::class), + ]), + ]); + } + + return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]); + } + +} diff --git a/tests/Type/Symfony/ExtensionTest.php b/tests/Type/Symfony/ExtensionTest.php index 5a49366b..61d354da 100644 --- a/tests/Type/Symfony/ExtensionTest.php +++ b/tests/Type/Symfony/ExtensionTest.php @@ -49,6 +49,8 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/denormalizer.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php'); } /** diff --git a/tests/Type/Symfony/data/FormInterface_getErrors.php b/tests/Type/Symfony/data/FormInterface_getErrors.php new file mode 100644 index 00000000..a360a21d --- /dev/null +++ b/tests/Type/Symfony/data/FormInterface_getErrors.php @@ -0,0 +1,20 @@ +', $form->getErrors()); +assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false)); +assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, true)); + +assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true)); +assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true, true)); + +assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, false)); + +assertType(FormErrorIterator::class . '<'. FormError::class .'|'. FormErrorIterator::class . '>', $form->getErrors(true, false));