From a6630c49a25f2ae24d2fbb61d30206dd1526fa52 Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 12 Sep 2021 20:53:11 +0200 Subject: [PATCH] fix mod calculation with ranges --- .../BinaryOp/ArithmeticOpAnalyzer.php | 63 +++++++++++++++++++ tests/IntRangeTest.php | 61 ++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 2bb261209..4985ca14b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -915,6 +915,69 @@ class ArithmeticOpAnalyzer return; } + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + //result of Mod is not directly dependant on the bounds of the range + if ($right_type_part->min_bound !== null && $right_type_part->min_bound === $right_type_part->max_bound) { + //if the second operand is a literal, we can be pretty detailed + if ($right_type_part->min_bound === 0) { + $new_result_type = Type::getEmpty(); + } else { + if ($left_type_part->isPositiveOrZero()) { + if ($right_type_part->isPositive()) { + $max = $right_type_part->min_bound - 1; + $new_result_type = new Type\Union([new TIntRange(0, $max)]); + } else { + $max = $right_type_part->min_bound + 1; + $new_result_type = new Type\Union([new TIntRange($max, 0)]); + } + } elseif ($left_type_part->isNegativeOrZero()) { + if ($right_type_part->isPositive()) { + $max = $right_type_part->min_bound - 1; + $new_result_type = new Type\Union([new TIntRange(-$max, 0)]); + } else { + $max = $right_type_part->min_bound + 1; + $new_result_type = new Type\Union([new TIntRange(-$max, 0)]); + } + } else { + if ($right_type_part->isPositive()) { + $max = $right_type_part->min_bound - 1; + } else { + $max = -$right_type_part->min_bound - 1; + } + $new_result_type = new Type\Union([new TIntRange(-$max, $max)]); + } + } + } elseif ($right_type_part->isPositive()) { + if ($left_type_part->isPositiveOrZero()) { + $new_result_type = new Type\Union([new TIntRange(0, null)]); + } elseif ($left_type_part->isNegativeOrZero()) { + $new_result_type = new Type\Union([new TIntRange(null, 0)]); + } else { + $new_result_type = Type::getInt(true); + } + } elseif ($right_type_part->isNegative()) { + if ($left_type_part->isPositiveOrZero()) { + $new_result_type = new Type\Union([new TIntRange(null, 0)]); + } elseif ($left_type_part->isNegativeOrZero()) { + $new_result_type = new Type\Union([new TIntRange(null, 0)]); + } else { + $new_result_type = Type::getInt(true); + } + } else { + $new_result_type = Type::getInt(true); + } + + if (!$result_type) { + $result_type = new Type\Union([$new_result_type]); + } else { + $result_type = Type::combineUnionTypes( + new Type\Union([$new_result_type]), + $result_type + ); + } + return; + } + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd || $parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr || $parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index 0ef3551ac..4d2829b5f 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -183,6 +183,67 @@ class IntRangeTest extends TestCase '$e===' => 'int<500, 4999>' ] ], + 'mod' => [ + '= 20);//positive range + assert($b <= -20);//negative range + /** @var int<0, 0> $c */; // 0 range + assert($d >= -100);// mixed range + assert($d <= 100);// mixed range + /** @var int<5, 5> $e */; // 5 range + + $f = $a % $e; + $g = $b % $e; + $h = $d % $e; + $i = -3 % $a; + $j = -3 % $b; + $k = -3 % $c; + $l = -3 % $d; + $m = 3 % $a; + $n = 3 % $b; + $o = 3 % $c; + $p = 3 % $d; + $q = $a % 0; + $r = $a % 3; + $s = $a % -3; + $t = $b % 0; + $u = $b % 3; + $v = $b % -3; + $w = $c % 0; + $x = $c % 3; + $y = $c % -3; + $z = $d % 0; + $aa = $d % 3; + $ab = $d % -3; + ', + 'assertions' => [ + '$f===' => 'int<0, 4>', + '$g===' => 'int<-4, 0>', + '$h===' => 'int<-4, 4>', + '$i===' => 'int', + '$j===' => 'int', + '$k===' => 'empty', + '$l===' => 'int', + '$m===' => 'int<0, max>', + '$n===' => 'int', + '$o===' => 'empty', + '$p===' => 'int', + '$q===' => 'empty', + '$r===' => 'int<0, 2>', + '$s===' => 'int<-2, 0>', + '$t===' => 'empty', + '$u===' => 'int<-2, 0>', + '$v===' => 'int<2, 0>', + '$w===' => 'empty', + '$x===' => 'int<0, 2>', + '$y===' => 'int<-2, 0>', + '$z===' => 'empty', + '$aa===' => 'int<-2, 2>', + '$ab===' => 'int<-2, 2>', + ] + ], 'pow' => [ '