/
ArraySearchFunctionDynamicReturnTypeExtension.php
123 lines (99 loc) · 3.75 KB
/
ArraySearchFunctionDynamicReturnTypeExtension.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
118
119
120
121
122
123
<?php declare(strict_types = 1);
namespace PHPStan\Type\Php;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function count;
final class ArraySearchFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{
public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'array_search';
}
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
$argsCount = count($functionCall->getArgs());
if ($argsCount < 2) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}
$haystackArgType = $scope->getType($functionCall->getArgs()[1]->value);
$haystackIsArray = $haystackArgType->isArray();
if ($haystackIsArray->no()) {
return new NullType();
}
if ($argsCount < 3) {
return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false));
}
$strictArgType = $scope->getType($functionCall->getArgs()[2]->value);
if (!$strictArgType instanceof ConstantBooleanType || $strictArgType->getValue() === false) {
return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false));
}
$needleArgType = $scope->getType($functionCall->getArgs()[0]->value);
if ($haystackArgType->getIterableValueType()->isSuperTypeOf($needleArgType)->no()) {
return new ConstantBooleanType(false);
}
$typesFromConstantArrays = [];
if ($haystackIsArray->maybe()) {
$typesFromConstantArrays[] = new NullType();
}
$haystackArrays = TypeUtils::getAnyArrays($haystackArgType);
if (count($haystackArrays) === 0) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}
$arrays = [];
$typesFromConstantArraysCount = 0;
foreach ($haystackArrays as $haystackArray) {
if (!$haystackArray instanceof ConstantArrayType) {
$arrays[] = $haystackArray;
continue;
}
$typesFromConstantArrays[] = $this->resolveTypeFromConstantHaystackAndNeedle($needleArgType, $haystackArray);
$typesFromConstantArraysCount++;
}
if (
$typesFromConstantArraysCount > 0
&& count($haystackArrays) === $typesFromConstantArraysCount
) {
return TypeCombinator::union(...$typesFromConstantArrays);
}
$iterableKeyType = TypeCombinator::union(...$arrays)->getIterableKeyType();
return TypeCombinator::union(
$iterableKeyType,
new ConstantBooleanType(false),
...$typesFromConstantArrays,
);
}
private function resolveTypeFromConstantHaystackAndNeedle(Type $needle, ConstantArrayType $haystack): Type
{
$matchesByType = [];
$hasIdenticalValue = false;
foreach ($haystack->getValueTypes() as $index => $valueType) {
$isNeedleSuperType = $valueType->isSuperTypeOf($needle);
if ($isNeedleSuperType->no()) {
continue;
}
if ($needle instanceof ConstantScalarType && $valueType instanceof ConstantScalarType
&& $needle->getValue() === $valueType->getValue()
) {
$hasIdenticalValue = true;
}
$matchesByType[] = $haystack->getKeyTypes()[$index];
}
if (count($matchesByType) > 0) {
if ($hasIdenticalValue) {
return TypeCombinator::union(...$matchesByType);
}
return TypeCombinator::union(new ConstantBooleanType(false), ...$matchesByType);
}
return new ConstantBooleanType(false);
}
}