Skip to content

Commit

Permalink
Add support for non-truthy values filtering with array_filter
Browse files Browse the repository at this point in the history
  • Loading branch information
fmasa authored and ondrejmirtes committed Jun 18, 2018
1 parent 582c78c commit 6c4bbce
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 deletions.
51 changes: 51 additions & 0 deletions src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;

class ArrayFilterFunctionReturnTypeReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
{
Expand All @@ -32,6 +41,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
$keyType = $arrayArgType->getIterableKeyType();
$itemType = $arrayArgType->getIterableValueType();

if ($callbackArg === null) {
return $this->removeFalsey($arrayArgType);
}

if ($flagArg === null && $callbackArg instanceof Closure && count($callbackArg->stmts) === 1) {
$statement = $callbackArg->stmts[0];
if ($statement instanceof Return_ && $statement->expr !== null && count($callbackArg->params) > 0) {
Expand All @@ -53,4 +66,42 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return new ArrayType($keyType, $itemType);
}

private function removeFalsey(Type $type): Type
{
$falseyTypes = new UnionType([
new NullType(),
new ConstantBooleanType(false),
new ConstantIntegerType(0),
new ConstantFloatType(0.0),
new ConstantStringType(''),
new ConstantArrayType([], []),
]);

if ($type instanceof ConstantArrayType) {
$keys = $type->getKeyTypes();
$values = $type->getValueTypes();

foreach ($values as $offset => $value) {
if (!$falseyTypes->isSuperTypeOf($value)->yes()) {
continue;
}

unset($keys[$offset], $values[$offset]);
}

return new ConstantArrayType(array_values($keys), array_values($values));
}

$keyType = $type->getIterableKeyType();
$valueType = $type->getIterableValueType();

$valueType = TypeCombinator::remove($valueType, $falseyTypes);

if ($valueType instanceof NeverType) {
return new ConstantArrayType([], []);
}

return new ArrayType($keyType, $valueType);
}

}
24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4116,6 +4116,30 @@ public function dataArrayFunctions(): array
'null',
'array_shift([])',
],
[
'array(null, \'\', 1)',
'$constantArrayWithFalseyValues',
],
[
'array(2 => 1)',
'$constantTruthyValues',
],
[
'array<int, false|null>',
'$falsey',
],
[
'array()',
'array_filter($falsey)',
],
[
'array<int, bool|null>',
'$withFalsey',
],
[
'array<int, true>',
'array_filter($withFalsey)',
],
];
}

Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Analyser/data/array-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
1 => new \stdClass(),
];

$constantArrayWithFalseyValues = [null, '', 1];

$constantTruthyValues = array_filter($constantArrayWithFalseyValues);

/** @var array<int, false|null> $falsey */
$falsey = doFoo();

/** @var array<int, bool|null> $withFalsey */
$withFalsey = doFoo();

/** @var array<string, int> $generalStringKeys */
$generalStringKeys = doFoo();

Expand Down

0 comments on commit 6c4bbce

Please sign in to comment.