Skip to content

Commit 2d73870

Browse files
committed
Narrow arrays in union based on count() with smaller/greater operator
1 parent 34d74e8 commit 2d73870

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,51 @@ public function specifyTypesInCondition(
244244
&& in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true)
245245
&& $leftType->isInteger()->yes()
246246
) {
247+
$argType = $scope->getType($expr->right->getArgs()[0]->value);
248+
249+
if (count($expr->right->getArgs()) === 1) {
250+
$isNormalCount = TrinaryLogic::createYes();
251+
} else {
252+
$mode = $scope->getType($expr->right->getArgs()[1]->value);
253+
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
254+
}
255+
256+
if (
257+
$isNormalCount->yes()
258+
&& $argType instanceof UnionType
259+
&& $leftType instanceof ConstantIntegerType
260+
) {
261+
if ($orEqual) {
262+
$constantType = IntegerRangeType::createAllGreaterThanOrEqualTo($leftType->getValue());
263+
} else {
264+
$constantType = IntegerRangeType::createAllGreaterThan($leftType->getValue());
265+
}
266+
267+
$result = [];
268+
foreach ($argType->getTypes() as $innerType) {
269+
$arraySize = $innerType->getArraySize();
270+
$isSize = $constantType->isSuperTypeOf($arraySize);
271+
if ($context->truthy()) {
272+
if ($isSize->no()) {
273+
continue;
274+
}
275+
}
276+
if ($context->falsey()) {
277+
if (!$isSize->yes()) {
278+
continue;
279+
}
280+
}
281+
282+
$result[] = $innerType;
283+
}
284+
285+
return $this->create($expr->right->getArgs()[0]->value, TypeCombinator::union(...$result), $context, false, $scope, $rootExpr);
286+
}
287+
247288
if (
248289
$context->true() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes())
249290
|| ($context->false() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes())
250291
) {
251-
$argType = $scope->getType($expr->right->getArgs()[0]->value);
252-
253292
if ($context->truthy() && $argType->isArray()->maybe()) {
254293
$countables = [];
255294
if ($argType instanceof UnionType) {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug11480;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function arrayGreatherThan(): void
10+
{
11+
$x = [];
12+
if (rand(0, 1)) {
13+
$x[] = 'ab';
14+
}
15+
if (rand(0, 1)) {
16+
$x[] = 'xy';
17+
}
18+
19+
if (count($x) > 0) {
20+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
21+
} else {
22+
assertType("array{}", $x);
23+
}
24+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
25+
26+
if (count($x) > 1) {
27+
assertType("array{0: 'ab', 1?: 'xy'}", $x);
28+
} else {
29+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
30+
}
31+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
32+
33+
if (count($x) >= 1) {
34+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
35+
} else {
36+
assertType("array{}", $x);
37+
}
38+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
39+
}
40+
41+
public function arraySmallerThan(): void
42+
{
43+
$x = [];
44+
if (rand(0, 1)) {
45+
$x[] = 'ab';
46+
}
47+
if (rand(0, 1)) {
48+
$x[] = 'xy';
49+
}
50+
51+
if (count($x) < 1) {
52+
assertType("array{}", $x);
53+
} else {
54+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
55+
}
56+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
57+
58+
if (count($x) <= 1) {
59+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
60+
} else {
61+
assertType("array{0: 'ab', 1?: 'xy'}", $x);
62+
}
63+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
64+
}
65+
}

0 commit comments

Comments
 (0)