From 2a377ef906dbe9a7d1db9b6b5a6e7ecc6dd9601f Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Sun, 18 Dec 2022 14:06:23 -0600 Subject: [PATCH] Allow conditions inside loops to preserve or narrow int range Fixes #7555, fixes #8020, fixes #8865 --- src/Psalm/Internal/Diff/FileDiffer.php | 2 -- .../Type/Comparator/ArrayTypeComparator.php | 4 --- .../Type/SimpleAssertionReconciler.php | 8 ----- .../Type/SimpleNegatedAssertionReconciler.php | 8 ----- tests/IntRangeTest.php | 32 +++++++++++++++++-- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/Psalm/Internal/Diff/FileDiffer.php b/src/Psalm/Internal/Diff/FileDiffer.php index 6d72bc038ce..4673d21a5c7 100644 --- a/src/Psalm/Internal/Diff/FileDiffer.php +++ b/src/Psalm/Internal/Diff/FileDiffer.php @@ -77,8 +77,6 @@ private static function extractDiff(array $trace, int $x, int $y, array $a, arra { $result = []; for ($d = count($trace) - 1; $d >= 0; --$d) { - // Todo: fix integer ranges in fors - /** @var int<0, max> $d */ $v = $trace[$d]; $k = $x - $y; diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index 0e0210a1605..975c6a32e3e 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -123,10 +123,6 @@ public static function isContainedBy( } foreach ($input_type_part->type_params as $i => $input_param) { - if ($i > 1) { - break; - } - $container_param = $container_type_part->type_params[$i]; if ($i === 0 diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index f05ba1ef504..4d5c409be33 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -1962,10 +1962,6 @@ private static function reconcileIsGreaterThan( } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { - if ($inside_loop) { - continue; - } - if ($atomic_type instanceof TIntRange) { if ($atomic_type->contains($assertion_value)) { // if the range contains the assertion, the range must be adapted @@ -2075,10 +2071,6 @@ private static function reconcileIsLessThan( } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { - if ($inside_loop) { - continue; - } - if ($atomic_type instanceof TIntRange) { if ($atomic_type->contains($assertion_value)) { // if the range contains the assertion, the range must be adapted diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index edeef3adb43..9bf3c658a78 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -1840,10 +1840,6 @@ private static function reconcileIsLessThanOrEqualTo( } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { - if ($inside_loop) { - continue; - } - if ($atomic_type instanceof TIntRange) { if ($atomic_type->contains($assertion_value)) { // if the range contains the assertion, the range must be adapted @@ -1951,10 +1947,6 @@ private static function reconcileIsGreaterThanOrEqualTo( } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { - if ($inside_loop) { - continue; - } - if ($atomic_type instanceof TIntRange) { if ($atomic_type->contains($assertion_value)) { // if the range contains the assertion, the range must be adapted diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index 1a9b78a6124..c3e5f51af76 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -443,9 +443,8 @@ function getInt() '$remainder===' => 'int' ] ], - 'SKIPPED-IntRangeRestrictWhenUntouched' => [ + 'IntRangeRestrictWhenUntouched' => [ 'code' => ' 1) { takesInt($i); @@ -457,6 +456,35 @@ function takesInt(int $i): void{ return; }', ], + 'intRangeExpandedByLoop' => [ + 'code' => ' $i */ + function takesInt(int $i): void{ + return; + }', + ], + 'statementsInLoopPreserveNonNegativeIntRange' => [ + 'code' => ' 0) { + $sum += $i; + } + } + takesNonNegativeInt($sum); + + /** @psalm-param int<0, max> $i */ + function takesNonNegativeInt(int $i): void{ + return; + }', + ], 'SKIPPED-wrongLoopAssertion' => [ 'code' => '