Skip to content

Commit

Permalink
Implement bit shift operation on integers and unions
Browse files Browse the repository at this point in the history
  • Loading branch information
thg2k authored and ondrejmirtes committed Jun 14, 2024
1 parent 231a227 commit dd534ea
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 17 deletions.
10 changes: 0 additions & 10 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,6 @@ parameters:
count: 1
path: src/Reflection/InitializerExprTypeResolver.php

-
message: "#^Binary operation \"\\<\\<\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\<0, max\\>\\|string\\|null results in an error\\.$#"
count: 1
path: src/Reflection/InitializerExprTypeResolver.php

-
message: "#^Binary operation \"\\>\\>\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\<0, max\\>\\|string\\|null results in an error\\.$#"
count: 1
path: src/Reflection/InitializerExprTypeResolver.php

-
message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#"
count: 1
Expand Down
38 changes: 31 additions & 7 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
use function dirname;
use function floor;
use function in_array;
use function intval;
use function is_finite;
use function is_float;
use function is_int;
Expand Down Expand Up @@ -1255,7 +1256,7 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb
return new ErrorType();
}

$resultType = $this->getTypeFromValue($leftNumberType->getValue() << $rightNumberType->getValue());
$resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue()));
if ($generalize) {
$resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
}
Expand All @@ -1273,7 +1274,7 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb
return new ErrorType();
}

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

/**
Expand Down Expand Up @@ -1312,7 +1313,7 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall
return new ErrorType();
}

$resultType = $this->getTypeFromValue($leftNumberType->getValue() >> $rightNumberType->getValue());
$resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue()));
if ($generalize) {
$resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
}
Expand All @@ -1330,7 +1331,7 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall
return new ErrorType();
}

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

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

/**
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div $expr
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
*/
private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
{
Expand Down Expand Up @@ -1536,6 +1537,9 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri
$leftNumberType->isFloat()->yes()
|| $rightNumberType->isFloat()->yes()
) {
if ($expr instanceof Expr\BinaryOp\ShiftLeft || $expr instanceof Expr\BinaryOp\ShiftRight) {
return new IntegerType();
}
return new FloatType();
}

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

/**
* @param ConstantIntegerType|IntegerRangeType $range
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
* @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $node
*/
private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
{
Expand Down Expand Up @@ -1683,7 +1687,7 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T
if (!is_finite($max)) {
$max = null;
}
} else {
} elseif ($node instanceof Expr\BinaryOp\Div) {
if ($operand instanceof ConstantIntegerType) {
$min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
$max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
Expand Down Expand Up @@ -1781,6 +1785,26 @@ private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): T

return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
}
} elseif ($node instanceof Expr\BinaryOp\ShiftLeft) {
if (!$operand instanceof ConstantIntegerType) {
return new IntegerType();
}
if ($operand->getValue() < 0) {
return new ErrorType();
}
$min = $rangeMin !== null ? intval($rangeMin) << $operand->getValue() : null;
$max = $rangeMax !== null ? intval($rangeMax) << $operand->getValue() : null;
} elseif ($node instanceof Expr\BinaryOp\ShiftRight) {
if (!$operand instanceof ConstantIntegerType) {
return new IntegerType();
}
if ($operand->getValue() < 0) {
return new ErrorType();
}
$min = $rangeMin !== null ? intval($rangeMin) >> $operand->getValue() : null;
$max = $rangeMax !== null ? intval($rangeMax) >> $operand->getValue() : null;
} else {
throw new ShouldNotHappenException();
}

if (is_float($min)) {
Expand Down
86 changes: 86 additions & 0 deletions tests/PHPStan/Analyser/nsrt/integer-range-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,92 @@ public function sayHello($p, $u): void
assertType('float|int<-2, 2>', $p / $u);
}

/**
* @param int<-5, 5> $a
* @param int<5, max> $b
* @param int<min, -5> $c
* @param 1|int<5, 10>|25|int<30, 40> $d
* @param 1|3.0|"5" $e
* @param 1|"ciao" $f
*/
public function shiftLeft($a, $b, $c, $d, $e, $f): void
{
assertType('int<-5, 5>', $a << 0);
assertType('int<5, max>', $b << 0);
assertType('int<min, -5>', $c << 0);
assertType('1|25|int<5, 10>|int<30, 40>', $d << 0);
assertType('1|3|5', $e << 0);
assertType('*ERROR*', $f << 0);

assertType('int<-10, 10>', $a << 1);
assertType('int<10, max>', $b << 1);
assertType('int<min, -10>', $c << 1);
assertType('2|50|int<10, 20>|int<60, 80>', $d << 1);
assertType('2|6|10', $e << 1);
assertType('*ERROR*', $f << 1);

assertType('*ERROR*', $a << -1);

assertType('int', $a << $b);

assertType('0', null << 1);
assertType('0', false << 1);
assertType('2', true << 1);
assertType('10', "10" << 0);
assertType('*ERROR*', "ciao" << 0);
assertType('30', 15.9 << 1);
assertType('*ERROR*', array(5) << 1);

assertType('8', 4.1 << 1.9);

/** @var float */
$float = 4.1;
assertType('int', $float << 1.9);
}

/**
* @param int<-5, 5> $a
* @param int<5, max> $b
* @param int<min, -5> $c
* @param 1|int<5, 10>|25|int<30, 40> $d
* @param 1|3.0|"5" $e
* @param 1|"ciao" $f
*/
public function shiftRight($a, $b, $c, $d, $e, $f): void
{
assertType('int<-5, 5>', $a >> 0);
assertType('int<5, max>', $b >> 0);
assertType('int<min, -5>', $c >> 0);
assertType('1|25|int<5, 10>|int<30, 40>', $d >> 0);
assertType('1|3|5', $e >> 0);
assertType('*ERROR*', $f >> 0);

assertType('int<-3, 2>', $a >> 1);
assertType('int<2, max>', $b >> 1);
assertType('int<min, -3>', $c >> 1);
assertType('0|12|int<2, 5>|int<15, 20>', $d >> 1);
assertType('0|1|2', $e >> 1);
assertType('*ERROR*', $f >> 1);

assertType('*ERROR*', $a >> -1);

assertType('int', $a >> $b);

assertType('0', null >> 1);
assertType('0', false >> 1);
assertType('0', true >> 1);
assertType('10', "10" >> 0);
assertType('*ERROR*', "ciao" >> 0);
assertType('7', 15.9 >> 1);
assertType('*ERROR*', array(5) >> 1);

assertType('2', 4.1 >> 1.9);

/** @var float */
$float = 4.1;
assertType('int', $float >> 1.9);
}

/**
* @param int<0, max> $positive
* @param int<min, 0> $negative
Expand Down

0 comments on commit dd534ea

Please sign in to comment.