Skip to content

Commit dd534ea

Browse files
thg2kondrejmirtes
authored andcommitted
Implement bit shift operation on integers and unions
1 parent 231a227 commit dd534ea

File tree

3 files changed

+117
-17
lines changed

3 files changed

+117
-17
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,6 @@ parameters:
360360
count: 1
361361
path: src/Reflection/InitializerExprTypeResolver.php
362362

363-
-
364-
message: "#^Binary operation \"\\<\\<\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\<0, max\\>\\|string\\|null results in an error\\.$#"
365-
count: 1
366-
path: src/Reflection/InitializerExprTypeResolver.php
367-
368-
-
369-
message: "#^Binary operation \"\\>\\>\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\<0, max\\>\\|string\\|null results in an error\\.$#"
370-
count: 1
371-
path: src/Reflection/InitializerExprTypeResolver.php
372-
373363
-
374364
message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#"
375365
count: 1

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
use function dirname;
7878
use function floor;
7979
use function in_array;
80+
use function intval;
8081
use function is_finite;
8182
use function is_float;
8283
use function is_int;
@@ -1255,7 +1256,7 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb
12551256
return new ErrorType();
12561257
}
12571258

1258-
$resultType = $this->getTypeFromValue($leftNumberType->getValue() << $rightNumberType->getValue());
1259+
$resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue()));
12591260
if ($generalize) {
12601261
$resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
12611262
}
@@ -1273,7 +1274,7 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb
12731274
return new ErrorType();
12741275
}
12751276

1276-
return new IntegerType();
1277+
return $this->resolveCommonMath(new Expr\BinaryOp\ShiftLeft($left, $right), $leftType, $rightType);
12771278
}
12781279

12791280
/**
@@ -1312,7 +1313,7 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall
13121313
return new ErrorType();
13131314
}
13141315

1315-
$resultType = $this->getTypeFromValue($leftNumberType->getValue() >> $rightNumberType->getValue());
1316+
$resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue()));
13161317
if ($generalize) {
13171318
$resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
13181319
}
@@ -1330,7 +1331,7 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall
13301331
return new ErrorType();
13311332
}
13321333

1333-
return new IntegerType();
1334+
return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType);
13341335
}
13351336

13361337
public function resolveIdenticalType(Type $leftType, Type $rightType): BooleanType
@@ -1469,7 +1470,7 @@ private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type
14691470
}
14701471

14711472
/**
1472-
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div $expr
1473+
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
14731474
*/
14741475
private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
14751476
{
@@ -1536,6 +1537,9 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
15361537
$leftNumberType->isFloat()->yes()
15371538
|| $rightNumberType->isFloat()->yes()
15381539
) {
1540+
if ($expr instanceof Expr\BinaryOp\ShiftLeft || $expr instanceof Expr\BinaryOp\ShiftRight) {
1541+
return new IntegerType();
1542+
}
15391543
return new FloatType();
15401544
}
15411545

@@ -1560,7 +1564,7 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
15601564

15611565
/**
15621566
* @param ConstantIntegerType|IntegerRangeType $range
1563-
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
1567+
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $node
15641568
*/
15651569
private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
15661570
{
@@ -1683,7 +1687,7 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
16831687
if (!is_finite($max)) {
16841688
$max = null;
16851689
}
1686-
} else {
1690+
} elseif ($node instanceof Expr\BinaryOp\Div) {
16871691
if ($operand instanceof ConstantIntegerType) {
16881692
$min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
16891693
$max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
@@ -1781,6 +1785,26 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
17811785

17821786
return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
17831787
}
1788+
} elseif ($node instanceof Expr\BinaryOp\ShiftLeft) {
1789+
if (!$operand instanceof ConstantIntegerType) {
1790+
return new IntegerType();
1791+
}
1792+
if ($operand->getValue() < 0) {
1793+
return new ErrorType();
1794+
}
1795+
$min = $rangeMin !== null ? intval($rangeMin) << $operand->getValue() : null;
1796+
$max = $rangeMax !== null ? intval($rangeMax) << $operand->getValue() : null;
1797+
} elseif ($node instanceof Expr\BinaryOp\ShiftRight) {
1798+
if (!$operand instanceof ConstantIntegerType) {
1799+
return new IntegerType();
1800+
}
1801+
if ($operand->getValue() < 0) {
1802+
return new ErrorType();
1803+
}
1804+
$min = $rangeMin !== null ? intval($rangeMin) >> $operand->getValue() : null;
1805+
$max = $rangeMax !== null ? intval($rangeMax) >> $operand->getValue() : null;
1806+
} else {
1807+
throw new ShouldNotHappenException();
17841808
}
17851809

