|
16 | 16 | use PHPStan\Reflection\FunctionReflection; |
17 | 17 | use PHPStan\Type\Accessory\NonEmptyArrayType; |
18 | 18 | use PHPStan\Type\ArrayType; |
| 19 | +use PHPStan\Type\Constant\ConstantArrayType; |
19 | 20 | use PHPStan\Type\FunctionTypeSpecifyingExtension; |
20 | 21 | use PHPStan\Type\MixedType; |
| 22 | +use PHPStan\Type\Type; |
21 | 23 | use PHPStan\Type\TypeCombinator; |
22 | 24 | use function count; |
23 | 25 | use function strtolower; |
@@ -113,25 +115,18 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n |
113 | 115 | } |
114 | 116 |
|
115 | 117 | $specifiedTypes = new SpecifiedTypes(); |
116 | | - if ( |
117 | | - $context->true() |
118 | | - || ( |
119 | | - $context->false() |
120 | | - && count($arrayValueType->getFiniteTypes()) > 0 |
121 | | - && count($needleType->getFiniteTypes()) > 0 |
122 | | - && $arrayType->isIterableAtLeastOnce()->yes() |
123 | | - ) |
124 | | - ) { |
| 118 | + $narrowingValueType = $this->computeNeedleNarrowingType($context, $needleType, $arrayType, $arrayValueType); |
| 119 | + if ($narrowingValueType !== null) { |
125 | 120 | $specifiedTypes = $this->typeSpecifier->create( |
126 | 121 | $needleExpr, |
127 | | - $arrayValueType, |
| 122 | + $narrowingValueType, |
128 | 123 | $context, |
129 | 124 | $scope, |
130 | 125 | ); |
131 | 126 | if ($needleExpr instanceof AlwaysRememberedExpr) { |
132 | 127 | $specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create( |
133 | 128 | $needleExpr->getExpr(), |
134 | | - $arrayValueType, |
| 129 | + $narrowingValueType, |
135 | 130 | $context, |
136 | 131 | $scope, |
137 | 132 | )); |
@@ -171,4 +166,68 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n |
171 | 166 | return $specifiedTypes; |
172 | 167 | } |
173 | 168 |
|
| 169 | + /** |
| 170 | + * Computes the type to narrow the needle against, or null if no narrowing should occur. |
| 171 | + * In true context, returns the array value type directly. |
| 172 | + * In false context, returns only the values guaranteed to be in every possible variant of the array. |
| 173 | + */ |
| 174 | + private function computeNeedleNarrowingType(TypeSpecifierContext $context, Type $needleType, Type $arrayType, Type $arrayValueType): ?Type |
| 175 | + { |
| 176 | + if ($context->true()) { |
| 177 | + return $arrayValueType; |
| 178 | + } |
| 179 | + |
| 180 | + if ( |
| 181 | + !$context->false() |
| 182 | + || count($needleType->getFiniteTypes()) === 0 |
| 183 | + || !$arrayType->isIterableAtLeastOnce()->yes() |
| 184 | + ) { |
| 185 | + return null; |
| 186 | + } |
| 187 | + |
| 188 | + $arrays = $arrayType->getArrays(); |
| 189 | + $guaranteedValueTypePerArray = []; |
| 190 | + foreach ($arrays as $array) { |
| 191 | + if ($array instanceof ConstantArrayType) { |
| 192 | + $innerGuaranteeValueType = []; |
| 193 | + foreach ($array->getValueTypes() as $i => $valueType) { |
| 194 | + if ($array->isOptionalKey($i)) { |
| 195 | + continue; |
| 196 | + } |
| 197 | + |
| 198 | + $finiteTypes = $valueType->getFiniteTypes(); |
| 199 | + if (count($finiteTypes) !== 1) { |
| 200 | + continue; |
| 201 | + } |
| 202 | + |
| 203 | + $innerGuaranteeValueType[] = $finiteTypes[0]; |
| 204 | + } |
| 205 | + |
| 206 | + if (count($innerGuaranteeValueType) === 0) { |
| 207 | + return null; |
| 208 | + } |
| 209 | + |
| 210 | + $guaranteedValueTypePerArray[] = TypeCombinator::union(...$innerGuaranteeValueType); |
| 211 | + } else { |
| 212 | + $finiteValueType = $array->getIterableValueType()->getFiniteTypes(); |
| 213 | + if (count($finiteValueType) !== 1) { |
| 214 | + return null; |
| 215 | + } |
| 216 | + |
| 217 | + $guaranteedValueTypePerArray[] = $finiteValueType[0]; |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + if (count($guaranteedValueTypePerArray) === 0) { |
| 222 | + return null; |
| 223 | + } |
| 224 | + |
| 225 | + $guaranteedValueType = TypeCombinator::intersect(...$guaranteedValueTypePerArray); |
| 226 | + if (count($guaranteedValueType->getFiniteTypes()) === 0) { |
| 227 | + return null; |
| 228 | + } |
| 229 | + |
| 230 | + return $guaranteedValueType; |
| 231 | + } |
| 232 | + |
174 | 233 | } |
0 commit comments