Skip to content

Commit

Permalink
Fix inference of type of Model::find() to base on framework behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan committed Nov 19, 2023
1 parent 39119f4 commit 9aa50ac
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 9 deletions.
19 changes: 10 additions & 9 deletions src/Type/ModelFindReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
Expand Down Expand Up @@ -49,11 +50,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
$methodName = $methodReflection->getName();

if ($methodName === 'find') {
return $this->getTypeFromFind($methodReflection, $methodCall, $scope);
return $this->getTypeFromFind($methodCall, $scope);
}

if ($methodName === 'findAll') {
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
return $this->getTypeFromFindAll($methodCall, $scope);
}

$classReflection = $this->getClassReflection($methodCall, $scope);
Expand All @@ -69,23 +70,23 @@ private function getClassReflection(MethodCall $methodCall, Scope $scope): Class
return current($classTypes);
}

private function getTypeFromFind(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
private function getTypeFromFind(MethodCall $methodCall, Scope $scope): Type
{
$args = $methodCall->getArgs();

if (! isset($args[0])) {
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
return $this->getTypeFromFindAll($methodCall, $scope);
}

return TypeTraverser::map(
$scope->getType($args[0]->value),
function (Type $idType, callable $traverse) use ($methodReflection, $methodCall, $scope): Type {
function (Type $idType, callable $traverse) use ($methodCall, $scope): Type {
if ($idType instanceof UnionType || $idType instanceof IntersectionType) {
return $traverse($idType);
}

if ($idType->isNull()->yes()) {
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
if ($idType->isArray()->yes() && ! $idType->isIterableAtLeastOnce()->yes()) {
return new ConstantArrayType([], []);
}

if ($idType->isInteger()->yes() || $idType->isString()->yes()) {
Expand All @@ -94,12 +95,12 @@ function (Type $idType, callable $traverse) use ($methodReflection, $methodCall,
return TypeCombinator::addNull($this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $methodCall, $scope));
}

return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
return $this->getTypeFromFindAll($methodCall, $scope);
}
);
}

private function getTypeFromFindAll(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
private function getTypeFromFindAll(MethodCall $methodCall, Scope $scope): Type
{
$classReflection = $this->getClassReflection($methodCall, $scope);

Expand Down
2 changes: 2 additions & 0 deletions tests/Type/DynamicMethodReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ public function testFileAsserts(string $assertType, string $file, mixed ...$args
public static function provideFileAssertsCases(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/model-find.php');

yield from self::gatherAssertTypes(__DIR__ . '/data/bug-7.php');
}
}
23 changes: 23 additions & 0 deletions tests/Type/data/bug-7.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

use CodeIgniter\Shield\Models\UserModel;

use function PHPStan\Testing\assertType;

$users = model(UserModel::class);
assertType('array{}', $users->find([]));
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find([1]));

// Model::find() does not fail if not `array|int|string|null` but defaults to get all
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find(new \stdClass()));

0 comments on commit 9aa50ac

Please sign in to comment.