diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8c54f66bd0..fcee38dc57 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4717,7 +4717,6 @@ private static function generalizeType(Type $a, Type $b): Type $resultTypes = []; foreach ([ - $constantIntegers, $constantFloats, $constantBooleans, $constantStrings, @@ -4766,6 +4765,8 @@ private static function generalizeType(Type $a, Type $b): Type ); } } + } elseif (count($constantArrays['b']) > 0) { + $resultTypes[] = TypeCombinator::union(...$constantArrays['b']); } if (count($generalArrays['a']) > 0) { @@ -4801,6 +4802,59 @@ private static function generalizeType(Type $a, Type $b): Type TypeCombinator::union(self::generalizeType($aValueType, $bValueType)), ); } + } elseif (count($generalArrays['b']) > 0) { + $resultTypes[] = TypeCombinator::union(...$generalArrays['b']); + } + + if (count($constantIntegers['a']) > 0) { + if (count($constantIntegers['b']) === 0) { + $resultTypes[] = TypeCombinator::union(...$constantIntegers['a']); + } else { + $constantIntegersA = TypeCombinator::union(...$constantIntegers['a']); + $constantIntegersB = TypeCombinator::union(...$constantIntegers['b']); + + if ($constantIntegersA->equals($constantIntegersB)) { + $resultTypes[] = $constantIntegersA; + } else { + $min = null; + $max = null; + foreach ($constantIntegers['a'] as $int) { + if ($min === null || $int->getValue() < $min) { + $min = $int->getValue(); + } + if ($max !== null && $int->getValue() <= $max) { + continue; + } + + $max = $int->getValue(); + } + + $gotGreater = false; + $gotSmaller = false; + foreach ($constantIntegers['b'] as $int) { + if ($int->getValue() > $max) { + $gotGreater = true; + } + if ($int->getValue() >= $min) { + continue; + } + + $gotSmaller = true; + } + + if ($gotGreater && $gotSmaller) { + $resultTypes[] = new IntegerType(); + } elseif ($gotGreater) { + $resultTypes[] = IntegerRangeType::fromInterval($min, null); + } elseif ($gotSmaller) { + $resultTypes[] = IntegerRangeType::fromInterval(null, $max); + } else { + $resultTypes[] = TypeCombinator::union($constantIntegersA, $constantIntegersB); + } + } + } + } elseif (count($constantIntegers['b']) > 0) { + $resultTypes[] = TypeCombinator::union(...$constantIntegers['b']); } return TypeCombinator::union(...$resultTypes, ...$otherTypes); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0d2bba03a4..10062cab46 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2943,6 +2943,9 @@ private function processClosureNode( if ($closureScope->equals($prevScope)) { break; } + if ($count >= self::GENERALIZE_AFTER_ITERATION) { + $closureScope = $prevScope->generalizeWith($closureScope); + } $count++; } while ($count < self::LOOP_SCOPE_ITERATIONS); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index d28ee02b4c..5a81d613b7 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -866,7 +866,7 @@ public function dataConstantTypes(): array [ $testScope, 'yetAnotherVariableInClosurePassedByReference', - 'int<0, max>', // could be 0|1 + '0|1', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/data/for-loop-i-type.php b/tests/PHPStan/Analyser/data/for-loop-i-type.php index f3cf8a1a8e..603b010f86 100644 --- a/tests/PHPStan/Analyser/data/for-loop-i-type.php +++ b/tests/PHPStan/Analyser/data/for-loop-i-type.php @@ -18,7 +18,7 @@ public function doBar() { assertType(\stdClass::class, $foo); for($i = 50; $i > 0; $i--) { - assertType('int<1, max>', $i); // could be int<1, 50> + assertType('int<1, 50>', $i); } assertType('0', $i); @@ -48,10 +48,10 @@ public function doCount2() { public function doBaz() { for($i = 1; $i < 50; $i += 2) { - assertType('int<1, 49>', $i); + assertType('1|int<3, 49>', $i); } - assertType('int<50, 51>', $i); + assertType('int<50, max>', $i); } public function doLOrem() { diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 7c96a79c10..0621c58689 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -76,12 +76,12 @@ function () { $i = 5; while ($i-- > 0) { - assertType('int<0, max>', $i); // should improved to be int<0, 4> + assertType('int<0, 4>', $i); } $i = 5; while (--$i > 0) { - assertType('int<1, max>', $i); // should improved to be int<1, 4> + assertType('int<1, 4>', $i); } }; diff --git a/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php b/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php index eb7d0d5c77..09ea18d059 100644 --- a/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php +++ b/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php @@ -23,3 +23,11 @@ function (int $i): void { } } }; + +function (): void { + for ($X = -3; $X <= 3; ++$X){ + for ($Z = -3; $Z <= 3; ++$Z){ + + } + } +};