1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

fix mod calculation with ranges

This commit is contained in:
orklah 2021-09-12 20:53:11 +02:00
parent dd9edb7afc
commit a6630c49a2
2 changed files with 124 additions and 0 deletions

View File

@ -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

View File

@ -183,6 +183,67 @@ class IntRangeTest extends TestCase
'$e===' => 'int<500, 4999>'
]
],
'mod' => [
'<?php
function getInt(): int{return 0;}
$a = $b = $c = $d = getInt();
assert($a >= 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<min, 0>',
'$j===' => 'int<min, 0>',
'$k===' => 'empty',
'$l===' => 'int',
'$m===' => 'int<0, max>',
'$n===' => 'int<min, 0>',
'$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' => [
'<?php
function getInt(): int{return 0;}