|
19 | 19 | use PHPStan\Type\FunctionTypeSpecifyingExtension; |
20 | 20 | use PHPStan\Type\MixedType; |
21 | 21 | use PHPStan\Type\NeverType; |
| 22 | +use PHPStan\Type\Type; |
22 | 23 | use PHPStan\Type\TypeCombinator; |
23 | 24 | use PHPStan\Type\UnionType; |
24 | 25 | use function count; |
@@ -135,37 +136,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n |
135 | 136 | && count($needleType->getFiniteTypes()) > 0 |
136 | 137 | && $arrayType->isIterableAtLeastOnce()->yes() |
137 | 138 | ) { |
138 | | - // In false context (!in_array), we can only remove values guaranteed |
139 | | - // to be in every possible array variant. For union types like |
140 | | - // array{A}|array{B}, getIterableValueType() returns A|B but neither |
141 | | - // value is guaranteed to be in every variant. |
142 | | - $innerTypes = $arrayType instanceof UnionType ? $arrayType->getTypes() : [$arrayType]; |
143 | | - $innerValueTypes = []; |
144 | | - foreach ($innerTypes as $innerType) { |
145 | | - $constantArrays = $innerType->getConstantArrays(); |
146 | | - if (count($constantArrays) > 0) { |
147 | | - // Only include values from non-optional keys, since optional |
148 | | - // keys may not be present in the array at runtime. |
149 | | - $perArrayTypes = []; |
150 | | - foreach ($constantArrays as $constantArray) { |
151 | | - $guaranteedTypes = []; |
152 | | - foreach ($constantArray->getValueTypes() as $i => $valueType) { |
153 | | - if (!$constantArray->isOptionalKey($i)) { |
154 | | - $guaranteedTypes[] = $valueType; |
155 | | - } |
156 | | - } |
157 | | - $perArrayTypes[] = count($guaranteedTypes) > 0 |
158 | | - ? TypeCombinator::union(...$guaranteedTypes) |
159 | | - : new NeverType(); |
160 | | - } |
161 | | - $innerValueTypes[] = TypeCombinator::intersect(...$perArrayTypes); |
162 | | - } else { |
163 | | - $innerValueTypes[] = $innerType->getIterableValueType(); |
164 | | - } |
165 | | - } |
166 | | - $narrowingValueType = count($innerValueTypes) > 0 |
167 | | - ? TypeCombinator::intersect(...$innerValueTypes) |
168 | | - : $arrayValueType; |
| 139 | + $narrowingValueType = $this->computeGuaranteedValueType($arrayType, $arrayValueType); |
169 | 140 | if (count($narrowingValueType->getFiniteTypes()) > 0) { |
170 | 141 | $specifiedTypes = $this->typeSpecifier->create( |
171 | 142 | $needleExpr, |
@@ -217,4 +188,39 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n |
217 | 188 | return $specifiedTypes; |
218 | 189 | } |
219 | 190 |
|
| 191 | + /** |
| 192 | + * Computes the type of values guaranteed to be in every possible variant |
| 193 | + * of the array. For union types like array{A}|array{B}, we intersect the |
| 194 | + * value types so only values present in all variants are used for narrowing. |
| 195 | + */ |
| 196 | + private function computeGuaranteedValueType(Type $arrayType, Type $arrayValueType): Type |
| 197 | + { |
| 198 | + $innerTypes = $arrayType instanceof UnionType ? $arrayType->getTypes() : [$arrayType]; |
| 199 | + $innerValueTypes = []; |
| 200 | + foreach ($innerTypes as $innerType) { |
| 201 | + $constantArrays = $innerType->getConstantArrays(); |
| 202 | + if (count($constantArrays) > 0) { |
| 203 | + $perArrayTypes = []; |
| 204 | + foreach ($constantArrays as $constantArray) { |
| 205 | + $guaranteedTypes = []; |
| 206 | + foreach ($constantArray->getValueTypes() as $i => $valueType) { |
| 207 | + if (!$constantArray->isOptionalKey($i)) { |
| 208 | + $guaranteedTypes[] = $valueType; |
| 209 | + } |
| 210 | + } |
| 211 | + $perArrayTypes[] = count($guaranteedTypes) > 0 |
| 212 | + ? TypeCombinator::union(...$guaranteedTypes) |
| 213 | + : new NeverType(); |
| 214 | + } |
| 215 | + $innerValueTypes[] = TypeCombinator::intersect(...$perArrayTypes); |
| 216 | + } else { |
| 217 | + $innerValueTypes[] = $innerType->getIterableValueType(); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + return count($innerValueTypes) > 0 |
| 222 | + ? TypeCombinator::intersect(...$innerValueTypes) |
| 223 | + : $arrayValueType; |
| 224 | + } |
| 225 | + |
220 | 226 | } |
0 commit comments