mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
add the fix for empty() too and fix empty returning bool on true/false only cases hiding errors when functions called
This commit is contained in:
parent
d5b713e439
commit
f3543ca9ab
@ -8,8 +8,15 @@ use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
use Psalm\Issue\InvalidArgument;
|
||||
use Psalm\Issue\TypeDoesNotContainType;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -35,21 +42,68 @@ final class EmptyAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
if (($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))
|
||||
&& $stmt_expr_type->hasBool()
|
||||
&& $stmt_expr_type->isSingle()
|
||||
&& !$stmt_expr_type->from_docblock
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidArgument(
|
||||
'Calling empty on a boolean value is almost certainly unintended',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
|
||||
'empty',
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
);
|
||||
$expr_type = $statements_analyzer->node_data->getType($stmt->expr);
|
||||
|
||||
if ($expr_type) {
|
||||
if ($expr_type->hasBool()
|
||||
&& $expr_type->isSingle()
|
||||
&& !$expr_type->from_docblock
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidArgument(
|
||||
'Calling empty on a boolean value is almost certainly unintended',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
|
||||
'empty',
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($expr_type->isAlwaysTruthy() && $expr_type->possibly_undefined === false) {
|
||||
$stmt_type = new TFalse($expr_type->from_docblock);
|
||||
} elseif ($expr_type->isAlwaysFalsy()) {
|
||||
$stmt_type = new TTrue($expr_type->from_docblock);
|
||||
} else {
|
||||
$has_both = false;
|
||||
$both_types = $expr_type->getBuilder();
|
||||
if (count($expr_type->getAtomicTypes()) > 1) {
|
||||
foreach ($both_types->getAtomicTypes() as $key => $atomic_type) {
|
||||
if ($atomic_type->isTruthy()
|
||||
|| $atomic_type->isFalsy()
|
||||
|| $atomic_type instanceof TBool) {
|
||||
$both_types->removeType($key);
|
||||
continue;
|
||||
}
|
||||
|
||||
$has_both = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($has_both) {
|
||||
$both_types = $both_types->freeze();
|
||||
IssueBuffer::maybeAdd(
|
||||
new TypeDoesNotContainType(
|
||||
'Operand of type ' . $expr_type->getId() . ' contains ' .
|
||||
'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
|
||||
$both_types->getId() . ', which can be falsy and truthy. ' .
|
||||
'This can cause possibly unexpected behavior. Use strict comparison instead.',
|
||||
new CodeLocation($statements_analyzer, $stmt),
|
||||
$expr_type->getId() . ' truthy-falsy',
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
);
|
||||
}
|
||||
|
||||
$stmt_type = new TBool();
|
||||
}
|
||||
|
||||
$stmt_type = new Union([$stmt_type], [
|
||||
'parent_nodes' => $expr_type->parent_nodes,
|
||||
]);
|
||||
} else {
|
||||
$stmt_type = Type::getBool();
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
}
|
||||
}
|
||||
|
@ -612,6 +612,14 @@ class EmptyTest extends TestCase
|
||||
'$GLOBALS[\'sql_query\']===' => 'string',
|
||||
],
|
||||
],
|
||||
'emptyLiteralTrueFalse' => [
|
||||
'code' => '<?php
|
||||
$b = "asdf";
|
||||
$x = !empty($b);',
|
||||
'assertions' => [
|
||||
'$x===' => 'true',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -720,6 +728,30 @@ class EmptyTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'LessSpecificReturnStatement',
|
||||
],
|
||||
'impossibleEmptyOnFalsyFunctionCall' => [
|
||||
'code' => '<?php
|
||||
/** @return false|null */
|
||||
function bar() {
|
||||
return rand(0, 5) ? null : false;
|
||||
}
|
||||
|
||||
if (!empty(bar())) {
|
||||
echo "abc";
|
||||
}',
|
||||
'error_message' => 'DocblockTypeContradiction',
|
||||
],
|
||||
'redundantEmptyOnFalsyFunctionCall' => [
|
||||
'code' => '<?php
|
||||
/** @return false|null */
|
||||
function bar() {
|
||||
return rand(0, 5) ? null : false;
|
||||
}
|
||||
|
||||
if (empty(bar())) {
|
||||
echo "abc";
|
||||
}',
|
||||
'error_message' => 'RedundantConditionGivenDocblockType',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user