Skip to content

Commit 906e8bf

Browse files
staabmphpstan-bot
authored andcommitted
Fix phpstan/phpstan#6830: Variable inside loop might not be defined
- Fixed intersectConditionalExpressions() in MutatingScope to merge conditional expression holders with same conditions but different result types - Previously, when a variable was conditionally defined before a loop and used inside the loop under the same condition, the conditional expression was lost during scope merging because the result type changed across iterations - Added fallback logic: when exact key matching fails, match holders by their condition expressions and merge result types via TypeCombinator::union() - Extended regression test in tests/PHPStan/Rules/Variables/data/bug-6830.php
1 parent 0c740e3 commit 906e8bf

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4039,13 +4039,63 @@ private function intersectConditionalExpressions(array $otherConditionalExpressi
40394039
}
40404040

40414041
$otherHolders = $otherConditionalExpressions[$exprString];
4042+
$allKeysMatch = true;
40424043
foreach (array_keys($holders) as $key) {
40434044
if (!array_key_exists($key, $otherHolders)) {
4044-
continue 2;
4045+
$allKeysMatch = false;
4046+
break;
4047+
}
4048+
}
4049+
4050+
if ($allKeysMatch) {
4051+
$newConditionalExpressions[$exprString] = $holders;
4052+
continue;
4053+
}
4054+
4055+
// When exact keys don't match (e.g. result types differ across loop iterations),
4056+
// try to merge holders that have the same conditions but different result types.
4057+
$mergedHolders = [];
4058+
foreach ($holders as $holder) {
4059+
$conditionHolders = $holder->getConditionExpressionTypeHolders();
4060+
$conditionKeys = array_keys($conditionHolders);
4061+
4062+
foreach ($otherHolders as $otherHolder) {
4063+
$otherConditionHolders = $otherHolder->getConditionExpressionTypeHolders();
4064+
if (array_keys($otherConditionHolders) !== $conditionKeys) {
4065+
continue;
4066+
}
4067+
4068+
$conditionsMatch = true;
4069+
foreach ($conditionHolders as $condKey => $condHolder) {
4070+
if (!$condHolder->equals($otherConditionHolders[$condKey])) {
4071+
$conditionsMatch = false;
4072+
break;
4073+
}
4074+
}
4075+
4076+
if (!$conditionsMatch) {
4077+
continue;
4078+
}
4079+
4080+
$ourTypeHolder = $holder->getTypeHolder();
4081+
$otherTypeHolder = $otherHolder->getTypeHolder();
4082+
$mergedType = TypeCombinator::union($ourTypeHolder->getType(), $otherTypeHolder->getType());
4083+
$mergedCertainty = TrinaryLogic::maxMin($ourTypeHolder->getCertainty(), $otherTypeHolder->getCertainty());
4084+
4085+
$mergedConditionalExpression = new ConditionalExpressionHolder(
4086+
$conditionHolders,
4087+
new ExpressionTypeHolder($ourTypeHolder->getExpr(), $mergedType, $mergedCertainty),
4088+
);
4089+
$mergedHolders[$mergedConditionalExpression->getKey()] = $mergedConditionalExpression;
4090+
break;
40454091
}
40464092
}
40474093

4048-
$newConditionalExpressions[$exprString] = $holders;
4094+
if ($mergedHolders === []) {
4095+
continue;
4096+
}
4097+
4098+
$newConditionalExpressions[$exprString] = $mergedHolders;
40494099
}
40504100

40514101
return $newConditionalExpressions;

tests/PHPStan/Rules/Variables/data/bug-6830.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,31 @@ function test(array $bools): void
1616
}
1717
}
1818
}
19+
20+
function test2(bool $do): void
21+
{
22+
if ($do) {
23+
$x = 9999;
24+
}
25+
26+
foreach ([1, 2, 3] as $whatever) {
27+
if ($do) {
28+
if ($x) {
29+
$x = 123;
30+
}
31+
}
32+
}
33+
}
34+
35+
function test3(bool $do): void
36+
{
37+
if ($do) {
38+
$x = 'hello';
39+
}
40+
41+
foreach ([1, 2, 3] as $whatever) {
42+
if ($do) {
43+
echo $x;
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)