From 0f4db694df248fdd2064b5f6e0be0d6cd4fbbebe 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 | 44 ++++++++++++++----- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/Psalm/Internal/Diff/FileDiffer.php b/src/Psalm/Internal/Diff/FileDiffer.php index e9c06c02ae4..668c6456bc5 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 7fac7954f14..6977e465876 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 af7e1051dbd..926e76e4a4e 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 5ae0b004b6c..cfb5827ed48 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 8a189c2854f..f4d8aefc2b2 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,26 +456,48 @@ function takesInt(int $i): void{ return; }', ], - 'SKIPPED-wrongLoopAssertion' => [ + '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; + }', + ], + 'wrongLoopAssertion' => [ 'code' => ' 0 && rand(0,1)) { continue; } - /** @psalm-trace $i */; - $type_tokens[$i] = ""; - /** @psalm-trace $type_tokens */; - - if($i > 1){ + if ($i > 1) { $type_tokens[$i - 2]; } } @@ -484,7 +505,6 @@ function a(): array { return []; } - /** @return array */ function getArray(): array { return [];