From 4c1b7a8b9bd843de1b2ec877443d8fe4fc78019c Mon Sep 17 00:00:00 2001 From: fluffycondor <7ionmail@gmail.com> Date: Tue, 20 Oct 2020 16:30:01 +0300 Subject: [PATCH 1/5] Specify types for Doctrine Collection first() method --- extension.neon | 6 ++ .../FirstTypeSpecifyingExtension.php | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php diff --git a/extension.neon b/extension.neon index 0702305a..659d5779 100644 --- a/extension.neon +++ b/extension.neon @@ -305,3 +305,9 @@ services: tags: [phpstan.doctrine.typeDescriptor] arguments: uuidTypeName: Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType + + # Doctrine Collection + - + class: App\PHPStan\DoctrineCollectionFirstTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension diff --git a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php new file mode 100644 index 00000000..b03ecc15 --- /dev/null +++ b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php @@ -0,0 +1,65 @@ +broker = $broker; + } + + public function getClass(): string + { + return self::COLLECTION_CLASS; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + return $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME && $context->false(); + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $classReflection = $this->broker->getClass(self::COLLECTION_CLASS); + $methodVariants = $classReflection->getNativeMethod(self::FIRST_METHOD_NAME)->getVariants(); + + return $this->typeSpecifier->create( + new MethodCall($node->var, self::FIRST_METHOD_NAME), + TypeCombinator::remove(new BooleanType(), ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()), + $context + ); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } +} From d6c858d91e4728e3b68d3a643e48e76a62c0b399 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Thu, 22 Oct 2020 12:17:12 +0300 Subject: [PATCH 2/5] Fix issues --- .../FirstTypeSpecifyingExtension.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php index b03ecc15..d13d49fd 100644 --- a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php @@ -8,7 +8,6 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; -use PHPStan\Broker\Broker; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\BooleanType; @@ -21,13 +20,8 @@ final class FirstTypeSpecifyingExtension implements MethodTypeSpecifyingExtensio private const IS_EMPTY_METHOD_NAME = 'isEmpty'; private const FIRST_METHOD_NAME = 'first'; - private Broker $broker; - private TypeSpecifier $typeSpecifier; - - public function __construct(Broker $broker) - { - $this->broker = $broker; - } + /** @var TypeSpecifier */ + private $typeSpecifier; public function getClass(): string { @@ -39,7 +33,12 @@ public function isMethodSupported( MethodCall $node, TypeSpecifierContext $context ): bool { - return $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME && $context->false(); + return + ( + $methodReflection->getDeclaringClass()->getName() === self::COLLECTION_CLASS + || $methodReflection->getDeclaringClass()->isSubclassOf(self::COLLECTION_CLASS) + ) + && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; } public function specifyTypes( @@ -48,12 +47,12 @@ public function specifyTypes( Scope $scope, TypeSpecifierContext $context ): SpecifiedTypes { - $classReflection = $this->broker->getClass(self::COLLECTION_CLASS); + $classReflection = $methodReflection->getDeclaringClass(); $methodVariants = $classReflection->getNativeMethod(self::FIRST_METHOD_NAME)->getVariants(); return $this->typeSpecifier->create( new MethodCall($node->var, self::FIRST_METHOD_NAME), - TypeCombinator::remove(new BooleanType(), ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()), + TypeCombinator::remove(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType(), new BooleanType()), $context ); } From caa109407a7aa46b83833b533a72bdaa27ca2881 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Thu, 22 Oct 2020 12:29:39 +0300 Subject: [PATCH 3/5] Add context condition --- .../FirstTypeSpecifyingExtension.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php index d13d49fd..39bf50aa 100644 --- a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php @@ -10,7 +10,7 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\MethodTypeSpecifyingExtension; use PHPStan\Type\TypeCombinator; @@ -50,11 +50,19 @@ public function specifyTypes( $classReflection = $methodReflection->getDeclaringClass(); $methodVariants = $classReflection->getNativeMethod(self::FIRST_METHOD_NAME)->getVariants(); - return $this->typeSpecifier->create( - new MethodCall($node->var, self::FIRST_METHOD_NAME), - TypeCombinator::remove(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType(), new BooleanType()), - $context - ); + if ($context->truthy()) { + return $this->typeSpecifier->create( + new MethodCall($node->var, self::FIRST_METHOD_NAME), + new ConstantBooleanType(false), + $context + ); + } else { + return $this->typeSpecifier->create( + new MethodCall($node->var, self::FIRST_METHOD_NAME), + TypeCombinator::remove(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType(), new ConstantBooleanType(false)), + $context + ); + } } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void From 44e6960132102e7ff9cd63d0a825be6ec453441e Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Thu, 22 Oct 2020 12:39:51 +0300 Subject: [PATCH 4/5] Fix build --- .../FirstTypeSpecifyingExtension.php | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php index 39bf50aa..2e1633a6 100644 --- a/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php @@ -16,6 +16,7 @@ final class FirstTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension { + private const COLLECTION_CLASS = 'Doctrine\Common\Collections\Collection'; private const IS_EMPTY_METHOD_NAME = 'isEmpty'; private const FIRST_METHOD_NAME = 'first'; @@ -32,13 +33,13 @@ public function isMethodSupported( MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context - ): bool { - return - ( - $methodReflection->getDeclaringClass()->getName() === self::COLLECTION_CLASS - || $methodReflection->getDeclaringClass()->isSubclassOf(self::COLLECTION_CLASS) - ) - && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; + ): bool + { + return ( + $methodReflection->getDeclaringClass()->getName() === self::COLLECTION_CLASS + || $methodReflection->getDeclaringClass()->isSubclassOf(self::COLLECTION_CLASS) + ) + && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; } public function specifyTypes( @@ -46,7 +47,8 @@ public function specifyTypes( MethodCall $node, Scope $scope, TypeSpecifierContext $context - ): SpecifiedTypes { + ): SpecifiedTypes + { $classReflection = $methodReflection->getDeclaringClass(); $methodVariants = $classReflection->getNativeMethod(self::FIRST_METHOD_NAME)->getVariants(); @@ -56,17 +58,18 @@ public function specifyTypes( new ConstantBooleanType(false), $context ); - } else { - return $this->typeSpecifier->create( - new MethodCall($node->var, self::FIRST_METHOD_NAME), - TypeCombinator::remove(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType(), new ConstantBooleanType(false)), - $context - ); } + + return $this->typeSpecifier->create( + new MethodCall($node->var, self::FIRST_METHOD_NAME), + TypeCombinator::remove(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType(), new ConstantBooleanType(false)), + $context + ); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { $this->typeSpecifier = $typeSpecifier; } + } From 59adc02a6dea91faeb7a8c945022f563324560b5 Mon Sep 17 00:00:00 2001 From: Semyon <7ionmail@gmail.com> Date: Thu, 22 Oct 2020 12:53:11 +0300 Subject: [PATCH 5/5] Fix classname in extension.neon --- extension.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension.neon b/extension.neon index 659d5779..ac938726 100644 --- a/extension.neon +++ b/extension.neon @@ -308,6 +308,6 @@ services: # Doctrine Collection - - class: App\PHPStan\DoctrineCollectionFirstTypeSpecifyingExtension + class: PHPStan\Type\Doctrine\Collection\FirstTypeSpecifyingExtension tags: - phpstan.typeSpecifier.methodTypeSpecifyingExtension