Skip to content

Commit 7e6348d

Browse files
phpstan-botondrejmirtes
authored andcommitted
Fix count()-based type narrowing on unions of constant array shapes
Closes phpstan/phpstan#14301
1 parent 68b76d8 commit 7e6348d

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,45 @@ private function specifyTypesForCountFuncCall(
14031403
$resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
14041404
}
14051405

1406+
if ($context->truthy() && $isConstantArray->yes() && $isList->yes()) {
1407+
$hasOptionalKeys = false;
1408+
foreach ($type->getConstantArrays() as $arrayType) {
1409+
if ($arrayType->getOptionalKeys() !== []) {
1410+
$hasOptionalKeys = true;
1411+
break;
1412+
}
1413+
}
1414+
1415+
if (!$hasOptionalKeys) {
1416+
$argExpr = $countFuncCall->getArgs()[0]->value;
1417+
$argExprString = $this->exprPrinter->printExpr($argExpr);
1418+
1419+
$sizeMin = null;
1420+
$sizeMax = null;
1421+
if ($sizeType instanceof ConstantIntegerType) {
1422+
$sizeMin = $sizeType->getValue();
1423+
$sizeMax = $sizeType->getValue();
1424+
} elseif ($sizeType instanceof IntegerRangeType) {
1425+
$sizeMin = $sizeType->getMin();
1426+
$sizeMax = $sizeType->getMax();
1427+
}
1428+
1429+
$sureTypes = [];
1430+
$sureNotTypes = [];
1431+
1432+
if ($sizeMin !== null && $sizeMin >= 1) {
1433+
$sureTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMin - 1), new MixedType())];
1434+
}
1435+
if ($sizeMax !== null) {
1436+
$sureNotTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMax), new MixedType())];
1437+
}
1438+
1439+
if ($sureTypes !== [] || $sureNotTypes !== []) {
1440+
return (new SpecifiedTypes($sureTypes, $sureNotTypes))->setRootExpr($rootExpr);
1441+
}
1442+
}
1443+
}
1444+
14061445
return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$resultTypes), $context, $scope)->setRootExpr($rootExpr);
14071446
}
14081447

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14301;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
/**
10+
* @param array{bool}|array{mixed, string|null, mixed} $row
11+
*/
12+
protected function testNotEquals(array $row): string
13+
{
14+
if (count($row) !== 1) {
15+
assertType('array{mixed, string|null, mixed}', $row);
16+
17+
[$field, $operator, $value] = $row;
18+
assertType('string|null', $operator);
19+
return $operator ?? '=';
20+
} else {
21+
assertType('array{bool}', $row);
22+
}
23+
24+
return '';
25+
}
26+
27+
/**
28+
* @param array{bool}|array{mixed, string|null, mixed} $row
29+
*/
30+
protected function testEquals(array $row): void
31+
{
32+
if (count($row) === 3) {
33+
assertType('array{mixed, string|null, mixed}', $row);
34+
} else {
35+
assertType('array{bool}', $row);
36+
}
37+
}
38+
39+
/**
40+
* @param array{bool}|array{mixed, string|null, mixed} $row
41+
*/
42+
protected function testEquals1(array $row): void
43+
{
44+
if (count($row) === 1) {
45+
assertType('array{bool}', $row);
46+
} else {
47+
assertType('array{mixed, string|null, mixed}', $row);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)