Skip to content

Commit 27e2b53

Browse files
Filter scope by non-empty array after foreach regardless of polluteScopeWithAlwaysIterableForeach
1 parent 83ccb7f commit 27e2b53

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -940,19 +940,15 @@ private function processStmtNode(
940940
$exprType = $scope->getType($stmt->expr);
941941
$isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce();
942942
if ($exprType->isIterable()->no() || $isIterableAtLeastOnce->maybe()) {
943-
if ($this->polluteScopeWithAlwaysIterableForeach) {
944-
$finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr(
945-
new BinaryOp\Identical(
946-
$stmt->expr,
947-
new Array_([]),
948-
),
949-
new FuncCall(new Name\FullyQualified('is_object'), [
950-
new Arg($stmt->expr),
951-
]),
952-
)));
953-
} else {
954-
$finalScope = $finalScope->mergeWith($scope);
955-
}
943+
$finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr(
944+
new BinaryOp\Identical(
945+
$stmt->expr,
946+
new Array_([]),
947+
),
948+
new FuncCall(new Name\FullyQualified('is_object'), [
949+
new Arg($stmt->expr),
950+
]),
951+
)));
956952
} elseif ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) {
957953
$finalScope = $scope;
958954
} elseif (!$this->polluteScopeWithAlwaysIterableForeach) {

src/Testing/TypeInferenceTestCase.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public static function processFile(
6363
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
6464
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
6565
self::createScopeFactory($reflectionProvider, $typeSpecifier),
66-
true,
67-
true,
66+
self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'),
67+
self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'),
6868
static::getEarlyTerminatingMethodCalls(),
6969
static::getEarlyTerminatingFunctionCalls(),
7070
self::getContainer()->getParameter('universalObjectCratesClasses'),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class Bug10922Test extends TypeInferenceTestCase
8+
{
9+
10+
public function dataFileAsserts(): iterable
11+
{
12+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10922.php');
13+
}
14+
15+
/**
16+
* @dataProvider dataFileAsserts
17+
* @param mixed ...$args
18+
*/
19+
public function testFileAsserts(
20+
string $assertType,
21+
string $file,
22+
...$args,
23+
): void
24+
{
25+
$this->assertFileAsserts($assertType, $file, ...$args);
26+
}
27+
28+
public static function getAdditionalConfigFiles(): array
29+
{
30+
return [
31+
__DIR__ . '/../../../conf/bleedingEdge.neon',
32+
__DIR__ . '/bug-10922.neon',
33+
];
34+
}
35+
36+
}

tests/PHPStan/Analyser/bug-10922.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
parameters:
2+
polluteScopeWithAlwaysIterableForeach: false
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Bug10922;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
/** @param array<string, array{foo: string}> $array */
10+
public function sayHello(array $array): void
11+
{
12+
foreach ($array as $key => $item) {
13+
$array[$key]['bar'] = '';
14+
}
15+
assertType("array<string, array{foo: string, bar: ''}>", $array);
16+
}
17+
18+
/** @param array<string, array{foo: string}> $array */
19+
public function sayHello2(array $array): void
20+
{
21+
if (count($array) > 0) {
22+
return;
23+
}
24+
25+
foreach ($array as $key => $item) {
26+
$array[$key]['bar'] = '';
27+
}
28+
assertType("array{}", $array);
29+
}
30+
31+
/** @param array<string, array{foo: string}> $array */
32+
public function sayHello3(array $array): void
33+
{
34+
if (count($array) === 0) {
35+
return;
36+
}
37+
38+
foreach ($array as $key => $item) {
39+
$array[$key]['bar'] = '';
40+
}
41+
assertType("non-empty-array<string, array{foo: string, bar: ''}>", $array);
42+
}
43+
}

0 commit comments

Comments
 (0)