Skip to content

Commit 967ed70

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix array not narrowed to non-empty when count stored in variable
- When $count = count($a) and later $count > 1 or $count === 1, the array $a was not narrowed to non-empty-array - Root cause: conditional expression matching required exact type equality, but count comparisons produce subtypes (e.g. int<2, max> is a subtype of int<1, max>) - Added conditionalExpressionHolderMatches() that uses isSuperTypeOf for IntegerRangeType conditions, enabling count-based narrowing - New regression test in tests/PHPStan/Analyser/nsrt/bug-4090.php Closes phpstan/phpstan#4090
1 parent 9c915ec commit 967ed70

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

src/Analyser/MutatingScope.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,20 @@ public function enterAnonymousFunctionWithoutReflection(
21442144
);
21452145
}
21462146

2147+
private function conditionalExpressionHolderMatches(ExpressionTypeHolder $specified, ExpressionTypeHolder $condition): bool
2148+
{
2149+
if ($specified->equals($condition)) {
2150+
return true;
2151+
}
2152+
2153+
$conditionType = $condition->getType();
2154+
if (!$conditionType instanceof IntegerRangeType) {
2155+
return false;
2156+
}
2157+
2158+
return $conditionType->isSuperTypeOf($specified->getType())->yes();
2159+
}
2160+
21472161
private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool
21482162
{
21492163
$expr = $typeHolder->getExpr();
@@ -3218,7 +3232,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
32183232
}
32193233
foreach ($conditionalExpressions as $conditionalExpression) {
32203234
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
3221-
if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) {
3235+
if (!array_key_exists($holderExprString, $specifiedExpressions) || !$this->conditionalExpressionHolderMatches($specifiedExpressions[$holderExprString], $conditionalTypeHolder)) {
32223236
continue 2;
32233237
}
32243238
}
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 Bug4090;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @param string[] $a */
8+
function foo(array $a): void
9+
{
10+
if (count($a) > 1) {
11+
assertType('non-empty-array<string>', $a);
12+
echo implode(',', $a);
13+
} elseif (count($a) === 1) {
14+
assertType('non-empty-array<string>', $a);
15+
echo trim(current($a));
16+
}
17+
}
18+
19+
20+
/** @param string[] $a */
21+
function bar(array $a): void
22+
{
23+
$count = count($a);
24+
if ($count > 1) {
25+
assertType('non-empty-array<string>', $a);
26+
echo implode(',', $a);
27+
} elseif ($count === 1) {
28+
assertType('non-empty-array<string>', $a);
29+
echo trim(current($a));
30+
}
31+
}
32+
33+
34+
/** @param string[] $a */
35+
function qux(array $a): void
36+
{
37+
switch (count($a)) {
38+
case 0:
39+
assertType('array{}', $a);
40+
break;
41+
case 1:
42+
assertType('non-empty-array<string>', $a);
43+
echo trim(current($a));
44+
break;
45+
default:
46+
assertType('non-empty-array<string>', $a);
47+
echo implode(',', $a);
48+
break;
49+
}
50+
}

0 commit comments

Comments
 (0)