diff --git a/extension.neon b/extension.neon index 0702305a..ac938726 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: PHPStan\Type\Doctrine\Collection\FirstTypeSpecifyingExtension + 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..2e1633a6 --- /dev/null +++ b/src/Type/Doctrine/Collection/FirstTypeSpecifyingExtension.php @@ -0,0 +1,75 @@ +getDeclaringClass()->getName() === self::COLLECTION_CLASS + || $methodReflection->getDeclaringClass()->isSubclassOf(self::COLLECTION_CLASS) + ) + && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes + { + $classReflection = $methodReflection->getDeclaringClass(); + $methodVariants = $classReflection->getNativeMethod(self::FIRST_METHOD_NAME)->getVariants(); + + if ($context->truthy()) { + return $this->typeSpecifier->create( + new MethodCall($node->var, self::FIRST_METHOD_NAME), + 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; + } + +}