mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Fix #4374 - prevent paradox and allow Psalm to understand more assignments in conditionals
This commit is contained in:
parent
26352d0e39
commit
7df404bfb5
@ -476,7 +476,7 @@ return [
|
|||||||
'asin' => ['float', 'number'=>'float'],
|
'asin' => ['float', 'number'=>'float'],
|
||||||
'asinh' => ['float', 'number'=>'float'],
|
'asinh' => ['float', 'number'=>'float'],
|
||||||
'asort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'],
|
'asort' => ['bool', '&rw_array'=>'array', 'sort_flags='=>'int'],
|
||||||
'assert' => ['bool', 'assertion'=>'string|bool', 'description='=>'string|Throwable|null'],
|
'assert' => ['bool', 'assertion'=>'string|bool|int', 'description='=>'string|Throwable|null'],
|
||||||
'assert_options' => ['mixed|false', 'what'=>'int', 'value='=>'mixed'],
|
'assert_options' => ['mixed|false', 'what'=>'int', 'value='=>'mixed'],
|
||||||
'ast\get_kind_name' => ['string', 'kind'=>'int'],
|
'ast\get_kind_name' => ['string', 'kind'=>'int'],
|
||||||
'ast\get_metadata' => ['array<int,ast\Metadata>'],
|
'ast\get_metadata' => ['array<int,ast\Metadata>'],
|
||||||
|
@ -79,10 +79,14 @@ class IfAnalyzer
|
|||||||
$if_scope = new IfScope();
|
$if_scope = new IfScope();
|
||||||
|
|
||||||
// We need to clone the original context for later use if we're exiting in this if conditional
|
// We need to clone the original context for later use if we're exiting in this if conditional
|
||||||
if (!$stmt->else && !$stmt->elseifs && $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
|
if (!$stmt->else && !$stmt->elseifs
|
||||||
|
&& ($stmt->cond instanceof PhpParser\Node\Expr\BinaryOp
|
||||||
|
|| ($stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
|
||||||
|
&& $stmt->cond->expr instanceof PhpParser\Node\Expr\BinaryOp))
|
||||||
|
) {
|
||||||
$final_actions = ScopeAnalyzer::getControlActions(
|
$final_actions = ScopeAnalyzer::getControlActions(
|
||||||
$stmt->stmts,
|
$stmt->stmts,
|
||||||
$statements_analyzer->node_data,
|
null,
|
||||||
$codebase->config->exit_functions,
|
$codebase->config->exit_functions,
|
||||||
$context->break_types
|
$context->break_types
|
||||||
);
|
);
|
||||||
@ -881,7 +885,6 @@ class IfAnalyzer
|
|||||||
if ($has_leaving_statements && !$has_break_statement && !$stmt->else && !$stmt->elseifs) {
|
if ($has_leaving_statements && !$has_break_statement && !$stmt->else && !$stmt->elseifs) {
|
||||||
// If we're assigning inside
|
// If we're assigning inside
|
||||||
if ($if_conditional_scope->cond_assigned_var_ids
|
if ($if_conditional_scope->cond_assigned_var_ids
|
||||||
&& $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
|
||||||
&& $if_scope->mic_drop_context
|
&& $if_scope->mic_drop_context
|
||||||
) {
|
) {
|
||||||
self::addConditionallyAssignedVarsToContext(
|
self::addConditionallyAssignedVarsToContext(
|
||||||
@ -1866,23 +1869,23 @@ class IfAnalyzer
|
|||||||
$old_node_data = $statements_analyzer->node_data;
|
$old_node_data = $statements_analyzer->node_data;
|
||||||
$statements_analyzer->node_data = clone $old_node_data;
|
$statements_analyzer->node_data = clone $old_node_data;
|
||||||
|
|
||||||
$suppressed_issues = $statements_analyzer->getSuppressedIssues();
|
IssueBuffer::startRecording();
|
||||||
|
|
||||||
if (!in_array('RedundantCondition', $suppressed_issues, true)) {
|
|
||||||
$statements_analyzer->addSuppressedIssues(['RedundantCondition']);
|
|
||||||
}
|
|
||||||
if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
|
|
||||||
$statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']);
|
|
||||||
}
|
|
||||||
if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) {
|
|
||||||
$statements_analyzer->addSuppressedIssues(['TypeDoesNotContainType']);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($exprs as $expr) {
|
foreach ($exprs as $expr) {
|
||||||
|
if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) {
|
||||||
|
$fake_not = new PhpParser\Node\Expr\BinaryOp\BooleanOr(
|
||||||
|
self::negateExpr($expr->left),
|
||||||
|
self::negateExpr($expr->right),
|
||||||
|
$expr->getAttributes()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$fake_not = self::negateExpr($expr);
|
||||||
|
}
|
||||||
|
|
||||||
$fake_negated_expr = new PhpParser\Node\Expr\FuncCall(
|
$fake_negated_expr = new PhpParser\Node\Expr\FuncCall(
|
||||||
new PhpParser\Node\Name\FullyQualified('assert'),
|
new PhpParser\Node\Name\FullyQualified('assert'),
|
||||||
[new PhpParser\Node\Arg(
|
[new PhpParser\Node\Arg(
|
||||||
new PhpParser\Node\Expr\BooleanNot($expr, $expr->getAttributes()),
|
$fake_not,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
$expr->getAttributes()
|
$expr->getAttributes()
|
||||||
@ -1901,15 +1904,8 @@ class IfAnalyzer
|
|||||||
$mic_drop_context->inside_negation = !$mic_drop_context->inside_negation;
|
$mic_drop_context->inside_negation = !$mic_drop_context->inside_negation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array('RedundantCondition', $suppressed_issues, true)) {
|
IssueBuffer::clearRecordingLevel();
|
||||||
$statements_analyzer->removeSuppressedIssues(['RedundantCondition']);
|
IssueBuffer::stopRecording();
|
||||||
}
|
|
||||||
if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
|
|
||||||
$statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']);
|
|
||||||
}
|
|
||||||
if (!in_array('TypeDoesNotContainType', $suppressed_issues, true)) {
|
|
||||||
$statements_analyzer->removeSuppressedIssues(['TypeDoesNotContainType']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$statements_analyzer->node_data = $old_node_data;
|
$statements_analyzer->node_data = $old_node_data;
|
||||||
|
|
||||||
@ -1919,4 +1915,13 @@ class IfAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function negateExpr(PhpParser\Node\Expr $expr) : PhpParser\Node\Expr
|
||||||
|
{
|
||||||
|
if ($expr instanceof PhpParser\Node\Expr\BooleanNot) {
|
||||||
|
return $expr->expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PhpParser\Node\Expr\BooleanNot($expr, $expr->getAttributes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2942,6 +2942,22 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
|||||||
) {}
|
) {}
|
||||||
}'
|
}'
|
||||||
],
|
],
|
||||||
|
'assertWithAssignmentInOr' => [
|
||||||
|
'function test(int $x = null): int {
|
||||||
|
\assert($x || ($x = rand(0, 10)));
|
||||||
|
return $x;
|
||||||
|
}'
|
||||||
|
],
|
||||||
|
'noParadoxicalConditionAfterTwoAssignments' => [
|
||||||
|
'<?php
|
||||||
|
function foo(string $str): ?int {
|
||||||
|
if (rand(0, 1) || (!($pos = strpos($str, "a")) && !($pos = strpos($str, "b")))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pos;
|
||||||
|
}'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3322,6 +3338,19 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
|||||||
}',
|
}',
|
||||||
'error_message' => 'TypeDoesNotContainType',
|
'error_message' => 'TypeDoesNotContainType',
|
||||||
],
|
],
|
||||||
|
'assignmentInBranchOfAnd' => [
|
||||||
|
'<?php
|
||||||
|
function foo(string $str): ?int {
|
||||||
|
$pos = 5;
|
||||||
|
|
||||||
|
if (rand(0, 1) && !($pos = $str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pos;
|
||||||
|
}',
|
||||||
|
'error_message' => 'InvalidReturnStatement',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user