New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve range multiplication and division #1961
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,14 +65,19 @@ | |
use PHPStan\Type\UnionType; | ||
use function array_keys; | ||
use function array_merge; | ||
use function assert; | ||
use function ceil; | ||
use function count; | ||
use function dirname; | ||
use function floor; | ||
use function in_array; | ||
use function is_float; | ||
use function is_int; | ||
use function max; | ||
use function min; | ||
use function sprintf; | ||
use function strtolower; | ||
use const INF; | ||
|
||
class InitializerExprTypeResolver | ||
{ | ||
|
@@ -1580,6 +1585,14 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T | |
return $union->toNumber(); | ||
} | ||
|
||
if ($operand instanceof IntegerRangeType) { | ||
$operandMin = $operand->getMin(); | ||
$operandMax = $operand->getMax(); | ||
} else { | ||
$operandMin = $operand->getValue(); | ||
$operandMax = $operand->getValue(); | ||
} | ||
|
||
if ($node instanceof BinaryOp\Plus) { | ||
if ($operand instanceof ConstantIntegerType) { | ||
/** @var int|float|null $min */ | ||
|
@@ -1645,63 +1658,103 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T | |
} | ||
} | ||
} elseif ($node instanceof Expr\BinaryOp\Mul) { | ||
if ($operand instanceof ConstantIntegerType) { | ||
/** @var int|float|null $min */ | ||
$min = $rangeMin !== null ? $rangeMin * $operand->getValue() : null; | ||
$min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF); | ||
$min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF); | ||
$max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF); | ||
$max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF); | ||
|
||
/** @var int|float|null $max */ | ||
$max = $rangeMax !== null ? $rangeMax * $operand->getValue() : null; | ||
} else { | ||
/** @var int|float|null $min */ | ||
$min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin * $operand->getMin() : null; | ||
$min = min($min1, $min2, $max1, $max2); | ||
$max = max($min1, $min2, $max1, $max2); | ||
|
||
/** @var int|float|null $max */ | ||
$max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax * $operand->getMax() : null; | ||
if ($min === -INF) { | ||
$min = null; | ||
} | ||
|
||
if ($min !== null && $max !== null && $min > $max) { | ||
[$min, $max] = [$max, $min]; | ||
} | ||
|
||
// invert maximas on multiplication with negative constants | ||
if ((($range instanceof ConstantIntegerType && $range->getValue() < 0) | ||
|| ($operand instanceof ConstantIntegerType && $operand->getValue() < 0)) | ||
&& ($min === null || $max === null)) { | ||
[$min, $max] = [$max, $min]; | ||
if ($max === INF) { | ||
$max = null; | ||
} | ||
|
||
} else { | ||
if ($operand instanceof ConstantIntegerType) { | ||
$min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null; | ||
$max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null; | ||
} else { | ||
$min = $rangeMin !== null && $operand->getMin() !== null && $operand->getMin() !== 0 ? $rangeMin / $operand->getMin() : null; | ||
$max = $rangeMax !== null && $operand->getMax() !== null && $operand->getMax() !== 0 ? $rangeMax / $operand->getMax() : null; | ||
} | ||
// Avoid division by zero when looking for the min and the max by using the closest int | ||
$operandMin = $operandMin !== 0 ? $operandMin : 1; | ||
$operandMax = $operandMax !== 0 ? $operandMax : -1; | ||
|
||
if ( | ||
($operandMin < 0 || $operandMin === null) | ||
&& ($operandMax > 0 || $operandMax === null) | ||
) { | ||
$negativeOperand = IntegerRangeType::fromInterval($operandMin, 0); | ||
assert($negativeOperand instanceof IntegerRangeType); | ||
$positiveOperand = IntegerRangeType::fromInterval(0, $operandMax); | ||
assert($positiveOperand instanceof IntegerRangeType); | ||
|
||
$result = TypeCombinator::union( | ||
$this->integerRangeMath($range, $node, $negativeOperand), | ||
$this->integerRangeMath($range, $node, $positiveOperand), | ||
)->toNumber(); | ||
|
||
if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) { | ||
return new BenevolentUnionType([new IntegerType(), new FloatType()]); | ||
} | ||
|
||
return $result; | ||
} | ||
if ( | ||
($rangeMin < 0 || $rangeMin === null) | ||
&& ($rangeMax > 0 || $rangeMax === null) | ||
) { | ||
$negativeRange = IntegerRangeType::fromInterval($rangeMin, 0); | ||
assert($negativeRange instanceof IntegerRangeType); | ||
$positiveRange = IntegerRangeType::fromInterval(0, $rangeMax); | ||
assert($positiveRange instanceof IntegerRangeType); | ||
|
||
$result = TypeCombinator::union( | ||
$this->integerRangeMath($negativeRange, $node, $operand), | ||
$this->integerRangeMath($positiveRange, $node, $operand), | ||
)->toNumber(); | ||
|
||
if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) { | ||
return new BenevolentUnionType([new IntegerType(), new FloatType()]); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
$rangeMinSign = ($rangeMin ?? -INF) <=> 0; | ||
$rangeMaxSign = ($rangeMax ?? INF) <=> 0; | ||
|
||
$min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1; | ||
$min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1; | ||
$max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1; | ||
$max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1; | ||
Comment on lines
+1728
to
+1731
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I basically use the same tips than for multiplication, replacing I can still simulate this division by saying the min/max is really near from 0 with the correct sign. |
||
|
||
$min = min($min1, $min2, $max1, $max2); | ||
$max = max($min1, $min2, $max1, $max2); | ||
|
||
if ($range instanceof IntegerRangeType && $operand instanceof IntegerRangeType) { | ||
if ($rangeMax === null && $operand->getMax() === null) { | ||
$min = 0; | ||
} elseif ($rangeMin === null && $operand->getMin() === null) { | ||
if ($min === -INF) { | ||
$min = null; | ||
} | ||
if ($max === INF) { | ||
$max = null; | ||
} | ||
} | ||
|
||
if ($min !== null && $max !== null && $min > $max) { | ||
[$min, $max] = [$max, $min]; | ||
} | ||
|
||
if ($operand instanceof IntegerRangeType | ||
&& ($operand->getMin() === null || $operand->getMax() === null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
There is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking, this is not always true.
but I think it would get too complicated adding this logic in this PR, so no need to fix it now. It's not working correctly now anyway:) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree. I didn't find a better solution. Moreover 6 / int<1, 6> is possibly a float. In the same way 6 / int<1,3> is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, your solution is smart and the results are much better than before! |
||
|| ($rangeMin === null || $rangeMax === null) | ||
|| is_float($min) || is_float($max) | ||
|| is_float($min) | ||
|| is_float($max) | ||
) { | ||
if (is_float($min)) { | ||
$min = (int) $min; | ||
$min = (int) ceil($min); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So,
|
||
} | ||
if (is_float($max)) { | ||
$max = (int) $max; | ||
} | ||
|
||
if ($min !== null && $max !== null && $min > $max) { | ||
[$min, $max] = [$max, $min]; | ||
$max = (int) floor($max); | ||
} | ||
|
||
// invert maximas on division with negative constants | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,70 +218,70 @@ public function math($i, $j, $z, $pi, $r1, $r2, $r3, $rMin, $rMax, $x, $y) { | |
assertType('int<2, 13>', $r1 + $j); | ||
assertType('int<-2, 9>', $r1 - $j); | ||
assertType('int<1, 30>', $r1 * $j); | ||
assertType('float|int<0, 10>', $r1 / $j); | ||
assertType('float|int<1, 10>', $r1 / $j); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please verify each expectation change on wolfram alpha like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @staabm I checked every expectation, and I'm good with my results. See https://www.wolframalpha.com/input?i=%28-1%2Finterval%5B-9999%2C+5%5D+%29 This is especially why in the code I split the |
||
assertType('int<min, 15>', $rMin * $j); | ||
assertType('int<5, max>', $rMax * $j); | ||
|
||
assertType('int<2, 13>', $j + $r1); | ||
assertType('int<-9, 2>', $j - $r1); | ||
assertType('int<1, 30>', $j * $r1); | ||
assertType('float|int<0, 3>', $j / $r1); | ||
assertType('float|int<1, 3>', $j / $r1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assertType('int<min, 15>', $j * $rMin); | ||
assertType('int<5, max>', $j * $rMax); | ||
|
||
assertType('int<-19, -10>|int<2, 13>', $r1 + $z); | ||
assertType('int<-2, 9>|int<21, 30>', $r1 - $z); | ||
assertType('int<-200, -20>|int<1, 30>', $r1 * $z); | ||
assertType('float|int<0, 10>', $r1 / $z); | ||
assertType('float|int<1, 10>', $r1 / $z); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible |
||
assertType('int', $rMin * $z); | ||
assertType('int<min, -100>|int<5, max>', $rMax * $z); | ||
|
||
assertType('int<2, max>', $pi + 1); | ||
assertType('int<-1, max>', $pi - 2); | ||
assertType('int<2, max>', $pi * 2); | ||
assertType('float|int<0, max>', $pi / 2); | ||
assertType('float|int<1, max>', $pi / 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assertType('int<2, max>', 1 + $pi); | ||
assertType('int<min, 2>', 2 - $pi); | ||
assertType('int<2, max>', 2 * $pi); | ||
assertType('float|int<2, max>', 2 / $pi); | ||
assertType('float|int<1, 2>', 2 / $pi); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. $pi is a positive int so the result cannot be an int bigger than 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
assertType('int<5, 14>', $r1 + 4); | ||
assertType('int<-3, 6>', $r1 - 4); | ||
assertType('int<4, 40>', $r1 * 4); | ||
assertType('float|int<0, 2>', $r1 / 4); | ||
assertType('float|int<1, 2>', $r1 / 4); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assertType('int<9, max>', $rMax + 4); | ||
assertType('int<1, max>', $rMax - 4); | ||
assertType('int<20, max>', $rMax * 4); | ||
assertType('float|int<1, max>', $rMax / 4); | ||
assertType('float|int<2, max>', $rMax / 4); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1 was never possible since $rMax >= 5 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
assertType('int<6, 20>', $r1 + $r2); | ||
assertType('int<-9, 5>', $r1 - $r2); | ||
assertType('int<5, 100>', $r1 * $r2); | ||
assertType('float|int<0, 1>', $r1 / $r2); | ||
assertType('float|int<1, 2>', $r1 / $r2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int<1, 10> / int<5, 10> 0 was never possible and 10 / 5 is 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
assertType('int<-99, 19>', $r1 - $r3); | ||
|
||
assertType('int<min, 15>', $r1 + $rMin); | ||
assertType('int<-4, max>', $r1 - $rMin); | ||
assertType('int<min, 50>', $r1 * $rMin); | ||
assertType('float|int<min, 2>', $r1 / $rMin); | ||
assertType('float|int<-10, -1>|int<1, 10>', $r1 / $rMin); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int<1, 10> / int<min, 5> 10 / -1 = -10 and 10 / -10 = -1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woflramalpha is not doing great on this https://www.wolframalpha.com/input?i=%28interval%5B1%2C10%5D+%2F+interval%5B-9999%2C5%5D%29 |
||
assertType('int<min, 15>', $rMin + $r1); | ||
assertType('int<min, 4>', $rMin - $r1); | ||
assertType('int<min, 50>', $rMin * $r1); | ||
assertType('float|int<min, 0>', $rMin / $r1); | ||
assertType('float|int<min, 5>', $rMin / $r1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int<min, 5> / int<1, 10> can reach 5 since $r1 can be 1 |
||
|
||
assertType('int<6, max>', $r1 + $rMax); | ||
assertType('int', $r1 - $rMax); | ||
assertType('int<5, max>', $r1 * $rMax); | ||
assertType('float|int<0, max>', $r1 / $rMax); | ||
assertType('float|int<1, 2>', $r1 / $rMax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int<1, 10> / int<5, max> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assertType('int<6, max>', $rMax + $r1); | ||
assertType('int<-5, max>', $rMax - $r1); | ||
assertType('int<5, max>', $rMax * $r1); | ||
assertType('float|int<5, max>', $rMax / $r1); | ||
assertType('float|int<1, max>', $rMax / $r1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int<5, max> / int<1, 10> When you do 5 / 5 you get 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
assertType('5|10|15|20|30', $x / $y); | ||
|
||
assertType('float|int<0, max>', $rMax / $rMax); | ||
assertType('float|int<1, max>', $rMax / $rMax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0 was never possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assertType('(float|int)', $rMin / $rMin); | ||
} | ||
|
||
|
@@ -307,8 +307,8 @@ public function maximaInversion($rMin, $rMax) { | |
assertType('int<-5, max>', $rMin * -1); | ||
assertType('int<min, -10>', $rMax * -2); | ||
|
||
assertType('float|int<0, max>', -1 / $rMin); | ||
assertType('float|int<min, 0>', -2 / $rMax); | ||
assertType('-1|1|float', -1 / $rMin); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -1 / $rMin with $rMin an int will never be something else There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wolframalpha is not doing great on this https://www.wolframalpha.com/input?i=%28-1%2Finterval%5B-9999%2C+5%5D+%29 |
||
assertType('float', -2 / $rMax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. $rMax is int<5, max> so it's not possible to have an int There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
assertType('float|int<-5, max>', $rMin / -1); | ||
assertType('float|int<min, -2>', $rMax / -2); | ||
|
@@ -330,4 +330,29 @@ public function unaryMinus($r1, $r2, $rMin, $rMax, $rZero) { | |
assertType('int<-50, 0>', -$rZero); | ||
} | ||
|
||
/** | ||
* @param int<-1, 2> $p | ||
* @param int<-1, 2> $u | ||
*/ | ||
public function sayHello($p, $u): void | ||
{ | ||
assertType('int<-2, 4>', $p + $u); | ||
assertType('int<-3, 3>', $p - $u); | ||
assertType('int<-2, 4>', $p * $u); | ||
assertType('float|int<-2, 2>', $p / $u); | ||
} | ||
|
||
/** | ||
* @param int<0, max> $positive | ||
* @param int<min, 0> $negative | ||
*/ | ||
public function zeroIssues($positive, $negative) | ||
{ | ||
assertType('0', 0 * $positive); | ||
assertType('int<0, max>', $positive * $positive); | ||
assertType('0', 0 * $negative); | ||
assertType('int<0, max>', $negative * $negative); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. another assert for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, added |
||
assertType('int<min, 0>', $negative * $positive); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found really complicated to compute
It seemed easier to split negative and positive integers.
So
And then I merge all these types.