mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 12:55:26 +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
|
&& $stmt->cond instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||||
&& $if_scope->mic_drop_context
|
&& $if_scope->mic_drop_context
|
||||||
) {
|
) {
|
||||||
$exprs = self::getDefinitelyEvaluatedOredExpressions($stmt->cond);
|
self::addConditionallyAssignedVarsToContext(
|
||||||
|
$statements_analyzer,
|
||||||
// if there was no assignment in the first expression it's safe to proceed
|
$stmt->cond,
|
||||||
$old_node_data = $statements_analyzer->node_data;
|
$if_scope->mic_drop_context,
|
||||||
$statements_analyzer->node_data = clone $old_node_data;
|
$outer_context,
|
||||||
|
$if_conditional_scope->cond_assigned_var_ids
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($if_scope->negated_types) {
|
if ($if_scope->negated_types) {
|
||||||
@ -1868,4 +1842,48 @@ class IfAnalyzer
|
|||||||
|
|
||||||
return [$stmt];
|
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();
|
$codebase = $statements_analyzer->getCodebase();
|
||||||
|
|
||||||
|
$mic_drop_context = null;
|
||||||
|
|
||||||
if (!$stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
if (!$stmt->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||||
&& !($stmt->left instanceof PhpParser\Node\Expr\BooleanNot
|
|| !$stmt->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||||
&& $stmt->left->expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd)
|
|| !$stmt->left->left->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||||
) {
|
) {
|
||||||
$if_scope = new \Psalm\Internal\Scope\IfScope();
|
$if_scope = new \Psalm\Internal\Scope\IfScope();
|
||||||
|
|
||||||
@ -66,6 +68,11 @@ class OrAnalyzer
|
|||||||
$left_context = $if_conditional_scope->if_context;
|
$left_context = $if_conditional_scope->if_context;
|
||||||
|
|
||||||
$left_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
|
$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) {
|
} catch (\Psalm\Exception\ScopeAnalysisException $e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -75,6 +82,8 @@ class OrAnalyzer
|
|||||||
|
|
||||||
$pre_assigned_var_ids = $context->assigned_var_ids;
|
$pre_assigned_var_ids = $context->assigned_var_ids;
|
||||||
|
|
||||||
|
$mic_drop_context = clone $context;
|
||||||
|
|
||||||
$left_context = clone $context;
|
$left_context = clone $context;
|
||||||
$left_context->parent_context = $context;
|
$left_context->parent_context = $context;
|
||||||
$left_context->if_context = null;
|
$left_context->if_context = null;
|
||||||
@ -176,6 +185,19 @@ class OrAnalyzer
|
|||||||
|
|
||||||
$right_context = clone $context;
|
$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) {
|
if ($negated_type_assertions) {
|
||||||
// while in an or, we allow scope to boil over to support
|
// while in an or, we allow scope to boil over to support
|
||||||
// statements of the form if ($x === null || $x->foo())
|
// statements of the form if ($x === null || $x->foo())
|
||||||
|
@ -1935,7 +1935,7 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
|||||||
'$a' => 'bool',
|
'$a' => 'bool',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'SKIPPED-assertVarRedefinedInOpWithOr' => [
|
'assertVarRedefinedInOpWithOr' => [
|
||||||
'<?php
|
'<?php
|
||||||
class O {
|
class O {
|
||||||
public function foo() : bool { return true; }
|
public function foo() : bool { return true; }
|
||||||
|
Loading…
Reference in New Issue
Block a user