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:
parent
dd9edb7afc
commit
a6630c49a2
@ -915,6 +915,69 @@ class ArithmeticOpAnalyzer
|
|||||||
return;
|
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 ||
|
if ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd ||
|
||||||
$parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr ||
|
$parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr ||
|
||||||
$parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor
|
$parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor
|
||||||
|
@ -183,6 +183,67 @@ class IntRangeTest extends TestCase
|
|||||||
'$e===' => 'int<500, 4999>'
|
'$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' => [
|
'pow' => [
|
||||||
'<?php
|
'<?php
|
||||||
function getInt(): int{return 0;}
|
function getInt(): int{return 0;}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user