Skip to content

Commit 35db779

Browse files
committed
Apply conditional expressions for non-empty arrays inside foreach
1 parent e9b783a commit 35db779

File tree

5 files changed

+37
-27
lines changed

5 files changed

+37
-27
lines changed

src/Analyser/NodeScopeResolver.php

+8-8
Original file line numberDiff line numberDiff line change
@@ -761,12 +761,16 @@ private function processStmtNode(
761761
} elseif ($stmt instanceof Foreach_) {
762762
$condResult = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep());
763763
$scope = $condResult->getScope();
764-
$bodyScope = $this->enterForeach($scope, $stmt);
764+
$arrayComparisonExpr = new BinaryOp\NotIdentical(
765+
$stmt->expr,
766+
new Array_([])
767+
);
768+
$bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt);
765769
$hasYield = false;
766770
$count = 0;
767771
do {
768772
$prevScope = $bodyScope;
769-
$bodyScope = $bodyScope->mergeWith($scope);
773+
$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr));
770774
$bodyScope = $this->enterForeach($bodyScope, $stmt);
771775
$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void {
772776
})->filterOutLoopExitPoints();
@@ -785,7 +789,7 @@ private function processStmtNode(
785789
$count++;
786790
} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS);
787791

788-
$bodyScope = $bodyScope->mergeWith($scope);
792+
$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr));
789793
$bodyScope = $this->enterForeach($bodyScope, $stmt);
790794
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints();
791795
$finalScope = $finalScopeResult->getScope();
@@ -801,11 +805,7 @@ private function processStmtNode(
801805
$finalScope = $scope;
802806
} elseif ($isIterableAtLeastOnce->maybe()) {
803807
if ($this->polluteScopeWithAlwaysIterableForeach) {
804-
$arrayComparisonExpr = new BinaryOp\NotIdentical(
805-
$stmt->expr,
806-
new Array_([])
807-
);
808-
$finalScope = $finalScope->filterByTruthyValue($arrayComparisonExpr)->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr));
808+
$finalScope = $finalScope->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr));
809809
} else {
810810
$finalScope = $finalScope->mergeWith($scope);
811811
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -4774,7 +4774,7 @@ public function dataForeachArrayType(): array
47744774
],
47754775
[
47764776
__DIR__ . '/data/foreach/foreach-with-specified-key-type.php',
4777-
'array<string, float|int|string>',
4777+
'array<string, float|int|string>&nonEmpty',
47784778
'$list',
47794779
],
47804780
[
@@ -6537,7 +6537,7 @@ public function dataIterable(): array
65376537
'$unionBar',
65386538
],
65396539
[
6540-
'array',
6540+
'array&nonEmpty',
65416541
'$mixedUnionIterableType',
65426542
],
65436543
[
@@ -10603,6 +10603,11 @@ public function dataBug4339(): array
1060310603
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4339.php');
1060410604
}
1060510605

10606+
public function dataBug4343(): array
10607+
{
10608+
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4343.php');
10609+
}
10610+
1060610611
/**
1060710612
* @param string $file
1060810613
* @return array<string, mixed[]>
@@ -10807,6 +10812,7 @@ private function gatherAssertTypes(string $file): array
1080710812
* @dataProvider dataBug3986
1080810813
* @dataProvider dataBug4188
1080910814
* @dataProvider dataBug4339
10815+
* @dataProvider dataBug4343
1081010816
* @param string $assertType
1081110817
* @param string $file
1081210818
* @param mixed ...$args
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Bug4343;
4+
5+
use PHPStan\TrinaryLogic;
6+
use function PHPStan\Analyser\assertVariableCertainty;
7+
8+
function (array $a) {
9+
if (count($a) > 0) {
10+
$test = new \stdClass();
11+
}
12+
13+
foreach ($a as $my) {
14+
assertVariableCertainty(TrinaryLogic::createYes(), $test);
15+
}
16+
};

tests/PHPStan/Analyser/data/native-types.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ public function doForeach(array $array): void
8383
assertNativeType('array', $array);
8484

8585
foreach ($array as $key => $value) {
86-
assertType('array<string, int>', $array);
87-
assertNativeType('array', $array);
86+
assertType('array<string, int>&nonEmpty', $array);
87+
assertNativeType('array&nonEmpty', $array);
8888

8989
assertType('string', $key);
9090
assertNativeType('(int|string)', $key);
@@ -124,8 +124,8 @@ public function doForeachArrayDestructuring(array $array)
124124
assertType('array<string, array(int, string)>', $array);
125125
assertNativeType('array', $array);
126126
foreach ($array as $key => [$i, $s]) {
127-
assertType('array<string, array(int, string)>', $array);
128-
assertNativeType('array', $array);
127+
assertType('array<string, array(int, string)>&nonEmpty', $array);
128+
assertNativeType('array&nonEmpty', $array);
129129

130130
assertType('string', $key);
131131
assertNativeType('(int|string)', $key);

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

+1-13
Original file line numberDiff line numberDiff line change
@@ -135,19 +135,7 @@ public function testTypesAssignedToPropertiesExpressionNames(): void
135135
97,
136136
],
137137
[
138-
'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.',
139-
110,
140-
],
141-
[
142-
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
143-
110,
144-
],
145-
[
146-
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.',
147-
110,
148-
],
149-
[
150-
'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept float|int|string.',
138+
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
151139
110,
152140
],
153141
[

0 commit comments

Comments
 (0)