1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

support shift and bitwise operations in constants (#4740)

This commit is contained in:
orklah 2020-11-30 03:43:49 +01:00 committed by Daniil Gentili
parent 08d9246b9a
commit 5f9aff5734
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
9 changed files with 91 additions and 19 deletions

View File

@ -43,13 +43,11 @@ class NonComparisonOpAnalyzer
|| $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\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,

View File

@ -313,11 +313,17 @@ class NonDivArithmeticOpAnalyzer
} 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;

View File

@ -13,6 +13,9 @@ use function count;
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
{
/**
@ -148,6 +151,11 @@ class SimpleTypeInferer
|| $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,

View File

@ -58,6 +58,8 @@ class ConstantTypeResolver
|| $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)
@ -78,6 +80,14 @@ class ConstantTypeResolver
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);
}

View File

@ -60,6 +60,14 @@ class ExpressionResolver
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) {

View File

@ -0,0 +1,10 @@
<?php
namespace Psalm\Internal\Scanner\UnresolvedConstant;
/**
* @psalm-immutable
*/
class UnresolvedBitwiseAnd extends UnresolvedBinaryOp
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Psalm\Internal\Scanner\UnresolvedConstant;
/**
* @psalm-immutable
*/
class UnresolvedBitwiseXor extends UnresolvedBinaryOp
{
}

View File

@ -174,9 +174,9 @@ class BinaryOperationTest extends TestCase
'assertions' => [
'$a' => 'int',
'$b' => 'int',
'$c' => 'positive-int',
'$d' => 'positive-int',
'$e' => 'positive-int',
'$c' => 'int',
'$d' => 'int',
'$e' => 'int',
'$f' => 'string',
],
],
@ -187,8 +187,8 @@ class BinaryOperationTest extends TestCase
$c = (true xor false);
$d = (false xor false);',
'assertions' => [
'$a' => 'positive-int',
'$b' => 'positive-int',
'$a' => 'int',
'$b' => 'int',
'$c' => 'bool',
'$d' => 'bool',
],
@ -216,11 +216,9 @@ class BinaryOperationTest extends TestCase
],
'exponent' => [
'<?php
$a = "x" ^ "y";
$b = 4 ^ 5;',
$b = 4 ** 5;',
'assertions' => [
'$a' => 'string',
'$b' => 'positive-int',
'$b' => 'int',
],
],
'bitwiseNot' => [

View File

@ -27,6 +27,11 @@ class Php56Test extends TestCase
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;
@ -46,7 +51,12 @@ class Php56Test extends TestCase
$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)',
@ -55,18 +65,32 @@ class Php56Test extends TestCase
'$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' => [