mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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\Internal\Analyzer\StatementsAnalyzer;
|
||||||
use Psalm\Issue\ForbiddenCode;
|
use Psalm\Issue\ForbiddenCode;
|
||||||
use Psalm\Issue\InvalidArgument;
|
use Psalm\Issue\InvalidArgument;
|
||||||
|
use Psalm\Issue\TypeDoesNotContainType;
|
||||||
use Psalm\IssueBuffer;
|
use Psalm\IssueBuffer;
|
||||||
use Psalm\Type;
|
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
|
* @internal
|
||||||
@ -35,21 +42,68 @@ final class EmptyAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))
|
$expr_type = $statements_analyzer->node_data->getType($stmt->expr);
|
||||||
&& $stmt_expr_type->hasBool()
|
|
||||||
&& $stmt_expr_type->isSingle()
|
if ($expr_type) {
|
||||||
&& !$stmt_expr_type->from_docblock
|
if ($expr_type->hasBool()
|
||||||
) {
|
&& $expr_type->isSingle()
|
||||||
IssueBuffer::maybeAdd(
|
&& !$expr_type->from_docblock
|
||||||
new InvalidArgument(
|
) {
|
||||||
'Calling empty on a boolean value is almost certainly unintended',
|
IssueBuffer::maybeAdd(
|
||||||
new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
|
new InvalidArgument(
|
||||||
'empty',
|
'Calling empty on a boolean value is almost certainly unintended',
|
||||||
),
|
new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
|
||||||
$statements_analyzer->getSuppressedIssues(),
|
'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',
|
'$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',
|
'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