Skip to content

Commit 65b91aa

Browse files
authored
implemented math on IntegerRangeType and ConstantIntegerType
1 parent cee5bbe commit 65b91aa

File tree

3 files changed

+183
-7
lines changed

3 files changed

+183
-7
lines changed

src/Analyser/MutatingScope.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,102 @@ private function resolveType(Expr $node): Type
12551255
$leftType = $this->getType($left);
12561256
$rightType = $this->getType($right);
12571257

1258+
if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
1259+
($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType) &&
1260+
!($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) {
1261+
1262+
if ($leftType instanceof ConstantIntegerType) {
1263+
$leftMin = $leftType->getValue();
1264+
$leftMax = $leftType->getValue();
1265+
} elseif ($leftType instanceof UnionType) {
1266+
$leftMin = null;
1267+
$leftMax = null;
1268+
1269+
foreach ($leftType->getTypes() as $type) {
1270+
if ($type instanceof IntegerRangeType) {
1271+
$leftMin = $leftMin !== null ? min($leftMin, $type->getMin()) : $type->getMin();
1272+
$leftMax = max($leftMax, $type->getMax());
1273+
} elseif ($type instanceof ConstantIntegerType) {
1274+
if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus ||
1275+
$node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
1276+
$leftMin = max($leftMin, $type->getValue());
1277+
$leftMax = $leftMax !== null ? min($leftMax, $type->getValue()) : $type->getValue();
1278+
} else {
1279+
$leftMin = $leftMin !== null ? min($leftMin, $type->getValue()) : $type->getValue();
1280+
$leftMax = max($leftMax, $type->getValue());
1281+
}
1282+
}
1283+
}
1284+
} else {
1285+
$leftMin = $leftType->getMin();
1286+
$leftMax = $leftType->getMax();
1287+
}
1288+
1289+
if ($rightType instanceof ConstantIntegerType) {
1290+
$rightMin = $rightType->getValue();
1291+
$rightMax = $rightType->getValue();
1292+
} elseif ($rightType instanceof UnionType) {
1293+
$rightMin = null;
1294+
$rightMax = null;
1295+
1296+
foreach ($rightType->getTypes() as $type) {
1297+
if ($type instanceof IntegerRangeType) {
1298+
$rightMin = $rightMin !== null ? min($rightMin, $type->getMin()) : $type->getMin();
1299+
$rightMax = max($rightMax, $type->getMax());
1300+
} elseif ($type instanceof ConstantIntegerType) {
1301+
if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus ||
1302+
$node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
1303+
$rightMin = max($rightMin, $type->getValue());
1304+
$rightMax = $rightMax !== null ? min($rightMax, $type->getValue()) : $type->getValue();
1305+
} else {
1306+
$rightMin = $rightMin !== null ? min($rightMin, $type->getValue()) : $type->getValue();
1307+
$rightMax = max($rightMax, $type->getValue());
1308+
}
1309+
}
1310+
}
1311+
} else {
1312+
$rightMin = $rightType->getMin();
1313+
$rightMax = $rightType->getMax();
1314+
}
1315+
1316+
if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) {
1317+
$min = $leftMin !== null && $rightMin !== null ? $leftMin + $rightMin : null;
1318+
$max = $leftMax !== null && $rightMax !== null ? $leftMax + $rightMax : null;
1319+
} elseif ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) {
1320+
$min = $leftMin !== null && $rightMin !== null ? $leftMin - $rightMin : null;
1321+
$max = $leftMax !== null && $rightMax !== null ? $leftMax - $rightMax : null;
1322+
1323+
if ($min !== null && $max !== null && $min > $max) {
1324+
[$min, $max] = [$max, $min];
1325+
}
1326+
} elseif ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) {
1327+
$min = $leftMin !== null && $rightMin !== null ? $leftMin * $rightMin : null;
1328+
$max = $leftMax !== null && $rightMax !== null ? $leftMax * $rightMax : null;
1329+
} else {
1330+
$min = $leftMin !== null && $rightMin !== null ? (int) ($leftMin / $rightMin) : null;
1331+
$max = $leftMax !== null && $rightMax !== null ? (int) ($leftMax / $rightMax) : null;
1332+
1333+
if ($min !== null && $max !== null && $min > $max) {
1334+
[$min, $max] = [$max, $min];
1335+
}
1336+
}
1337+
1338+
if ($min !== null || $max !== null) {
1339+
$integerRange = IntegerRangeType::fromInterval($min, $max);
1340+
1341+
if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) {
1342+
if ($min === $max && $min === 0) {
1343+
// division of upper and lower bound turns into a tiny 0.x fraction, which casted to int turns into 0.
1344+
// this leads to a useless 0|float type; we return only float instead.
1345+
return new FloatType();
1346+
}
1347+
return TypeCombinator::union($integerRange, new FloatType());
1348+
}
1349+
1350+
return $integerRange;
1351+
}
1352+
}
1353+
12581354
$operatorSigil = null;
12591355

12601356
if ($node instanceof BinaryOp) {

src/File/ParentDirectoryRelativePathHelper.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ public function getFilenameParts(string $filename): array
5454
}
5555

