Skip to content

Commit af24514

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents d156d37 + 0fe9f46 commit af24514

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed

src/Analyser/MutatingScope.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3744,7 +3744,7 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
37443744
$finallyScope->nativeExpressionTypes,
37453745
$originalFinallyScope->nativeExpressionTypes,
37463746
),
3747-
$this->conditionalExpressions,
3747+
$this->intersectConditionalExpressions($finallyScope->conditionalExpressions),
37483748
$this->inClosureBindScopeClasses,
37493749
$this->anonymousFunctionReflection,
37503750
$this->inFirstLevelStatement,
@@ -3889,7 +3889,7 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
38893889
$this->getNamespace(),
38903890
$expressionTypes,
38913891
$nativeTypes,
3892-
$this->conditionalExpressions,
3892+
$this->intersectConditionalExpressions($finalScope->conditionalExpressions),
38933893
$this->inClosureBindScopeClasses,
38943894
$this->anonymousFunctionReflection,
38953895
$this->inFirstLevelStatement,
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+
use PHPUnit\Framework\Attributes\DataProvider;
7+
8+
class Bug14446Test extends TypeInferenceTestCase
9+
{
10+
11+
public static function dataFileAsserts(): iterable
12+
{
13+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-14446.php');
14+
}
15+
16+
/**
17+
* @param mixed ...$args
18+
*/
19+
#[DataProvider('dataFileAsserts')]
20+
public function testFileAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args,
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
public static function getAdditionalConfigFiles(): array
30+
{
31+
return [
32+
__DIR__ . '/bug-14446.neon',
33+
];
34+
}
35+
36+
}
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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14446;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function test(bool $initial): void {
8+
$current = $initial;
9+
10+
while (true) {
11+
assertType('bool', $initial);
12+
if (!$current) {
13+
assertType('bool', $initial);
14+
break;
15+
}
16+
17+
$items = [1];
18+
foreach ($items as $item) {
19+
$current = false;
20+
}
21+
}
22+
23+
assertType('bool', $initial);
24+
var_dump($initial === true);
25+
}
26+
27+
function testMaybeIterable(bool $initial): void {
28+
$current = $initial;
29+
30+
while (true) {
31+
assertType('bool', $initial);
32+
if (!$current) {
33+
assertType('bool', $initial);
34+
break;
35+
}
36+
37+
$items = rand() > 0 ? [1] : [];
38+
foreach ($items as $item) {
39+
$current = false;
40+
}
41+
}
42+
43+
assertType('bool', $initial);
44+
var_dump($initial === true);
45+
}
46+
47+
function testFinally(bool $initial): void {
48+
$current = $initial;
49+
try {
50+
// nothing
51+
} finally {
52+
$current = false;
53+
}
54+
assertType('false', $current);
55+
assertType('bool', $initial);
56+
if (!$current) {
57+
assertType('bool', $initial);
58+
}
59+
}
60+
61+
function testFinallyWithCatch(bool $initial): void {
62+
$current = $initial;
63+
try {
64+
doSomething();
65+
} catch (\Exception $e) {
66+
// nothing
67+
} finally {
68+
$current = false;
69+
}
70+
assertType('false', $current);
71+
if (!$current) {
72+
assertType('bool', $initial);
73+
}
74+
assertType('bool', $initial);
75+
}
76+
77+
function doSomething(): void {}
78+
79+
/**
80+
* @param mixed $value
81+
*/
82+
function testForeachKeyOverwrite($value): void {
83+
if (is_array($value) && $value !== []) {
84+
$hasOnlyStringKey = true;
85+
foreach (array_keys($value) as $key) {
86+
if (is_int($key)) {
87+
$hasOnlyStringKey = false;
88+
break;
89+
}
90+
}
91+
92+
assertType('bool', $hasOnlyStringKey);
93+
94+
if ($hasOnlyStringKey) {
95+
// $key should not be in scope here with polluteScopeWithAlwaysIterableForeach: false
96+
// Second foreach should not report "Foreach overwrites $key with its key variable"
97+
foreach ($value as $key => $element) {
98+
assertType('(int|string)', $key);
99+
}
100+
}
101+
}
102+
}

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class StrictComparisonOfDifferentTypesRuleTest extends RuleTestCase
2121

2222
private bool $treatPhpDocTypesAsCertain = true;
2323

24+
private bool $polluteScopeWithAlwaysIterableForeach = true;
25+
2426
protected function getRule(): Rule
2527
{
2628
// @phpstan-ignore argument.type
@@ -42,6 +44,11 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool
4244
return $this->treatPhpDocTypesAsCertain;
4345
}
4446

47+
protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool
48+
{
49+
return $this->polluteScopeWithAlwaysIterableForeach;
50+
}
51+
4552
public function testStrictComparison(): void
4653
{
4754
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
@@ -1216,4 +1223,10 @@ public function testBug13421(): void
12161223
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-13421.php'], []);
12171224
}
12181225

1226+
public function testBug14446(): void
1227+
{
1228+
$this->polluteScopeWithAlwaysIterableForeach = false;
1229+
$this->analyse([__DIR__ . '/../../Analyser/data/bug-14446.php'], []);
1230+
}
1231+
12191232
}

0 commit comments

Comments
 (0)