mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Support for bitwise not
This commit is contained in:
parent
0686d347c4
commit
8454c0db39
@ -35,7 +35,9 @@ use Psalm\Issue\ForbiddenCode;
|
|||||||
use Psalm\Issue\InvalidCast;
|
use Psalm\Issue\InvalidCast;
|
||||||
use Psalm\Issue\InvalidClone;
|
use Psalm\Issue\InvalidClone;
|
||||||
use Psalm\Issue\InvalidDocblock;
|
use Psalm\Issue\InvalidDocblock;
|
||||||
|
use Psalm\Issue\InvalidOperand;
|
||||||
use Psalm\Issue\PossiblyInvalidCast;
|
use Psalm\Issue\PossiblyInvalidCast;
|
||||||
|
use Psalm\Issue\PossiblyInvalidOperand;
|
||||||
use Psalm\Issue\PossiblyUndefinedVariable;
|
use Psalm\Issue\PossiblyUndefinedVariable;
|
||||||
use Psalm\Issue\UndefinedConstant;
|
use Psalm\Issue\UndefinedConstant;
|
||||||
use Psalm\Issue\UndefinedVariable;
|
use Psalm\Issue\UndefinedVariable;
|
||||||
@ -232,6 +234,70 @@ class ExpressionAnalyzer
|
|||||||
if (self::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
if (self::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isset($stmt->expr->inferredType)) {
|
||||||
|
$stmt->inferredType = new Type\Union([new TInt(), new TString()]);
|
||||||
|
} elseif ($stmt->expr->inferredType->isMixed()) {
|
||||||
|
$stmt->inferredType = Type::getMixed();
|
||||||
|
} else {
|
||||||
|
$acceptable_types = [];
|
||||||
|
$unacceptable_type = null;
|
||||||
|
$has_valid_operand = false;
|
||||||
|
|
||||||
|
foreach ($stmt->expr->inferredType->getTypes() as $type_string => $type_part) {
|
||||||
|
if ($type_part instanceof TInt || $type_part instanceof TString) {
|
||||||
|
if ($type_part instanceof Type\Atomic\TLiteralInt) {
|
||||||
|
$type_part->value = ~$type_part->value;
|
||||||
|
} elseif ($type_part instanceof Type\Atomic\TLiteralString) {
|
||||||
|
$type_part->value = ~$type_part->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$acceptable_types[] = $type_part;
|
||||||
|
$has_valid_operand = true;
|
||||||
|
} elseif ($type_part instanceof TFloat) {
|
||||||
|
$type_part = ($type_part instanceof Type\Atomic\TLiteralFloat) ?
|
||||||
|
new Type\Atomic\TLiteralInt(~$type_part->value) :
|
||||||
|
new TInt;
|
||||||
|
|
||||||
|
$stmt->expr->inferredType->removeType($type_string);
|
||||||
|
$stmt->expr->inferredType->addType($type_part);
|
||||||
|
|
||||||
|
$acceptable_types[] = $type_part;
|
||||||
|
$has_valid_operand = true;
|
||||||
|
} elseif (!$unacceptable_type) {
|
||||||
|
$unacceptable_type = $type_part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($unacceptable_type) {
|
||||||
|
$message = 'Cannot negate a non-numeric non-string type ' . $unacceptable_type;
|
||||||
|
if ($has_valid_operand) {
|
||||||
|
if (IssueBuffer::accepts(
|
||||||
|
new PossiblyInvalidOperand(
|
||||||
|
$message,
|
||||||
|
new CodeLocation($statements_analyzer, $stmt)
|
||||||
|
),
|
||||||
|
$statements_analyzer->getSuppressedIssues()
|
||||||
|
)) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (IssueBuffer::accepts(
|
||||||
|
new InvalidOperand(
|
||||||
|
$message,
|
||||||
|
new CodeLocation($statements_analyzer, $stmt)
|
||||||
|
),
|
||||||
|
$statements_analyzer->getSuppressedIssues()
|
||||||
|
)) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->inferredType = Type::getMixed();
|
||||||
|
} else {
|
||||||
|
$stmt->inferredType = new Type\Union($acceptable_types);
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
||||||
if (BinaryOpAnalyzer::analyze(
|
if (BinaryOpAnalyzer::analyze(
|
||||||
$statements_analyzer,
|
$statements_analyzer,
|
||||||
|
@ -197,6 +197,19 @@ class BinaryOperationTest extends TestCase
|
|||||||
'$b' => 'int',
|
'$b' => 'int',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'bitwiseNot' => [
|
||||||
|
'<?php
|
||||||
|
$a = ~4;
|
||||||
|
$b = ~4.0;
|
||||||
|
$c = ~4.4;
|
||||||
|
$d = ~"a";',
|
||||||
|
'assertions' => [
|
||||||
|
'$a' => 'int',
|
||||||
|
'$b' => 'int',
|
||||||
|
'$c' => 'int',
|
||||||
|
'$d' => 'string',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +301,21 @@ class BinaryOperationTest extends TestCase
|
|||||||
$a = "x" | new stdClass;',
|
$a = "x" | new stdClass;',
|
||||||
'error_message' => 'InvalidOperand',
|
'error_message' => 'InvalidOperand',
|
||||||
],
|
],
|
||||||
|
'invalidBitwiseNot' => [
|
||||||
|
'<?php
|
||||||
|
$a = ~new stdClass;',
|
||||||
|
'error_message' => 'InvalidOperand',
|
||||||
|
],
|
||||||
|
'possiblyInvalidBitwiseNot' => [
|
||||||
|
'<?php
|
||||||
|
$a = ~(rand(0, 1) ? 2 : null);',
|
||||||
|
'error_message' => 'PossiblyInvalidOperand',
|
||||||
|
],
|
||||||
|
'invalidBooleanBitwiseNot' => [
|
||||||
|
'<?php
|
||||||
|
$a = ~true;',
|
||||||
|
'error_message' => 'InvalidOperand',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user