From de351768a738f5f3e8d0c1b448a6dd340e346803 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 6 Feb 2025 21:29:05 +0100 Subject: [PATCH 1/3] feat: remove not needed extensions --- composer.json | 2 +- extension.neon | 10 - src/Methods/ModelTypeHelper.php | 31 ---- src/ReturnTypes/BuilderModelFindExtension.php | 2 - ...MethodDynamicMethodReturnTypeExtension.php | 175 ------------------ ...DynamicStaticMethodReturnTypeExtension.php | 110 ----------- 6 files changed, 1 insertion(+), 329 deletions(-) delete mode 100644 src/Methods/ModelTypeHelper.php delete mode 100644 src/ReturnTypes/EnumerableGenericStaticMethodDynamicMethodReturnTypeExtension.php delete mode 100644 src/ReturnTypes/EnumerableGenericStaticMethodDynamicStaticMethodReturnTypeExtension.php diff --git a/composer.json b/composer.json index 4e380ab46..76efd033b 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.16", "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.16", "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.12.11" + "phpstan/phpstan": "^1.12.17" }, "require-dev": { "doctrine/coding-standard": "^12.0", diff --git a/extension.neon b/extension.neon index 334ed74c7..924708299 100644 --- a/extension.neon +++ b/extension.neon @@ -290,11 +290,6 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: Larastan\Larastan\ReturnTypes\EnumerableGenericStaticMethodDynamicMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: Larastan\Larastan\ReturnTypes\NewModelQueryDynamicMethodReturnTypeExtension tags: @@ -305,11 +300,6 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: Larastan\Larastan\ReturnTypes\EnumerableGenericStaticMethodDynamicStaticMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: Larastan\Larastan\Types\AbortIfFunctionTypeSpecifyingExtension tags: diff --git a/src/Methods/ModelTypeHelper.php b/src/Methods/ModelTypeHelper.php deleted file mode 100644 index e0015a122..000000000 --- a/src/Methods/ModelTypeHelper.php +++ /dev/null @@ -1,31 +0,0 @@ -getClassName() === Model::class) { - return new ObjectType($modelClass); - } - - return $traverse($type); - }); - } -} diff --git a/src/ReturnTypes/BuilderModelFindExtension.php b/src/ReturnTypes/BuilderModelFindExtension.php index 2979b9657..275ed5385 100644 --- a/src/ReturnTypes/BuilderModelFindExtension.php +++ b/src/ReturnTypes/BuilderModelFindExtension.php @@ -84,8 +84,6 @@ public function getTypeFromMethodCall( foreach ($modelClassType->getObjectClassReflections() as $objectClassReflection) { $modelName = $objectClassReflection->getName(); - $returnType = ModelTypeHelper::replaceStaticTypeWithModel($returnType, $modelName); - if ($argType->isIterable()->yes()) { if (in_array(Collection::class, $returnType->getReferencedClasses(), true)) { $models[] = $this->collectionHelper->determineCollectionClass($modelName); diff --git a/src/ReturnTypes/EnumerableGenericStaticMethodDynamicMethodReturnTypeExtension.php b/src/ReturnTypes/EnumerableGenericStaticMethodDynamicMethodReturnTypeExtension.php deleted file mode 100644 index 9ddfbab71..000000000 --- a/src/ReturnTypes/EnumerableGenericStaticMethodDynamicMethodReturnTypeExtension.php +++ /dev/null @@ -1,175 +0,0 @@ -getDeclaringClass()->getName() === EloquentCollection::class) { - return in_array($methodReflection->getName(), ['find', 'map', 'mapWithKeys', 'unique'], true); - } - - $methods = [ - 'chunk', - 'chunkWhile', - 'collapse', - 'combine', - 'concat', - 'crossJoin', - 'flatMap', - 'flip', - 'groupBy', - 'keyBy', - 'keys', - 'map', - 'mapInto', - 'mapToDictionary', - 'mapToGroups', - 'mapWithKeys', - 'mergeRecursive', - 'pad', - 'partition', - 'pluck', - 'random', - 'sliding', - 'split', - 'splitIn', - 'values', - 'wrap', - 'zip', - ]; - - if ($methodReflection->getDeclaringClass()->getName() === Collection::class) { - $methods = array_merge($methods, ['pop', 'shift']); - } - - return in_array($methodReflection->getName(), $methods, true); - } - - public function getTypeFromMethodCall( - MethodReflection $methodReflection, - MethodCall $methodCall, - Scope $scope, - ): Type { - $returnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $methodCall->getArgs(), - $methodReflection->getVariants(), - )->getReturnType(); - - if ((! $returnType instanceof UnionType) && $returnType->isObject()->no()) { - return $returnType; - } - - // map and mapWithKeys returns base collection, - // if items does not include Eloquent model. - // This logic is in stub files, so here we check if the return type is base collection - if ( - $methodReflection->getDeclaringClass()->getName() === EloquentCollection::class && - in_array($methodReflection->getName(), ['map', 'mapWithKeys'], true) && - $returnType->getObjectClassNames()[0] === Collection::class - ) { - return $returnType; - } - - $calledOnType = $scope->getType($methodCall->var); - - if ($calledOnType->getObjectClassReflections() === []) { - return $returnType; - } - - $classReflection = $calledOnType->getObjectClassReflections()[0]; - - // Special cases for methods returning single models - if ((new ObjectType(Model::class))->isSuperTypeOf($returnType)->yes()) { - return $returnType; - } - - // If it's a UnionType, traverse the types and try to find a collection object type - if ($returnType instanceof UnionType) { - return $returnType->traverse(function (Type $type) use ($classReflection) { - // @phpcs:ignore - if ($type instanceof GenericObjectType && ($innerReflection = $type->getClassReflection()) !== null) { // @phpstan-ignore-line - return $this->handleGenericObjectType($classReflection, $innerReflection); - } - - return $type; - }); - } - - if ($returnType->getObjectClassReflections() === []) { - return $returnType; - } - - return $this->handleGenericObjectType($classReflection, $returnType->getObjectClassReflections()[0]); - } - - private function handleGenericObjectType(ClassReflection $classReflection, ClassReflection $returnTypeClassReflection): ObjectType - { - if ($classReflection->getActiveTemplateTypeMap()->count() !== $returnTypeClassReflection->getActiveTemplateTypeMap()->count()) { - return new ObjectType($classReflection->getName()); - } - - $genericTypes = $returnTypeClassReflection->typeMapToList($returnTypeClassReflection->getActiveTemplateTypeMap()); - - if ($genericTypes === []) { - return new ObjectType($classReflection->getName()); - } - - // If the key type is gonna be a model, we change it to string - if ((new ObjectType(Model::class))->isSuperTypeOf($genericTypes[0])->yes()) { - $genericTypes[0] = new StringType(); - } - - $genericTypes = array_map(static function (Type $type) use ($classReflection) { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($classReflection): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } - - // @phpcs:ignore - if ($type instanceof GenericObjectType && (($innerTypeReflection = $type->getClassReflection()) !== null)) { - $genericTypes = $innerTypeReflection->typeMapToList($innerTypeReflection->getActiveTemplateTypeMap()); - - if ($classReflection->isSubclassOf($type->getClassName())) { - return new GenericObjectType($classReflection->getName(), $genericTypes); - } - } - - return $traverse($type); - }); - }, $genericTypes); - - return new GenericObjectType($classReflection->getName(), $genericTypes); - } -} diff --git a/src/ReturnTypes/EnumerableGenericStaticMethodDynamicStaticMethodReturnTypeExtension.php b/src/ReturnTypes/EnumerableGenericStaticMethodDynamicStaticMethodReturnTypeExtension.php deleted file mode 100644 index 2de7fa39d..000000000 --- a/src/ReturnTypes/EnumerableGenericStaticMethodDynamicStaticMethodReturnTypeExtension.php +++ /dev/null @@ -1,110 +0,0 @@ -getName(), [ - 'make', - 'wrap', - 'times', - 'range', - ], true); - } - - public function getTypeFromStaticMethodCall( - MethodReflection $methodReflection, - StaticCall $methodCall, - Scope $scope, - ): Type { - $returnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $methodCall->getArgs(), - $methodReflection->getVariants(), - )->getReturnType(); - - if (! $returnType instanceof UnionType && $returnType->isObject()->no()) { - return $returnType; - } - - $class = $methodCall->class; - - if (! $class instanceof Name) { - return new ErrorType(); - } - - if (! $this->reflectionProvider->hasClass((string) $class)) { - return $returnType; - } - - $classReflection = $this->reflectionProvider->getClass((string) $class); - - // If it's called on Support collection, just return. - if ($classReflection->getName() === Collection::class) { - return $returnType; - } - - // If it's a UnionType, traverse the types and try to find a collection object type - if ($returnType instanceof UnionType) { - return $returnType->traverse(function (Type $type) use ($classReflection) { - // @phpcs:ignore - if ($type instanceof GenericObjectType && ($innerReflection = $type->getClassReflection()) !== null) { // @phpstan-ignore-line - return $this->handleGenericObjectType($classReflection, $innerReflection); - } - - return $type; - }); - } - - $returnTypeClassReflections = $returnType->getObjectClassReflections(); - - if ($returnTypeClassReflections === []) { - return $returnType; - } - - return $this->handleGenericObjectType($classReflection, $returnTypeClassReflections[0]); - } - - private function handleGenericObjectType(ClassReflection $classReflection, ClassReflection $returnTypeClassReflection): ObjectType - { - if ($classReflection->getActiveTemplateTypeMap()->count() !== $returnTypeClassReflection->getActiveTemplateTypeMap()->count()) { - return new ObjectType($classReflection->getName()); - } - - return new GenericObjectType( - $classReflection->getName(), - $returnTypeClassReflection->typeMapToList($returnTypeClassReflection->getActiveTemplateTypeMap()), - ); - } -} From d8429fb1ceecb32a9ea73fb77cf67d09b346ce6a Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 6 Feb 2025 21:29:41 +0100 Subject: [PATCH 2/3] feat: get bounded type for templates --- .../CollectionFilterRejectDynamicReturnTypeExtension.php | 9 +++++++++ tests/Type/data/collection-filter.php | 9 +++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ReturnTypes/CollectionFilterRejectDynamicReturnTypeExtension.php b/src/ReturnTypes/CollectionFilterRejectDynamicReturnTypeExtension.php index f06ad5abf..2da5638ad 100644 --- a/src/ReturnTypes/CollectionFilterRejectDynamicReturnTypeExtension.php +++ b/src/ReturnTypes/CollectionFilterRejectDynamicReturnTypeExtension.php @@ -15,6 +15,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -53,6 +54,14 @@ public function getTypeFromMethodCall( return null; } + if ($keyType instanceof TemplateType) { + $keyType = $keyType->getBound(); + } + + if ($valueType instanceof TemplateType) { + $valueType = $valueType->getBound(); + } + $methodName = $methodReflection->getName(); assert($methodName === 'filter' || $methodName === 'reject', 'proven in isMethodSupported'); diff --git a/tests/Type/data/collection-filter.php b/tests/Type/data/collection-filter.php index 722d55d76..60e8c6648 100644 --- a/tests/Type/data/collection-filter.php +++ b/tests/Type/data/collection-filter.php @@ -21,8 +21,11 @@ function dummyFilter($value) return random_int(0, 1) > 1; } -/** @param EloquentCollection $users */ -function test(User $user, SupportCollection $users): void +/** + * @param EloquentCollection $users + * @param SupportCollection $mixedCollection + */ +function test(User $user, SupportCollection $users, SupportCollection $mixedCollection): void { assertType("Illuminate\Support\Collection<(int|string), mixed~(0|0.0|''|'0'|array{}|false|null)>", collect()->filter()); @@ -60,4 +63,6 @@ function test(User $user, SupportCollection $users): void ->map(function ($account) { assertType('App\Account', $account); }); + + assertType("Illuminate\Support\Collection<(int|string), mixed~(0|0.0|''|'0'|array{}|false|null)>", $mixedCollection->pluck('foo')->filter()); } From 78f7f8da613e54edb2ab4afa5bede045228fb843 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 6 Feb 2025 22:03:14 +0100 Subject: [PATCH 3/3] fix CS --- src/ReturnTypes/BuilderModelFindExtension.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ReturnTypes/BuilderModelFindExtension.php b/src/ReturnTypes/BuilderModelFindExtension.php index 275ed5385..e35a3bec2 100644 --- a/src/ReturnTypes/BuilderModelFindExtension.php +++ b/src/ReturnTypes/BuilderModelFindExtension.php @@ -10,7 +10,6 @@ use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Support\Str; use Larastan\Larastan\Internal\LaravelVersion; -use Larastan\Larastan\Methods\ModelTypeHelper; use Larastan\Larastan\Support\CollectionHelper; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope;