Skip to content

Commit 5be8e54

Browse files
authored
Use IntegerRangeType in modulo-operator
1 parent 96b10ea commit 5be8e54

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/Analyser/MutatingScope.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,48 @@ private function resolveType(Expr $node): Type
11111111
}
11121112

11131113
if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Expr\AssignOp\Mod) {
1114+
if ($node instanceof Node\Expr\AssignOp) {
1115+
$left = $node->var;
1116+
$right = $node->expr;
1117+
} else {
1118+
$left = $node->left;
1119+
$right = $node->right;
1120+
}
1121+
1122+
$leftType = $this->getType($left);
1123+
$rightType = $this->getType($right);
1124+
1125+
$integer = new IntegerType();
1126+
$positiveInt = IntegerRangeType::fromInterval(0, null);
1127+
if ($integer->isSuperTypeOf($rightType)->yes()) {
1128+
$rangeMin = null;
1129+
$rangeMax = null;
1130+
1131+
if ($rightType instanceof IntegerRangeType) {
1132+
$rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null;
1133+
} elseif ($rightType instanceof ConstantIntegerType) {
1134+
$rangeMax = $rightType->getValue() - 1;
1135+
} elseif ($rightType instanceof UnionType) {
1136+
foreach ($rightType->getTypes() as $type) {
1137+
if ($type instanceof IntegerRangeType) {
1138+
$rangeMax = max($rangeMax, $type->getMax() !== null ? $type->getMax() - 1 : null);
1139+
} elseif ($type instanceof ConstantIntegerType) {
1140+
$rangeMax = max($rangeMax, $type->getValue() - 1);
1141+
}
1142+
}
1143+
}
1144+
1145+
if ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1146+
$rangeMin = 0;
1147+
} elseif ($rangeMax !== null) {
1148+
$rangeMin = $rangeMax * -1;
1149+
}
1150+
1151+
return IntegerRangeType::fromInterval($rangeMin, $rangeMax);
1152+
} elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1153+
return IntegerRangeType::fromInterval(0, null);
1154+
}
1155+
11141156
return new IntegerType();
11151157
}
11161158

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ public function dataFileAsserts(): iterable
463463
}
464464

465465
yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-types.php');
466+
yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php');
466467
}
467468

468469
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace ModuloOperator;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
/**
10+
* @param positive-int $p
11+
* @param int<5, 10> $range
12+
* @param int<0, max> $zeroOrMore
13+
* @param 1|2|3 $intConst
14+
* @param int<min, 3>|int<4, max> $unionRange
15+
* @param int<min, 3>|7 $hybridUnionRange
16+
*/
17+
function doBar(int $i, $p, $range, $zeroOrMore, $intConst, $unionRange, $hybridUnionRange, $mixed)
18+
{
19+
assertType('int<-1, 1>', $i % 2);
20+
assertType('int<0, 1>', $p % 2);
21+
22+
assertType('int<-2, 2>', $i % 3);
23+
assertType('int<0, 2>', $p % 3);
24+
25+
assertType('0|1|2', $intConst % 3);
26+
assertType('int<-2, 2>', $i % $intConst);
27+
assertType('int<0, 2>', $p % $intConst);
28+
29+
assertType('int<0, 2>', $range % 3);
30+
31+
assertType('int<-9, 9>', $i % $range);
32+
assertType('int<0, 9>', $p % $range);
33+
34+
assertType('int', $i % $unionRange);
35+
assertType('int<0, max>', $p % $unionRange);
36+
37+
assertType('int<-6, 6>', $i % $hybridUnionRange);
38+
assertType('int<0, 6>', $p % $hybridUnionRange);
39+
40+
assertType('int<0, max>', $zeroOrMore % $mixed);
41+
}
42+
}

0 commit comments

Comments
 (0)