Skip to content

Commit

Permalink
support shift and bitwise operations in constants (#4740)
Browse files Browse the repository at this point in the history
  • Loading branch information
orklah committed Nov 30, 2020
1 parent b60c42a commit a760a24
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 19 deletions.
Expand Up @@ -43,13 +43,11 @@ public static function analyze(
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Pow
|| (($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftLeft
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftRight
)
)
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftLeft
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftRight
) {
NonDivArithmeticOpAnalyzer::analyze(
$statements_analyzer,
Expand Down
Expand Up @@ -313,11 +313,17 @@ private static function analyzeNonDivOperands(
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mul) {
$calculated_type = Type::getInt(false, $left_type_part->value * $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Pow) {
$calculated_type = Type::getInt(false, $left_type_part->value ^ $right_type_part->value);
$calculated_type = Type::getInt(false, $left_type_part->value ** $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) {
$calculated_type = Type::getInt(false, $left_type_part->value | $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd) {
$calculated_type = Type::getInt(false, $left_type_part->value & $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) {
$calculated_type = Type::getInt(false, $left_type_part->value ^ $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\ShiftLeft) {
$calculated_type = Type::getInt(false, $left_type_part->value << $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\ShiftRight) {
$calculated_type = Type::getInt(false, $left_type_part->value >> $right_type_part->value);
} elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Div) {
$value = $left_type_part->value / $right_type_part->value;

Expand Down
Expand Up @@ -13,6 +13,9 @@
use function array_shift;
use function reset;

/**
* This class takes a statement and return its type by analyzing each part of the statement if necessary
*/
class SimpleTypeInferer
{
/**
Expand Down Expand Up @@ -148,6 +151,11 @@ public static function infer(
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Pow
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftRight
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\ShiftLeft
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd
) {
NonDivArithmeticOpAnalyzer::analyze(
$file_source instanceof StatementsSource ? $file_source : null,
Expand Down
10 changes: 10 additions & 0 deletions src/Psalm/Internal/Codebase/ConstantTypeResolver.php
Expand Up @@ -58,6 +58,8 @@ public static function resolve(
|| $c instanceof UnresolvedConstant\UnresolvedDivisionOp
|| $c instanceof UnresolvedConstant\UnresolvedMultiplicationOp
|| $c instanceof UnresolvedConstant\UnresolvedBitwiseOr
|| $c instanceof UnresolvedConstant\UnresolvedBitwiseXor
|| $c instanceof UnresolvedConstant\UnresolvedBitwiseAnd
) {
if (($left instanceof Type\Atomic\TLiteralFloat || $left instanceof Type\Atomic\TLiteralInt)
&& ($right instanceof Type\Atomic\TLiteralFloat || $right instanceof Type\Atomic\TLiteralInt)
Expand All @@ -78,6 +80,14 @@ public static function resolve(
return self::getLiteralTypeFromScalarValue($left->value | $right->value);
}

if ($c instanceof UnresolvedConstant\UnresolvedBitwiseXor) {
return self::getLiteralTypeFromScalarValue($left->value ^ $right->value);
}

if ($c instanceof UnresolvedConstant\UnresolvedBitwiseAnd) {
return self::getLiteralTypeFromScalarValue($left->value & $right->value);
}

return self::getLiteralTypeFromScalarValue($left->value * $right->value);
}

Expand Down
Expand Up @@ -60,6 +60,14 @@ public static function getUnresolvedClassConstExpr(
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) {
return new UnresolvedConstant\UnresolvedBitwiseOr($left, $right);
}

if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) {
return new UnresolvedConstant\UnresolvedBitwiseXor($left, $right);
}

if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd) {
return new UnresolvedConstant\UnresolvedBitwiseAnd($left, $right);
}
}

if ($stmt instanceof PhpParser\Node\Expr\Ternary) {
Expand Down
@@ -0,0 +1,10 @@
<?php

namespace Psalm\Internal\Scanner\UnresolvedConstant;

/**
* @psalm-immutable
*/
class UnresolvedBitwiseAnd extends UnresolvedBinaryOp
{
}
@@ -0,0 +1,10 @@
<?php

namespace Psalm\Internal\Scanner\UnresolvedConstant;

/**
* @psalm-immutable
*/
class UnresolvedBitwiseXor extends UnresolvedBinaryOp
{
}
16 changes: 7 additions & 9 deletions tests/BinaryOperationTest.php
Expand Up @@ -174,9 +174,9 @@ function foo(string $s) : int {
'assertions' => [
'$a' => 'int',
'$b' => 'int',
'$c' => 'positive-int',
'$d' => 'positive-int',
'$e' => 'positive-int',
'$c' => 'int',
'$d' => 'int',
'$e' => 'int',
'$f' => 'string',
],
],
Expand All @@ -187,8 +187,8 @@ function foo(string $s) : int {
$c = (true xor false);
$d = (false xor false);',
'assertions' => [
'$a' => 'positive-int',
'$b' => 'positive-int',
'$a' => 'int',
'$b' => 'int',
'$c' => 'bool',
'$d' => 'bool',
],
Expand Down Expand Up @@ -216,11 +216,9 @@ function foo(string $s) : int {
],
'exponent' => [
'<?php
$a = "x" ^ "y";
$b = 4 ^ 5;',
$b = 4 ** 5;',
'assertions' => [
'$a' => 'string',
'$b' => 'positive-int',
'$b' => 'int',
],
],
'bitwiseNot' => [
Expand Down
28 changes: 26 additions & 2 deletions tests/Php56Test.php
Expand Up @@ -27,6 +27,11 @@ class C {
const THREE = self::TWO + 1;
const ONE_THIRD = self::ONE / self::THREE;
const SENTENCE = "The value of THREE is " . self::THREE;
const SHIFT = self::ONE >> 2;
const SHIFT2 = self::ONE << 1;
const BITAND = 1 & 1;
const BITOR = 1 | 1;
const BITXOR = 1 ^ 1;
/** @var int */
public $four = self::ONE + self::THREE;
Expand All @@ -46,7 +51,12 @@ public function f($a = self::ONE + self::THREE) {
$c1_3rd = C::ONE_THIRD;
$c_sentence = C::SENTENCE;
$cf = (new C)->f();
$c4 = (new C)->four;',
$c4 = (new C)->four;
$shift = C::SHIFT;
$shift2 = C::SHIFT2;
$bitand = C::BITAND;
$bitor = C::BITOR;
$bitxor = C::BITXOR;',
'assertions' => [
'$c1' => 'int',
'$c2===' => 'int(2)',
Expand All @@ -55,18 +65,32 @@ public function f($a = self::ONE + self::THREE) {
'$c_sentence' => 'string',
'$cf' => 'int',
'$c4' => 'int',
'$shift' => 'int',
'$shift2' => 'int',
'$bitand' => 'int',
'$bitor' => 'int',
'$bitxor' => 'int',
],
],
'constFeatures' => [
'<?php
const ONE = 1;
const TWO = ONE * 2;
const BITWISE = ONE & 2;
const SHIFT = ONE << 2;
const SHIFT2 = PHP_INT_MAX << 1;
$one = ONE;
$two = TWO;',
$two = TWO;
$bitwise = BITWISE;
$shift = SHIFT;
$shift2 = SHIFT2;',
'assertions' => [
'$one' => 'int',
'$two' => 'int',
'$bitwise' => 'int',
'$shift' => 'int',
'$shift2' => 'int',
],
],
'exponentiation' => [
Expand Down

0 comments on commit a760a24

Please sign in to comment.