From ec5eae33476e7bd4d985e1c57d6403811064469d Mon Sep 17 00:00:00 2001 From: robchett Date: Sun, 8 Oct 2023 16:42:44 +0100 Subject: [PATCH 1/4] Maintain loop start value after an increment --- .../BinaryOp/ArithmeticOpAnalyzer.php | 3 ++- tests/BinaryOperationTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 90e8f78b881..478c34c2896 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -822,8 +822,9 @@ private static function analyzeOperands( } } } else { + $start = $left_type_part instanceof TLiteralInt ? $left_type_part->value : 1; $result_type = Type::combineUnionTypes( - $always_positive ? Type::getIntRange(1, null) : Type::getInt(true), + $always_positive ? Type::getIntRange($start, null) : Type::getInt(true), $result_type, ); } diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 094f1bbe09c..b1013c65435 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -955,6 +955,23 @@ function scope(array $a): int|float { '$b' => 'float|int', ], ], + 'incrementInLoop' => [ + 'code' => ' [ + '$i' => 'int<0, 10>', + '$j' => 'int<100, 110>', + ], + ], 'coalesceFilterOutNullEvenWithTernary' => [ 'code' => ' Date: Sun, 8 Oct 2023 16:56:03 +0100 Subject: [PATCH 2/4] Correct decrement min/max ranges --- .../BinaryOp/ArithmeticOpAnalyzer.php | 11 +++++++++-- tests/BinaryOperationTest.php | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 478c34c2896..4a14d1f25d9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -24,6 +24,8 @@ use Psalm\Issue\PossiblyNullOperand; use Psalm\Issue\StringIncrement; use Psalm\IssueBuffer; +use Psalm\Node\Expr\BinaryOp\VirtualMinus; +use Psalm\Node\Expr\BinaryOp\VirtualPlus; use Psalm\StatementsSource; use Psalm\Type; use Psalm\Type\Atomic; @@ -821,10 +823,15 @@ private static function analyzeOperands( $result_type = Type::getInt(); } } - } else { + } elseif ($parent instanceof VirtualPlus) { + $start = $left_type_part instanceof TLiteralInt ? $left_type_part->value : 1; + $result_type = Type::combineUnionTypes(Type::getIntRange($start, null), $result_type); + } elseif ($parent instanceof VirtualMinus) { $start = $left_type_part instanceof TLiteralInt ? $left_type_part->value : 1; + $result_type = Type::combineUnionTypes(Type::getIntRange(null, $start), $result_type); + } else { $result_type = Type::combineUnionTypes( - $always_positive ? Type::getIntRange($start, null) : Type::getInt(true), + $always_positive ? Type::getIntRange(1, null) : Type::getInt(true), $result_type, ); } diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index b1013c65435..c2463e43d83 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -972,6 +972,23 @@ function scope(array $a): int|float { '$j' => 'int<100, 110>', ], ], + 'decrementInLoop' => [ + 'code' => ' 0; $i--) { + if (rand(0,1)) { + break; + } + } + for ($j = 110; $j > 100; $j--) { + if (rand(0,1)) { + break; + } + }', + 'assertions' => [ + '$i' => 'int<0, 10>', + '$j' => 'int<100, 110>', + ], + ], 'coalesceFilterOutNullEvenWithTernary' => [ 'code' => ' Date: Sun, 8 Oct 2023 20:29:28 +0100 Subject: [PATCH 3/4] Better reconciling of ++/-- operators in ints --- .../BinaryOp/ArithmeticOpAnalyzer.php | 28 +++++++-- .../RedundantConditionTest.php | 58 +++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 4a14d1f25d9..5d4e3e290aa 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -823,12 +823,28 @@ private static function analyzeOperands( $result_type = Type::getInt(); } } - } elseif ($parent instanceof VirtualPlus) { - $start = $left_type_part instanceof TLiteralInt ? $left_type_part->value : 1; - $result_type = Type::combineUnionTypes(Type::getIntRange($start, null), $result_type); - } elseif ($parent instanceof VirtualMinus) { - $start = $left_type_part instanceof TLiteralInt ? $left_type_part->value : 1; - $result_type = Type::combineUnionTypes(Type::getIntRange(null, $start), $result_type); + } elseif ($parent instanceof VirtualPlus || $parent instanceof VirtualMinus) { + $sum = $parent instanceof VirtualPlus ? 1 : -1; + if ($context && $context->inside_loop && $left_type_part instanceof TLiteralInt) { + if ($parent instanceof VirtualPlus) { + $new_type = new TIntRange($left_type_part->value + $sum, null); + } else { + $new_type = new TIntRange(null, $left_type_part->value + $sum); + } + } elseif ($left_type_part instanceof TLiteralInt) { + $new_type = new TLiteralInt($left_type_part->value + $sum); + } elseif ($left_type_part instanceof TIntRange) { + $start = $left_type_part->min_bound === null ? null : $left_type_part->min_bound + $sum; + $end = $left_type_part->max_bound === null ? null : $left_type_part->max_bound + $sum; + $new_type = new TIntRange($start, $end); + } else { + $new_type = new TInt(); + } + + $result_type = Type::combineUnionTypes( + new Union([$new_type], ['from_calculation' => true]), + $result_type, + ); } else { $result_type = Type::combineUnionTypes( $always_positive ? Type::getIntRange(1, null) : Type::getInt(true), diff --git a/tests/TypeReconciliation/RedundantConditionTest.php b/tests/TypeReconciliation/RedundantConditionTest.php index b6e01460cc9..a7217834014 100644 --- a/tests/TypeReconciliation/RedundantConditionTest.php +++ b/tests/TypeReconciliation/RedundantConditionTest.php @@ -441,6 +441,64 @@ function foo(int $x) : void { } }', ], + 'allowIntValueCheckAfterComparisonDueToUnderflow' => [ + 'code' => ' [ + 'code' => ' [ + 'code' => ' [ 'code' => ' Date: Thu, 26 Oct 2023 11:25:12 +0100 Subject: [PATCH 4/4] Rework test as it was a false negative --- tests/Loop/ForTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Loop/ForTest.php b/tests/Loop/ForTest.php index f8fd5f2b22b..037893b1ef4 100644 --- a/tests/Loop/ForTest.php +++ b/tests/Loop/ForTest.php @@ -143,7 +143,7 @@ function test(Node $head) { * @param list $arr */ function cartesianProduct(array $arr) : void { - for ($i = 20; $arr[$i] === 5 && $i > 0; $i--) {} + for ($i = 20; $i > 0 && $arr[$i] === 5 ; $i--) {} }', ], 'noCrashOnLongThing' => [