Skip to content

Commit c32cc9c

Browse files
committed
Fixed handling analysis with early termination in loops for general expressions
1 parent 2d62b9b commit c32cc9c

File tree

3 files changed

+83
-42
lines changed

3 files changed

+83
-42
lines changed

src/Analyser/Scope.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2135,13 +2135,22 @@ public function removeSpecified(self $initialScope): self
21352135
}
21362136
}
21372137

2138+
$moreSpecificTypeHolders = $this->moreSpecificTypes;
2139+
foreach ($moreSpecificTypeHolders as $exprString => $holder) {
2140+
if (isset($initialScope->moreSpecificTypes[$exprString])) {
2141+
continue;
2142+
}
2143+
2144+
unset($moreSpecificTypeHolders[$exprString]);
2145+
}
2146+
21382147
return $this->scopeFactory->create(
21392148
$this->context,
21402149
$this->isDeclareStrictTypes(),
21412150
$this->getFunction(),
21422151
$this->getNamespace(),
21432152
$variableTypeHolders,
2144-
[],
2153+
$moreSpecificTypeHolders,
21452154
$this->inClosureBindScopeClass,
21462155
$this->getAnonymousFunctionReturnType(),
21472156
$this->getInFunctionCall(),

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5445,6 +5445,21 @@ public function dataForeachLoopVariables(): array
54455445
'$integers',
54465446
"'afterLoop'",
54475447
],
5448+
[
5449+
'array<string, mixed>', // should be 'array<string, 1|2|3>',
5450+
'$this->property',
5451+
"'begin'",
5452+
],
5453+
[
5454+
'array<string, mixed>', // should be 'array<string, 1|2|3>',
5455+
'$this->property',
5456+
"'end'",
5457+
],
5458+
[
5459+
'array<string, mixed>', // should be 'array<string, 1|2|3>',
5460+
'$this->property',
5461+
"'afterLoop'",
5462+
],
54485463
];
54495464
}
54505465

tests/PHPStan/Analyser/data/foreach-loop-variables.php

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,69 @@
22

33
namespace LoopVariables;
44

5-
function () {
6-
$foo = null;
7-
$key = null;
8-
$val = null;
9-
10-
$integers = [];
11-
$i = 0;
12-
foreach ([1, 2, 3] as $key => $val) {
13-
'begin';
14-
$foo = new Foo();
15-
'afterAssign';
16-
17-
$foo && $i++;
18-
19-
$nullableInt = $val;
20-
if (rand(0, 1) === 1) {
21-
$nullableInt = null;
22-
}
5+
class ForeachFoo
6+
{
237

24-
if (something()) {
25-
$foo = new Bar();
26-
break;
27-
}
28-
if (something()) {
29-
$foo = new Baz();
30-
return;
31-
}
32-
if (something()) {
33-
$foo = new Lorem();
34-
continue;
35-
}
8+
/** @var int[] */
9+
private $property = [];
3610

37-
if ($nullableInt === null) {
38-
continue;
39-
}
11+
public function doFoo(string $s)
12+
{
13+
$foo = null;
14+
$key = null;
15+
$val = null;
4016

41-
$integers[] = $nullableInt;
17+
$this->property = [];
4218

43-
'end';
44-
}
19+
$integers = [];
20+
$i = 0;
21+
foreach ([1, 2, 3] as $key => $val) {
22+
'begin';
23+
$foo = new Foo();
24+
'afterAssign';
25+
26+
$foo && $i++;
27+
28+
$nullableInt = $val;
29+
if (rand(0, 1) === 1) {
30+
$nullableInt = null;
31+
}
32+
33+
if (something()) {
34+
$foo = new Bar();
35+
break;
36+
}
37+
if (something()) {
38+
$foo = new Baz();
39+
return;
40+
}
41+
if (something()) {
42+
$foo = new Lorem();
43+
continue;
44+
}
4545

46-
$emptyForeachKey = null;
47-
$emptyForeachVal = null;
48-
foreach ([1, 2, 3] as $emptyForeachKey => $emptyForeachVal) {
46+
if ($nullableInt === null) {
47+
continue;
48+
}
49+
50+
if (isset($this->property[$s])) {
51+
continue;
52+
}
53+
54+
$this->property[$s] = $val;
55+
56+
$integers[] = $nullableInt;
57+
58+
'end';
59+
}
60+
61+
$emptyForeachKey = null;
62+
$emptyForeachVal = null;
63+
foreach ([1, 2, 3] as $emptyForeachKey => $emptyForeachVal) {
64+
65+
}
4966

67+
'afterLoop';
5068
}
5169

52-
'afterLoop';
53-
};
70+
}

0 commit comments

Comments
 (0)