Skip to content

Commit

Permalink
fix type for collection->whereNotNull (#1332)
Browse files Browse the repository at this point in the history
  • Loading branch information
crissi committed Feb 19, 2023
1 parent 8a3e4c6 commit 3a6a692
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
4 changes: 4 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ services:
class: NunoMaduro\Larastan\ReturnTypes\CollectionFilterDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: NunoMaduro\Larastan\ReturnTypes\CollectionWhereNotNullDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: NunoMaduro\Larastan\ReturnTypes\CollectionGenericStaticMethodDynamicMethodReturnTypeExtension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\Larastan\ReturnTypes;

use Illuminate\Support\Enumerable;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class CollectionWhereNotNullDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return Enumerable::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'whereNotNull';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
$calledOnType = $scope->getType($methodCall->var);

if (! $calledOnType instanceof \PHPStan\Type\Generic\GenericObjectType) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

$keyType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TKey');
$valueType = $methodReflection->getDeclaringClass()->getActiveTemplateTypeMap()->getType('TValue');

if ($keyType === null || $valueType === null) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

$nonFalseyTypes = TypeCombinator::removeNull($valueType);

return new GenericObjectType($calledOnType->getClassName(), [$keyType, $nonFalseyTypes]);
}
}
1 change: 1 addition & 0 deletions tests/Type/CollectionDynamicReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CollectionDynamicReturnTypeExtensionTest extends \PHPStan\Testing\TypeInfe
public function dataFileAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__.'/data/collection-filter.php');
yield from $this->gatherAssertTypes(__DIR__.'/data/collection-where-not-null.php');

if (PHP_VERSION_ID >= 70400) {
yield from $this->gatherAssertTypes(__DIR__.'/data/collection-filter-arrow-function.php');
Expand Down
27 changes: 27 additions & 0 deletions tests/Type/data/collection-where-not-null.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace CollectionWhereNotNull;

use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use function PHPStan\Testing\assertType;

assertType('Illuminate\Support\Collection<int, int|string>', collect([1, 2, null, '', 'hello'])->whereNotNull());
assertType('Illuminate\Support\Collection<int, int|string>', collect([1, 2, null, '', 'hello'])->whereNotNull());

/** @param \Illuminate\Database\Eloquent\Collection<int, \App\User> $foo */
function objectAndParam(EloquentCollection $foo): void
{
assertType("Illuminate\Database\Eloquent\Collection<int, App\User>", $foo->whereNotNull('blocked'));
}

/** @param \Illuminate\Database\Eloquent\Collection<int, ?\App\User> $foo */
function objectOrNullAndParam(EloquentCollection $foo): void
{
assertType("Illuminate\Database\Eloquent\Collection<int, App\User>", $foo->whereNotNull('blocked'));
}

/** @param \Illuminate\Database\Eloquent\Collection<int, ?\App\User> $foo */
function objectOrNull(EloquentCollection $foo): void
{
assertType("Illuminate\Database\Eloquent\Collection<int, App\User>", $foo->whereNotNull());
}

0 comments on commit 3a6a692

Please sign in to comment.