Skip to content

Commit 32c94b9

Browse files
VincentLangletphpstan-bot
authored andcommitted
Do not create conditional expressions for non-Variable expressions absent from the other branch
- In `MutatingScope::createConditionalExpressions()`, skip creating a conditional expression when the target expression (e.g. an array dim fetch like `$R['aa']`) is not tracked in the other branch, the expression is not a Variable, and the guard type overlaps with the other branch's guard type. - This prevents incorrect type narrowing where an array dim fetch or property fetch from an `elseif` condition leaks into a subsequent `if` block that checks a variable set in multiple branches. - The Variable exception preserves the existing behavior for variable certainty tracking (e.g. a variable defined in only one branch correctly gets Yes certainty via conditional expressions). - Tested with array dim fetch, nested array dim fetch, property fetch, and multiple elseif variants.
1 parent 04a99c1 commit 32c94b9

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

src/Analyser/MutatingScope.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3649,6 +3649,15 @@ private function createConditionalExpressions(
36493649
) {
36503650
continue;
36513651
}
3652+
if (
3653+
!array_key_exists($exprString, $theirExpressionTypes)
3654+
&& !$holder->getExpr() instanceof Variable
3655+
&& array_key_exists($guardExprString, $theirExpressionTypes)
3656+
&& $theirExpressionTypes[$guardExprString]->getCertainty()->yes()
3657+
&& !$guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->no()
3658+
) {
3659+
continue;
3660+
}
36523661
$conditionalExpression = new ConditionalExpressionHolder([$guardExprString => $guardHolder], $holder);
36533662
$conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
36543663
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Bug14469Nsrt;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function t(array $R, bool $var1, object $user): void {
8+
$aa = null;
9+
10+
if ($var1) {
11+
$aa = $user->id === 10 ? 2 : null;
12+
} elseif ($R['aa']) {
13+
$aa = $R['aa'];
14+
}
15+
16+
if ($aa) {
17+
assertType('mixed', $R['aa']);
18+
}
19+
}

tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,10 @@ public function testBug6702(): void
236236
$this->analyse([__DIR__ . '/data/bug-6702.php'], []);
237237
}
238238

239+
public function testBug14469(): void
240+
{
241+
$this->treatPhpDocTypesAsCertain = true;
242+
$this->analyse([__DIR__ . '/data/bug-14469.php'], []);
243+
}
244+
239245
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Bug14469;
4+
5+
function t(array $R, bool $var1, object $user, bool $is): array {
6+
$aa = null;
7+
8+
if ($var1) {
9+
$aa = $user->id === 10 ? 2 : null;
10+
} elseif ($R['aa']) {
11+
$aa = $R['aa'];
12+
}
13+
14+
if ($aa) {
15+
if (!$R['aa']) {
16+
return [];
17+
}
18+
}
19+
return $R;
20+
}
21+
22+
/** Property fetch variant */
23+
function propertyFetch(object $obj, bool $var1, object $user): void {
24+
$aa = null;
25+
26+
if ($var1) {
27+
$aa = $user->id === 10 ? 2 : null;
28+
} elseif ($obj->prop) {
29+
$aa = $obj->prop;
30+
}
31+
32+
if ($aa) {
33+
if (!$obj->prop) {
34+
return;
35+
}
36+
}
37+
}
38+
39+
/** Nested array fetch variant */
40+
function nestedArrayFetch(array $R, bool $var1, object $user): void {
41+
$aa = null;
42+
43+
if ($var1) {
44+
$aa = $user->id === 10 ? 2 : null;
45+
} elseif ($R['a']['b']) {
46+
$aa = $R['a']['b'];
47+
}
48+
49+
if ($aa) {
50+
if (!$R['a']['b']) {
51+
return;
52+
}
53+
}
54+
}
55+
56+
/** Multiple elseif branches */
57+
function multipleElseif(array $R, bool $var1, bool $var2, object $user): void {
58+
$aa = null;
59+
60+
if ($var1) {
61+
$aa = $user->id === 10 ? 2 : null;
62+
} elseif ($var2) {
63+
$aa = 5;
64+
} elseif ($R['aa']) {
65+
$aa = $R['aa'];
66+
}
67+
68+
if ($aa) {
69+
if (!$R['aa']) {
70+
return;
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)