17861810
if (is_float($min)) {

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,92 @@ public function sayHello($p, $u): void
342342
assertType('float|int<-2, 2>', $p / $u);
343343
}
344344

345+
/**
346+
* @param int<-5, 5> $a
347+
* @param int<5, max> $b
348+
* @param int<min, -5> $c
349+
* @param 1|int<5, 10>|25|int<30, 40> $d
350+
* @param 1|3.0|"5" $e
351+
* @param 1|"ciao" $f
352+
*/
353+
public function shiftLeft($a, $b, $c, $d, $e, $f): void
354+
{
355+
assertType('int<-5, 5>', $a << 0);
356+
assertType('int<5, max>', $b << 0);
357+
assertType('int<min, -5>', $c << 0);
358+
assertType('1|25|int<5, 10>|int<30, 40>', $d << 0);
359+
assertType('1|3|5', $e << 0);
360+
assertType('*ERROR*', $f << 0);
361+
362+
assertType('int<-10, 10>', $a << 1);
363+
assertType('int<10, max>', $b << 1);
364+
assertType('int<min, -10>', $c << 1);
365+
assertType('2|50|int<10, 20>|int<60, 80>', $d << 1);
366+
assertType('2|6|10', $e << 1);
367+
assertType('*ERROR*', $f << 1);
368+
369+
assertType('*ERROR*', $a << -1);
370+
371+
assertType('int', $a << $b);
372+
373+
assertType('0', null << 1);
374+
assertType('0', false << 1);
375+
assertType('2', true << 1);
376+
assertType('10', "10" << 0);
377+
assertType('*ERROR*', "ciao" << 0);
378+
assertType('30', 15.9 << 1);
379+
assertType('*ERROR*', array(5) << 1);
380+
381+
assertType('8', 4.1 << 1.9);
382+
383+
/** @var float */
384+
$float = 4.1;
385+
assertType('int', $float << 1.9);
386+
}
387+
388+
/**
389+
* @param int<-5, 5> $a
390+
* @param int<5, max> $b
391+
* @param int<min, -5> $c
392+
* @param 1|int<5, 10>|25|int<30, 40> $d
393+
* @param 1|3.0|"5" $e
394+
* @param 1|"ciao" $f
395+
*/
396+
public function shiftRight($a, $b, $c, $d, $e, $f): void
397+
{
398+
assertType('int<-5, 5>', $a >> 0);
399+
assertType('int<5, max>', $b >> 0);
400+
assertType('int<min, -5>', $c >> 0);
401+
assertType('1|25|int<5, 10>|int<30, 40>', $d >> 0);
402+
assertType('1|3|5', $e >> 0);
403+
assertType('*ERROR*', $f >> 0);
404+
405+
assertType('int<-3, 2>', $a >> 1);
406+
assertType('int<2, max>', $b >> 1);
407+
assertType('int<min, -3>', $c >> 1);
408+
assertType('0|12|int<2, 5>|int<15, 20>', $d >> 1);
409+
assertType('0|1|2', $e >> 1);
410+
assertType('*ERROR*', $f >> 1);
411+
412+
assertType('*ERROR*', $a >> -1);
413+
414+
assertType('int', $a >> $b);
415+
416+
assertType('0', null >> 1);
417+
assertType('0', false >> 1);
418+
assertType('0', true >> 1);
419+
assertType('10', "10" >> 0);
420+
assertType('*ERROR*', "ciao" >> 0);
421+
assertType('7', 15.9 >> 1);
422+
assertType('*ERROR*', array(5) >> 1);
423+
424+
assertType('2', 4.1 >> 1.9);
425+
426+
/** @var float */
427+
$float = 4.1;
428+
assertType('int', $float >> 1.9);
429+
}
430+
345431
/**
346432
* @param int<0, max> $positive
347433
* @param int<min, 0> $negative

0 commit comments

Comments
 (0)