-
Notifications
You must be signed in to change notification settings - Fork 432
/
ArrayFilterFunctionReturnTypeReturnTypeExtension.php
117 lines (94 loc) · 3.54 KB
/
ArrayFilterFunctionReturnTypeReturnTypeExtension.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php declare(strict_types = 1);
namespace PHPStan\Type\Php;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\StaticTypeFactory;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
class ArrayFilterFunctionReturnTypeReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
{
public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'array_filter';
}
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
$arrayArg = $functionCall->args[0]->value ?? null;
$callbackArg = $functionCall->args[1]->value ?? null;
$flagArg = $functionCall->args[2]->value ?? null;
if ($arrayArg !== null) {
$arrayArgType = $scope->getType($arrayArg);
$keyType = $arrayArgType->getIterableKeyType();
$itemType = $arrayArgType->getIterableValueType();
if ($arrayArgType instanceof MixedType) {
return new BenevolentUnionType([
new ArrayType(new MixedType(), new MixedType()),
new NullType(),
]);
}
if ($callbackArg === null) {
return TypeCombinator::union(
...array_map([$this, 'removeFalsey'], TypeUtils::getArrays($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) {
if (!$callbackArg->params[0]->var instanceof Variable || !is_string($callbackArg->params[0]->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
$itemVariableName = $callbackArg->params[0]->var->name;
if (!$scope instanceof MutatingScope) {
throw new \PHPStan\ShouldNotHappenException();
}
$scope = $scope->assignVariable($itemVariableName, $itemType);
$scope = $scope->filterByTruthyValue($statement->expr);
$itemType = $scope->getVariableType($itemVariableName);
}
}
} else {
$keyType = new MixedType();
$itemType = new MixedType();
}
return new ArrayType($keyType, $itemType);
}
public function removeFalsey(Type $type): Type
{
$falseyTypes = StaticTypeFactory::falsey();
if ($type instanceof ConstantArrayType) {
$keys = $type->getKeyTypes();
$values = $type->getValueTypes();
$generalize = false;
foreach ($values as $offset => $value) {
$isFalsey = $falseyTypes->isSuperTypeOf($value);
if ($isFalsey->yes()) {
unset($keys[$offset], $values[$offset]);
} elseif ($isFalsey->maybe()) {
$values[$offset] = TypeCombinator::remove($values[$offset], $falseyTypes);
$generalize = true;
}
}
$filteredArray = new ConstantArrayType(array_values($keys), array_values($values));
return $generalize ? $filteredArray->generalize() : $filteredArray;
}
$keyType = $type->getIterableKeyType();
$valueType = $type->getIterableValueType();
$valueType = TypeCombinator::remove($valueType, $falseyTypes);
if ($valueType instanceof NeverType) {
return new ConstantArrayType([], []);
}
return new ArrayType($keyType, $valueType);
}
}