mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Improve binary op || analysis for chain
This commit is contained in:
parent
559abb7b6a
commit
c0f6afbd87
@ -884,39 +884,13 @@ class IfAnalyzer
|
||||
&& $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
&& $if_scope->mic_drop_context
|
||||
) {
|
||||
$exprs = self::getDefinitelyEvaluatedOredExpressions($stmt->cond);
|
||||
|
||||
// if there was no assignment in the first expression it's safe to proceed
|
||||
$old_node_data = $statements_analyzer->node_data;
|
||||
$statements_analyzer->node_data = clone $old_node_data;
|
||||
|
||||
foreach ($exprs as $expr) {
|
||||
$fake_negated_expr = new PhpParser\Node\Expr\FuncCall(
|
||||
new PhpParser\Node\Name\FullyQualified('assert'),
|
||||
[new PhpParser\Node\Arg(
|
||||
new PhpParser\Node\Expr\BooleanNot($expr, $expr->getAttributes()),
|
||||
false,
|
||||
false,
|
||||
$expr->getAttributes()
|
||||
)],
|
||||
$expr->getAttributes()
|
||||
);
|
||||
|
||||
ExpressionAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$fake_negated_expr,
|
||||
$if_scope->mic_drop_context
|
||||
);
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data = $old_node_data;
|
||||
|
||||
foreach ($if_conditional_scope->cond_assigned_var_ids as $var_id => $_) {
|
||||
if (isset($if_scope->mic_drop_context->vars_in_scope[$var_id])) {
|
||||
$outer_context->vars_in_scope[$var_id]
|
||||
= clone $if_scope->mic_drop_context->vars_in_scope[$var_id];
|
||||
}
|
||||
}
|
||||
self::addConditionallyAssignedVarsToContext(
|
||||
$statements_analyzer,
|
||||
$stmt->cond,
|
||||
$if_scope->mic_drop_context,
|
||||
$outer_context,
|
||||
$if_conditional_scope->cond_assigned_var_ids
|
||||
);
|
||||
}
|
||||
|
||||
if ($if_scope->negated_types) {
|
||||
@ -1868,4 +1842,48 @@ class IfAnalyzer
|
||||
|
||||
return [$stmt];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, bool> $cond_assigned_var_ids
|
||||
*/
|
||||
public static function addConditionallyAssignedVarsToContext(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr $cond,
|
||||
Context $mic_drop_context,
|
||||
Context $outer_context,
|
||||
array $cond_assigned_var_ids
|
||||
) : void {
|
||||
$exprs = self::getDefinitelyEvaluatedOredExpressions($cond);
|
||||
|
||||
// if there was no assignment in the first expression it's safe to proceed
|
||||
$old_node_data = $statements_analyzer->node_data;
|
||||
$statements_analyzer->node_data = clone $old_node_data;
|
||||
|
||||
foreach ($exprs as $expr) {
|
||||
$fake_negated_expr = new PhpParser\Node\Expr\FuncCall(
|
||||
new PhpParser\Node\Name\FullyQualified('assert'),
|
||||
[new PhpParser\Node\Arg(
|
||||
new PhpParser\Node\Expr\BooleanNot($expr, $expr->getAttributes()),
|
||||
false,
|
||||
false,
|
||||
$expr->getAttributes()
|
||||
)],
|
||||
$expr->getAttributes()
|
||||
);
|
||||
|
||||
ExpressionAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$fake_negated_expr,
|
||||
$mic_drop_context
|
||||
);
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data = $old_node_data;
|
||||
|
||||
foreach ($cond_assigned_var_ids as $var_id => $_) {
|
||||
if (isset($mic_drop_context->vars_in_scope[$var_id])) {
|
||||
$outer_context->vars_in_scope[$var_id] = clone $mic_drop_context->vars_in_scope[$var_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,9 +47,11 @@ class OrAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$mic_drop_context = null;
|
||||
|
||||
if (!$stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
&& !($stmt->left instanceof PhpParser\Node\Expr\BooleanNot
|
||||
&& $stmt->left->expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd)
|
||||
|| !$stmt->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
|| !$stmt->left->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
) {
|
||||
$if_scope = new \Psalm\Internal\Scope\IfScope();
|
||||
|
||||
@ -66,6 +68,11 @@ class OrAnalyzer
|
||||
$left_context = $if_conditional_scope->if_context;
|
||||
|
||||
$left_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
|
||||
$left_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids;
|
||||
|
||||
if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
|
||||
$mic_drop_context = clone $context;
|
||||
}
|
||||
} catch (\Psalm\Exception\ScopeAnalysisException $e) {
|
||||
return false;
|
||||
}
|
||||
@ -75,6 +82,8 @@ class OrAnalyzer
|
||||
|
||||
$pre_assigned_var_ids = $context->assigned_var_ids;
|
||||
|
||||
$mic_drop_context = clone $context;
|
||||
|
||||
$left_context = clone $context;
|
||||
$left_context->parent_context = $context;
|
||||
$left_context->if_context = null;
|
||||
@ -176,6 +185,19 @@ class OrAnalyzer
|
||||
|
||||
$right_context = clone $context;
|
||||
|
||||
if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
&& $left_assigned_var_ids
|
||||
&& $mic_drop_context
|
||||
) {
|
||||
IfAnalyzer::addConditionallyAssignedVarsToContext(
|
||||
$statements_analyzer,
|
||||
$stmt->left,
|
||||
$mic_drop_context,
|
||||
$right_context,
|
||||
$left_assigned_var_ids
|
||||
);
|
||||
}
|
||||
|
||||
if ($negated_type_assertions) {
|
||||
// while in an or, we allow scope to boil over to support
|
||||
// statements of the form if ($x === null || $x->foo())
|
||||
|
@ -1935,7 +1935,7 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
||||
'$a' => 'bool',
|
||||
]
|
||||
],
|
||||
'SKIPPED-assertVarRedefinedInOpWithOr' => [
|
||||
'assertVarRedefinedInOpWithOr' => [
|
||||
'<?php
|
||||
class O {
|
||||
public function foo() : bool { return true; }
|
||||
|
Loading…
Reference in New Issue
Block a user