Skip to content

Commit

Permalink
implemented math on IntegerRangeType and ConstantIntegerType
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Aug 27, 2021
1 parent cee5bbe commit 65b91aa
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 7 deletions.
96 changes: 96 additions & 0 deletions src/Analyser/MutatingScope.php
Expand Up @@ -1255,6 +1255,102 @@ private function resolveType(Expr $node): Type
$leftType = $this->getType($left);
$rightType = $this->getType($right);

if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType) &&
!($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) {

if ($leftType instanceof ConstantIntegerType) {
$leftMin = $leftType->getValue();
$leftMax = $leftType->getValue();
} elseif ($leftType instanceof UnionType) {
$leftMin = null;
$leftMax = null;

foreach ($leftType->getTypes() as $type) {
if ($type instanceof IntegerRangeType) {
$leftMin = $leftMin !== null ? min($leftMin, $type->getMin()) : $type->getMin();
$leftMax = max($leftMax, $type->getMax());
} elseif ($type instanceof ConstantIntegerType) {
if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus ||
$node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
$leftMin = max($leftMin, $type->getValue());
$leftMax = $leftMax !== null ? min($leftMax, $type->getValue()) : $type->getValue();
} else {
$leftMin = $leftMin !== null ? min($leftMin, $type->getValue()) : $type->getValue();
$leftMax = max($leftMax, $type->getValue());
}
}
}
} else {
$leftMin = $leftType->getMin();
$leftMax = $leftType->getMax();
}

if ($rightType instanceof ConstantIntegerType) {
$rightMin = $rightType->getValue();
$rightMax = $rightType->getValue();
} elseif ($rightType instanceof UnionType) {
$rightMin = null;
$rightMax = null;

foreach ($rightType->getTypes() as $type) {
if ($type instanceof IntegerRangeType) {
$rightMin = $rightMin !== null ? min($rightMin, $type->getMin()) : $type->getMin();
$rightMax = max($rightMax, $type->getMax());
} elseif ($type instanceof ConstantIntegerType) {
if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus ||
$node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
$rightMin = max($rightMin, $type->getValue());
$rightMax = $rightMax !== null ? min($rightMax, $type->getValue()) : $type->getValue();
} else {
$rightMin = $rightMin !== null ? min($rightMin, $type->getValue()) : $type->getValue();
$rightMax = max($rightMax, $type->getValue());
}
}
}
} else {
$rightMin = $rightType->getMin();
$rightMax = $rightType->getMax();
}

if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) {
$min = $leftMin !== null && $rightMin !== null ? $leftMin + $rightMin : null;
$max = $leftMax !== null && $rightMax !== null ? $leftMax + $rightMax : null;
} elseif ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) {
$min = $leftMin !== null && $rightMin !== null ? $leftMin - $rightMin : null;
$max = $leftMax !== null && $rightMax !== null ? $leftMax - $rightMax : null;

if ($min !== null && $max !== null && $min > $max) {
[$min, $max] = [$max, $min];
}
} elseif ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) {
$min = $leftMin !== null && $rightMin !== null ? $leftMin * $rightMin : null;
$max = $leftMax !== null && $rightMax !== null ? $leftMax * $rightMax : null;
} else {
$min = $leftMin !== null && $rightMin !== null ? (int) ($leftMin / $rightMin) : null;
$max = $leftMax !== null && $rightMax !== null ? (int) ($leftMax / $rightMax) : null;

if ($min !== null && $max !== null && $min > $max) {
[$min, $max] = [$max, $min];
}
}

if ($min !== null || $max !== null) {
$integerRange = IntegerRangeType::fromInterval($min, $max);

if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
if ($min === $max && $min === 0) {
// division of upper and lower bound turns into a tiny 0.x fraction, which casted to int turns into 0.
// this leads to a useless 0|float type; we return only float instead.
return new FloatType();
}
return TypeCombinator::union($integerRange, new FloatType());
}

return $integerRange;
}
}

$operatorSigil = null;

if ($node instanceof BinaryOp) {
Expand Down
4 changes: 0 additions & 4 deletions src/File/ParentDirectoryRelativePathHelper.php
Expand Up @@ -54,10 +54,6 @@ public function getFilenameParts(string $filename): array
}

$dotsCount = $parentPartsCount - $i;
if ($dotsCount < 0) {
throw new \PHPStan\ShouldNotHappenException();
}

return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i));
}

Expand Down
90 changes: 87 additions & 3 deletions tests/PHPStan/Analyser/data/integer-range-types.php
Expand Up @@ -152,9 +152,9 @@ function (int $a, int $b, int $c): void {

assertType('int<14, max>', $c);

assertType('int', $a * $b);
assertType('int', $b * $c);
assertType('int', $a * $b * $c);
assertType('int<156, max>', $a * $b);
assertType('int<182, max>', $b * $c);
assertType('int<2184, max>', $a * $b * $c);
};

class X {
Expand Down Expand Up @@ -194,4 +194,88 @@ public function supportsPhpdocIntegerRange() {
assertType('*ERROR*', $this->error2);
assertType('int', $this->int);
}

/**
* @param int $i
* @param 1|2|3 $j
* @param 1|-20|3 $z
* @param positive-int $pi
* @param int<1, 10> $r1
* @param int<5, 10> $r2
* @param int<min, 5> $rMin
* @param int<5, max> $rMax
*
* @param 20|40|60 $x
* @param 2|4 $y
*/
public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) {
assertType('int', $r1 + $i);
assertType('int', $r1 - $i);
assertType('int', $r1 * $i);
assertType('(float|int)', $r1 / $i);

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('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('int<min, 15>', $j * $rMin);
assertType('int<5, max>', $j * $rMax);

assertType('int<-19, 13>', $r1 + $z);
assertType('int<-2, 30>', $r1 - $z);
assertType('int<-20, 30>', $r1 * $z);
assertType('float', $r1 / $z);
assertType('int<min, 15>', $rMin * $z);
assertType('int<-100, 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('int<2, max>', 1 + $pi);
assertType('int<1, max>', 2 - $pi);
assertType('int<2, max>', 2 * $pi);
assertType('float|int<2, max>', 2 / $pi);

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('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('int<6, 20>', $r1 + $r2);
assertType('int<-4, 0>', $r1 - $r2);
assertType('int<5, 100>', $r1 * $r2);
assertType('float|int<0, 1>', $r1 / $r2);

assertType('int<min, 15>', $r1 + $rMin);
assertType('int<min, 5>', $r1 - $rMin);
assertType('int<min, 50>', $r1 * $rMin);
assertType('float|int<min, 2>', $r1 / $rMin);
assertType('int<min, 15>', $rMin + $r1);
assertType('int<min, -5>', $rMin - $r1);
assertType('int<min, 50>', $rMin * $r1);
assertType('float|int<min, 0>', $rMin / $r1);

assertType('int<6, max>', $r1 + $rMax);
assertType('int<-4, max>', $r1 - $rMax);
assertType('int<5, max>', $r1 * $rMax);
assertType('float|int<0, max>', $r1 / $rMax);
assertType('int<6, max>', $rMax + $r1);
assertType('int<4, max>', $rMax - $r1);
assertType('int<5, max>', $rMax * $r1);
assertType('float|int<5, max>', $rMax / $r1);

assertType('5|10|15|20|30', $x / $y);
}
}

0 comments on commit 65b91aa

Please sign in to comment.