Skip to content

Commit 0c740e3

Browse files
phpstan-botstaabm
andauthored
Fix phpstan/phpstan#14117: Incorrect 'Variable $value might not be defined.' (#5125)
Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent ea5f10a commit 0c740e3

File tree

8 files changed

+338
-2
lines changed

8 files changed

+338
-2
lines changed

src/Analyser/MutatingScope.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4094,6 +4094,14 @@ private function createConditionalExpressions(
40944094
continue;
40954095
}
40964096

4097+
if (
4098+
array_key_exists($exprString, $newVariableTypes)
4099+
&& !$newVariableTypes[$exprString]->getCertainty()->equals($holder->getCertainty())
4100+
&& $newVariableTypes[$exprString]->equalTypes($holder)
4101+
) {
4102+
continue;
4103+
}
4104+
40974105
unset($newVariableTypes[$exprString]);
40984106
}
40994107

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
127127
return null;
128128
}
129129

130-
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier); // @phpstan-ignore variable.undefined
130+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier);
131131
$classReflection = $this->nodeToReflection($reflector, $fetchedClassNode);
132132
$this->cache->save($reflectionCacheKey, $variableCacheKey, $classReflection->exportToCache());
133133

@@ -171,7 +171,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
171171
return null;
172172
}
173173

174-
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier); // @phpstan-ignore variable.undefined
174+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier);
175175
$constantReflection = $this->nodeToReflection(
176176
$reflector,
177177
$fetchedConstantNode,

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,26 @@ public function testBug5919(): void
13231323
$this->analyse([__DIR__ . '/data/bug-5919.php'], []);
13241324
}
13251325

1326+
public function testBug8430(): void
1327+
{
1328+
$this->cliArgumentsVariablesRegistered = true;
1329+
$this->polluteScopeWithLoopInitialAssignments = false;
1330+
$this->checkMaybeUndefinedVariables = true;
1331+
$this->polluteScopeWithAlwaysIterableForeach = true;
1332+
1333+
$this->analyse([__DIR__ . '/data/bug-8430.php'], []);
1334+
}
1335+
1336+
public function testBug8430b(): void
1337+
{
1338+
$this->cliArgumentsVariablesRegistered = true;
1339+
$this->polluteScopeWithLoopInitialAssignments = false;
1340+
$this->checkMaybeUndefinedVariables = true;
1341+
$this->polluteScopeWithAlwaysIterableForeach = true;
1342+
1343+
$this->analyse([__DIR__ . '/data/bug-8430b.php'], []);
1344+
}
1345+
13261346
public function testBug5477(): void
13271347
{
13281348
$this->cliArgumentsVariablesRegistered = true;
@@ -1333,4 +1353,51 @@ public function testBug5477(): void
13331353
$this->analyse([__DIR__ . '/data/bug-5477.php'], []);
13341354
}
13351355

