diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8de4e331b6..0cb8e4bd0e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4039,13 +4039,63 @@ private function intersectConditionalExpressions(array $otherConditionalExpressi } $otherHolders = $otherConditionalExpressions[$exprString]; + $allKeysMatch = true; foreach (array_keys($holders) as $key) { if (!array_key_exists($key, $otherHolders)) { - continue 2; + $allKeysMatch = false; + break; + } + } + + if ($allKeysMatch) { + $newConditionalExpressions[$exprString] = $holders; + continue; + } + + // When exact keys don't match (e.g. result types differ across loop iterations), + // try to merge holders that have the same conditions but different result types. + $mergedHolders = []; + foreach ($holders as $holder) { + $conditionHolders = $holder->getConditionExpressionTypeHolders(); + $conditionKeys = array_keys($conditionHolders); + + foreach ($otherHolders as $otherHolder) { + $otherConditionHolders = $otherHolder->getConditionExpressionTypeHolders(); + if (array_keys($otherConditionHolders) !== $conditionKeys) { + continue; + } + + $conditionsMatch = true; + foreach ($conditionHolders as $condKey => $condHolder) { + if (!$condHolder->equals($otherConditionHolders[$condKey])) { + $conditionsMatch = false; + break; + } + } + + if (!$conditionsMatch) { + continue; + } + + $ourTypeHolder = $holder->getTypeHolder(); + $otherTypeHolder = $otherHolder->getTypeHolder(); + $mergedType = TypeCombinator::union($ourTypeHolder->getType(), $otherTypeHolder->getType()); + $mergedCertainty = TrinaryLogic::maxMin($ourTypeHolder->getCertainty(), $otherTypeHolder->getCertainty()); + + $mergedConditionalExpression = new ConditionalExpressionHolder( + $conditionHolders, + new ExpressionTypeHolder($ourTypeHolder->getExpr(), $mergedType, $mergedCertainty), + ); + $mergedHolders[$mergedConditionalExpression->getKey()] = $mergedConditionalExpression; + break; } } - $newConditionalExpressions[$exprString] = $holders; + if ($mergedHolders === []) { + continue; + } + + $newConditionalExpressions[$exprString] = $mergedHolders; } return $newConditionalExpressions; diff --git a/tests/PHPStan/Rules/Variables/data/bug-6830.php b/tests/PHPStan/Rules/Variables/data/bug-6830.php index 1a4e5a4490..1c5e950d53 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-6830.php +++ b/tests/PHPStan/Rules/Variables/data/bug-6830.php @@ -16,3 +16,31 @@ function test(array $bools): void } } } + +function test2(bool $do): void +{ + if ($do) { + $x = 9999; + } + + foreach ([1, 2, 3] as $whatever) { + if ($do) { + if ($x) { + $x = 123; + } + } + } +} + +function test3(bool $do): void +{ + if ($do) { + $x = 'hello'; + } + + foreach ([1, 2, 3] as $whatever) { + if ($do) { + echo $x; + } + } +}