Skip to content

Commit 300b7b2

Browse files
authored
infer non-empty-list/array after array_key_exists($i, $arr) (#4440)
1 parent 27ccf57 commit 300b7b2

File tree

5 files changed

+71
-6
lines changed

5 files changed

+71
-6
lines changed

src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,19 @@ public function specifyTypes(
6666
&& !$keyType instanceof ConstantStringType
6767
) {
6868
if ($context->true()) {
69-
if ($arrayType->isIterableAtLeastOnce()->no()) {
70-
return $this->typeSpecifier->create(
69+
$specifiedTypes = new SpecifiedTypes();
70+
71+
if (count($keyType->getConstantScalarTypes()) <= 1) {
72+
$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
7173
$array,
7274
new NonEmptyArrayType(),
7375
$context,
7476
$scope,
75-
);
77+
));
78+
}
79+
80+
if ($arrayType->isIterableAtLeastOnce()->no()) {
81+
return $specifiedTypes;
7682
}
7783

7884
$arrayKeyType = $arrayType->getIterableKeyType();
@@ -82,12 +88,12 @@ public function specifyTypes(
8288
$arrayKeyType = TypeCombinator::union($arrayKeyType, $arrayKeyType->toString());
8389
}
8490

85-
$specifiedTypes = $this->typeSpecifier->create(
91+
$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
8692
$key,
8793
$arrayKeyType,
8894
$context,
8995
$scope,
90-
);
96+
));
9197

9298
$arrayDimFetch = new ArrayDimFetch(
9399
$array,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Bug13674a;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
/**
10+
* @param array<int> $arrayA
11+
* @param list<int> $listA
12+
*/
13+
public function sayHello($arrayA, $listA, int $i): void
14+
{
15+
if (array_key_exists($i, $arrayA)) {
16+
assertType('non-empty-array<int>', $arrayA);
17+
} else {
18+
assertType('array<int>', $arrayA);
19+
}
20+
assertType('array<int>', $arrayA);
21+
22+
if (array_key_exists($i, $listA)) {
23+
assertType('non-empty-list<int>', $listA);
24+
} else {
25+
assertType('list<int>', $listA);
26+
}
27+
assertType('list<int>', $listA);
28+
}
29+
}

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,16 @@ public function testBug7000(): void
406406
]);
407407
}
408408

409+
public function testBug7000b(): void
410+
{
411+
$this->analyse([__DIR__ . '/data/bug-7000b.php'], [
412+
[
413+
"Offset 'require'|'require-dev' might not exist on array{require?: array<string, string>, require-dev?: array<string, string>}.",
414+
16,
415+
],
416+
]);
417+
}
418+
409419
public function testBug6508(): void
410420
{
411421
$this->analyse([__DIR__ . '/data/bug-6508.php'], []);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug7000b;
4+
5+
class Foo
6+
{
7+
public function doBar(): void
8+
{
9+
/** @var array{require?: array<string, string>, require-dev?: array<string, string>} $composer */
10+
$composer = array();
11+
/** @var 'require'|'require-dev' $foo */
12+
$foo = '';
13+
foreach (array('require', 'require-dev') as $linkType) {
14+
if (array_key_exists($linkType, $composer)) {
15+
foreach ($composer[$linkType] as $x) {} // should not report error
16+
foreach ($composer[$foo] as $x) {} // should report error. It can be $linkType = 'require', $foo = 'require-dev'
17+
}
18+
}
19+
}
20+
}

tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function doFoo(array $percentageIntervals, array $changes): void
1515
if ($percentageInterval->isInInterval((float) $changeInPercents)) {
1616
$key = $percentageInterval->getFormatted();
1717
if (array_key_exists($key, $intervalResults)) {
18-
assertType('array<array{itemsCount: mixed, interval: mixed}>', $intervalResults);
18+
assertType('non-empty-array<array{itemsCount: mixed, interval: mixed}>', $intervalResults);
1919
assertType('array{itemsCount: mixed, interval: mixed}', $intervalResults[$key]);
2020
$intervalResults[$key]['itemsCount'] += $itemsCount;
2121
assertType('non-empty-array<array{itemsCount: (array|float|int), interval: mixed}>', $intervalResults);

0 commit comments

Comments
 (0)