1
0
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:
kkmuffme 2024-01-12 22:40:47 +01:00
parent d5b713e439
commit f3543ca9ab
2 changed files with 100 additions and 14 deletions

View File

@ -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);
} }
} }

View File

@ -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',
],
]; ];
} }
} }