5656
$dotsCount = $parentPartsCount - $i;
57-
if ($dotsCount < 0) {
58-
throw new \PHPStan\ShouldNotHappenException();
59-
}
60-
6157
return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i));
6258
}
6359

tests/PHPStan/Analyser/data/integer-range-types.php

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ function (int $a, int $b, int $c): void {
152152

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

155-
assertType('int', $a * $b);
156-
assertType('int', $b * $c);
157-
assertType('int', $a * $b * $c);
155+
assertType('int<156, max>', $a * $b);
156+
assertType('int<182, max>', $b * $c);
157+
assertType('int<2184, max>', $a * $b * $c);
158158
};
159159

160160
class X {
@@ -194,4 +194,88 @@ public function supportsPhpdocIntegerRange() {
194194
assertType('*ERROR*', $this->error2);
195195
assertType('int', $this->int);
196196
}
197+
198+
/**
199+
* @param int $i
200+
* @param 1|2|3 $j
201+
* @param 1|-20|3 $z
202+
* @param positive-int $pi
203+
* @param int<1, 10> $r1
204+
* @param int<5, 10> $r2
205+
* @param int<min, 5> $rMin
206+
* @param int<5, max> $rMax
207+
*
208+
* @param 20|40|60 $x
209+
* @param 2|4 $y
210+
*/
211+
public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) {
212+
assertType('int', $r1 + $i);
213+
assertType('int', $r1 - $i);
214+
assertType('int', $r1 * $i);
215+
assertType('(float|int)', $r1 / $i);
216+
217+
assertType('int<2, 13>', $r1 + $j);
218+
assertType('int<-2, 9>', $r1 - $j);
219+
assertType('int<1, 30>', $r1 * $j);
220+
assertType('float|int<0, 10>', $r1 / $j);
221+
assertType('int<min, 15>', $rMin * $j);
222+
assertType('int<5, max>', $rMax * $j);
223+
224+
assertType('int<2, 13>', $j + $r1);
225+
assertType('int<-9, 2>', $j - $r1);
226+
assertType('int<1, 30>', $j * $r1);
227+
assertType('float|int<0, 3>', $j / $r1);
228+
assertType('int<min, 15>', $j * $rMin);
229+
assertType('int<5, max>', $j * $rMax);
230+
231+
assertType('int<-19, 13>', $r1 + $z);
232+
assertType('int<-2, 30>', $r1 - $z);
233+
assertType('int<-20, 30>', $r1 * $z);
234+
assertType('float', $r1 / $z);
235+
assertType('int<min, 15>', $rMin * $z);
236+
assertType('int<-100, max>', $rMax * $z);
237+
238+
assertType('int<2, max>', $pi + 1);
239+
assertType('int<-1, max>', $pi - 2);
240+
assertType('int<2, max>', $pi * 2);
241+
assertType('float|int<0, max>', $pi / 2);
242+
assertType('int<2, max>', 1 + $pi);
243+
assertType('int<1, max>', 2 - $pi);
244+
assertType('int<2, max>', 2 * $pi);
245+
assertType('float|int<2, max>', 2 / $pi);
246+
247+
assertType('int<5, 14>', $r1 + 4);
248+
assertType('int<-3, 6>', $r1 - 4);
249+
assertType('int<4, 40>', $r1 * 4);
250+
assertType('float|int<0, 2>', $r1 / 4);
251+
assertType('int<9, max>', $rMax + 4);
252+
assertType('int<1, max>', $rMax - 4);
253+
assertType('int<20, max>', $rMax * 4);
254+
assertType('float|int<1, max>', $rMax / 4);
255+
256+
assertType('int<6, 20>', $r1 + $r2);
257+
assertType('int<-4, 0>', $r1 - $r2);
258+
assertType('int<5, 100>', $r1 * $r2);
259+
assertType('float|int<0, 1>', $r1 / $r2);
260+
261+
assertType('int<min, 15>', $r1 + $rMin);
262+
assertType('int<min, 5>', $r1 - $rMin);
263+
assertType('int<min, 50>', $r1 * $rMin);
264+
assertType('float|int<min, 2>', $r1 / $rMin);
265+
assertType('int<min, 15>', $rMin + $r1);
266+
assertType('int<min, -5>', $rMin - $r1);
267+
assertType('int<min, 50>', $rMin * $r1);
268+
assertType('float|int<min, 0>', $rMin / $r1);
269+
270+
assertType('int<6, max>', $r1 + $rMax);
271+
assertType('int<-4, max>', $r1 - $rMax);
272+
assertType('int<5, max>', $r1 * $rMax);
273+
assertType('float|int<0, max>', $r1 / $rMax);
274+
assertType('int<6, max>', $rMax + $r1);
275+
assertType('int<4, max>', $rMax - $r1);
276+
assertType('int<5, max>', $rMax * $r1);
277+
assertType('float|int<5, max>', $rMax / $r1);
278+
279+
assertType('5|10|15|20|30', $x / $y);
280+
}
197281
}

0 commit comments

Comments
 (0)