1356+
public function testBug10657(): void
1357+
{
1358+
$this->cliArgumentsVariablesRegistered = true;
1359+
$this->polluteScopeWithLoopInitialAssignments = false;
1360+
$this->checkMaybeUndefinedVariables = true;
1361+
$this->polluteScopeWithAlwaysIterableForeach = true;
1362+
1363+
$this->analyse([__DIR__ . '/data/bug-10657.php'], []);
1364+
}
1365+
1366+
public function testBug6830(): void
1367+
{
1368+
$this->cliArgumentsVariablesRegistered = true;
1369+
$this->polluteScopeWithLoopInitialAssignments = false;
1370+
$this->checkMaybeUndefinedVariables = true;
1371+
$this->polluteScopeWithAlwaysIterableForeach = true;
1372+
1373+
$this->analyse([__DIR__ . '/data/bug-6830.php'], []);
1374+
}
1375+
1376+
public function testBug14117(): void
1377+
{
1378+
$this->cliArgumentsVariablesRegistered = true;
1379+
$this->polluteScopeWithLoopInitialAssignments = false;
1380+
$this->checkMaybeUndefinedVariables = true;
1381+
$this->polluteScopeWithAlwaysIterableForeach = true;
1382+
1383+
$this->analyse([__DIR__ . '/data/bug-14117.php'], [
1384+
[
1385+
'Variable $value might not be defined.',
1386+
33,
1387+
],
1388+
[
1389+
'Variable $value might not be defined.',
1390+
49,
1391+
],
1392+
[
1393+
'Undefined variable: $value',
1394+
65,
1395+
],
1396+
[
1397+
'Undefined variable: $value',
1398+
81,
1399+
],
1400+
]);
1401+
}
1402+
13361403
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Bug10657;
4+
5+
class HelloWorld
6+
{
7+
public function sayHello(bool $flag): void
8+
{
9+
for ($i = 0; $i < 10; $i++) {
10+
if ($flag) {
11+
$foo = 'bar';
12+
}
13+
if ($flag) {
14+
echo $foo;
15+
}
16+
}
17+
}
18+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14117;
4+
5+
function foo(): void {
6+
$key = rand(0, 2);
7+
8+
if ($key === 2) {
9+
$value = 'test';
10+
}
11+
12+
if ($key === 1) {
13+
$value = 'test';
14+
}
15+
16+
if ($key === 1) {
17+
echo $value;
18+
}
19+
}
20+
21+
function bar(): void {
22+
$key = rand(0, 2);
23+
24+
if ($key === 2) {
25+
$value = 'two';
26+
}
27+
28+
if ($key === 1) {
29+
$value = 'one';
30+
}
31+
32+
if ($key === 2) {
33+
echo $value;
34+
}
35+
}
36+
37+
function baz(): void {
38+
$key = rand(0, 3);
39+
40+
if ($key === 1) {
41+
$value = 'one';
42+
}
43+
44+
if ($key === 2) {
45+
$value = 'two';
46+
}
47+
48+
if ($key === 3) {
49+
echo $value; // SHOULD report "is not defined"
50+
}
51+
}
52+
53+
function boo(): void {
54+
$key = rand(0, 2);
55+
56+
if ($key === 1) {
57+
$value = 'test';
58+
}
59+
60+
if ($key === 1) {
61+
unset($value);
62+
}
63+
64+
if ($key === 1) {
65+
echo $value;
66+
}
67+
}
68+
69+
function loo(): void {
70+
$key = rand(0, 2);
71+
72+
if ($key === 1) {
73+
$value = 'test';
74+
}
75+
76+
if ($key === 1 || $key === 2) {
77+
unset($value);
78+
}
79+
80+
if ($key === 1) {
81+
echo $value;
82+
}
83+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bug6830;
6+
7+
/** @param array<bool> $bools */
8+
function test(array $bools): void
9+
{
10+
foreach ($bools as $bool) {
11+
if ($bool) {
12+
$foo = 'foo';
13+
}
14+
if ($bool) {
15+
echo $foo;
16+
}
17+
}
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bug8430;
6+
7+
class B
8+
{
9+
public function abc(string $a, bool $b): void
10+
{
11+
for ($i = 1; $i <= 5; $i++) {
12+
if (!$b) {
13+
$arr = ['a' => 1];
14+
}
15+
if (!$a && !$b) {
16+
echo $arr['a'];
17+
}
18+
}
19+
}
20+
21+
public function def(string $a, bool $b): void
22+
{
23+
if (!$b) {
24+
$arr = ['a' => 1];
25+
}
26+
if (!$a && !$b) {
27+
echo $arr['a'];
28+
}
29+
}
30+
}
31+
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bug8430b;
6+
7+
class A
8+
{
9+
/** @var A[][] */
10+
public array $a;
11+
/** @var A[] */
12+
public array $b;
13+
/** @var array<string,string|string[]> */
14+
public array $c;
15+
public string $d;
16+
}
17+
18+
class B
19+
{
20+
private function abc(): void
21+
{
22+
}
23+
24+
/**
25+
* @param A[] $a
26+
* @param array<string,string> $b
27+
*/
28+
public function def(array $a, array $b, string $c, bool $d): void
29+
{
30+
$e = false;
31+
$f = false;
32+
switch ($b['repeat'] ?? null) {
33+
case 'Y':
34+
$e = true;
35+
break;
36+
case 'A':
37+
$e = true;
38+
$f = true;
39+
break;
40+
}
41+
$g = 5;
42+
$h = 0;
43+
for ($i = 1; $i <= $g; $i++) {
44+
if (!$d) {
45+
$arr = ['a' => 1];
46+
}
47+
$j = $a[$i] ?? null;
48+
if ($j) {
49+
/** @var array<A[]> $k */
50+
$k = [];
51+
if ($e) {
52+
foreach ($j->a as $l) {
53+
/** @var A[] $m */
54+
$m = $f || empty($k) ? $j->b : [];
55+
array_push($m, ...$l);
56+
array_push($k, $m);
57+
}
58+
if (empty($k)) {
59+
array_push($k, $j->b);
60+
}
61+
} else {
62+
array_push($k, $j->b);
63+
foreach ($j->a as $l) {
64+
array_push($k[0], ...$l);
65+
break;
66+
}
67+
}
68+
foreach ($k as $n) {
69+
if (!$d) {
70+
foreach ($n as $o) {
71+
switch ($o->c['x'] ?? '') {
72+
case 'y':
73+
$p = $o->c[$o->d] ?? null;
74+
if (is_array($p)) {
75+
$this->abc();
76+
}
77+
break;
78+
case 'z':
79+
$p = $o->c[$o->d] ?? null;
80+
if (is_array($p)) {
81+
$this->abc();
82+
}
83+
break;
84+
default:
85+
$this->abc();
86+
break;
87+
}
88+
}
89+
}
90+
if (!empty($n)) {
91+
$h++;
92+
}
93+
}
94+
}
95+
if (!$c && !$d) {
96+
echo $arr['a'];
97+
}
98+
}
99+
}
100+
101+
public function ghi(string $a, bool $b): void
102+
{
103+
if (!$b) {
104+
$arr = ['a' => 1];
105+
}
106+
$this->abc();
107+
if (!$a && !$b) {
108+
echo $arr['a'];
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)