1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Support for bitwise not

This commit is contained in:
bugreportuser 2019-04-13 08:11:25 -06:00 committed by Matthew Brown
parent 0686d347c4
commit 8454c0db39
2 changed files with 94 additions and 0 deletions

View File

@ -35,7 +35,9 @@ use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\InvalidCast;
use Psalm\Issue\InvalidClone;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidOperand;
use Psalm\Issue\PossiblyInvalidCast;
use Psalm\Issue\PossiblyInvalidOperand;
use Psalm\Issue\PossiblyUndefinedVariable;
use Psalm\Issue\UndefinedConstant;
use Psalm\Issue\UndefinedVariable;
@ -232,6 +234,70 @@ class ExpressionAnalyzer
if (self::analyze($statements_analyzer, $stmt->expr, $context) === 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) {
if (BinaryOpAnalyzer::analyze(
$statements_analyzer,

View File

@ -197,6 +197,19 @@ class BinaryOperationTest extends TestCase
'$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;',
'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',
],
];
}
}