mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #977 - improve handling of orred expressions
This commit is contained in:
parent
1d3e66c798
commit
334c463e08
@ -166,14 +166,28 @@ class BinaryOpChecker
|
||||
|
||||
$pre_assigned_var_ids = $context->assigned_var_ids;
|
||||
|
||||
if (ExpressionChecker::analyze($statements_checker, $stmt->left, $context) === false) {
|
||||
$pre_op_context = clone $context;
|
||||
$pre_op_context->parent_context = $context;
|
||||
|
||||
if (ExpressionChecker::analyze($statements_checker, $stmt->left, $pre_op_context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$new_referenced_var_ids = $context->referenced_var_ids;
|
||||
$context->referenced_var_ids = array_merge($pre_referenced_var_ids, $new_referenced_var_ids);
|
||||
foreach ($pre_op_context->vars_in_scope as $var_id => $type) {
|
||||
if (!isset($context->vars_in_scope[$var_id])) {
|
||||
$context->vars_in_scope[$var_id] = clone $type;
|
||||
} else {
|
||||
$context->vars_in_scope[$var_id] = Type::combineUnionTypes(
|
||||
$context->vars_in_scope[$var_id],
|
||||
$type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$new_assigned_var_ids = array_diff_key($context->assigned_var_ids, $pre_assigned_var_ids);
|
||||
$new_referenced_var_ids = $pre_op_context->referenced_var_ids;
|
||||
$pre_op_context->referenced_var_ids = array_merge($pre_referenced_var_ids, $new_referenced_var_ids);
|
||||
|
||||
$new_assigned_var_ids = array_diff_key($pre_op_context->assigned_var_ids, $pre_assigned_var_ids);
|
||||
|
||||
$new_referenced_var_ids = array_diff_key($new_referenced_var_ids, $new_assigned_var_ids);
|
||||
|
||||
@ -187,7 +201,7 @@ class BinaryOpChecker
|
||||
|
||||
$clauses_for_right_analysis = Algebra::simplifyCNF(
|
||||
array_merge(
|
||||
$context->clauses,
|
||||
$pre_op_context->clauses,
|
||||
$negated_left_clauses
|
||||
)
|
||||
);
|
||||
@ -200,7 +214,7 @@ class BinaryOpChecker
|
||||
// statements of the form if ($x === null || $x->foo())
|
||||
$op_vars_in_scope = Reconciler::reconcileKeyedTypes(
|
||||
$negated_type_assertions,
|
||||
$context->vars_in_scope,
|
||||
$pre_op_context->vars_in_scope,
|
||||
$changed_var_ids,
|
||||
$new_referenced_var_ids,
|
||||
$statements_checker,
|
||||
@ -208,11 +222,14 @@ class BinaryOpChecker
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
$op_context = clone $context;
|
||||
$op_context = clone $pre_op_context;
|
||||
$op_context->clauses = $clauses_for_right_analysis;
|
||||
$op_context->vars_in_scope = $op_vars_in_scope;
|
||||
|
||||
$op_context->removeReconciledClauses($changed_var_ids);
|
||||
if ($changed_var_ids) {
|
||||
$op_context->removeReconciledClauses($changed_var_ids);
|
||||
$context->removeReconciledClauses($changed_var_ids);
|
||||
}
|
||||
|
||||
if (ExpressionChecker::analyze($statements_checker, $stmt->right, $op_context) === false) {
|
||||
return false;
|
||||
@ -230,10 +247,10 @@ class BinaryOpChecker
|
||||
} elseif ($stmt->left instanceof PhpParser\Node\Expr\Assign) {
|
||||
$var_id = ExpressionChecker::getVarId($stmt->left->var, $context->self);
|
||||
|
||||
if ($var_id && isset($context->vars_in_scope[$var_id])) {
|
||||
if ($var_id && isset($pre_op_context->vars_in_scope[$var_id])) {
|
||||
$left_inferred_reconciled = Reconciler::reconcileTypes(
|
||||
'!falsy',
|
||||
$context->vars_in_scope[$var_id],
|
||||
$pre_op_context->vars_in_scope[$var_id],
|
||||
'',
|
||||
$statements_checker,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->left),
|
||||
|
@ -397,6 +397,50 @@ class IssetTest extends TestCase
|
||||
throw new \InvalidArgumentException();
|
||||
}'
|
||||
],
|
||||
'notIssetOneOrOther' => [
|
||||
'<?php
|
||||
$foo = [
|
||||
"one" => rand(0,1) ? new DateTime : null,
|
||||
"two" => rand(0,1) ? new DateTime : null,
|
||||
"three" => new DateTime
|
||||
];
|
||||
|
||||
if (!(isset($foo["one"]) || isset($foo["two"]))) {
|
||||
exit;
|
||||
}
|
||||
|
||||
echo $foo["one"]->format("Y");',
|
||||
'assertions' => [],
|
||||
'error_levels' => ['PossiblyNullReference'],
|
||||
],
|
||||
'notIssetOneOrOtherWithoutAssert' => [
|
||||
'<?php
|
||||
$foo = [
|
||||
"one" => rand(0,1) ? new DateTime : null,
|
||||
"two" => rand(0,1) ? new DateTime : null,
|
||||
"three" => new DateTime
|
||||
];
|
||||
|
||||
isset($foo["one"]) || isset($foo["two"]);
|
||||
|
||||
echo $foo["one"]->format("Y");',
|
||||
'assertions' => [],
|
||||
'error_levels' => ['PossiblyNullReference'],
|
||||
],
|
||||
'notIssetOneOrOtherWithAssert' => [
|
||||
'<?php
|
||||
$foo = [
|
||||
"one" => rand(0,1) ? new DateTime : null,
|
||||
"two" => rand(0,1) ? new DateTime : null,
|
||||
"three" => new DateTime
|
||||
];
|
||||
|
||||
assert(isset($foo["one"]) || isset($foo["two"]));
|
||||
|
||||
echo $foo["one"]->format("Y");',
|
||||
'assertions' => [],
|
||||
'error_levels' => ['PossiblyNullReference'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -142,19 +142,23 @@ class RedundantConditionTest extends TestCase
|
||||
'noRedundantConditionAfterFromDocblockRemoval' => [
|
||||
'<?php
|
||||
class A {
|
||||
public function foo(): void{}
|
||||
public function bar(): void{}
|
||||
public function foo(): bool {
|
||||
return (bool) rand(0, 1);
|
||||
}
|
||||
public function bar(): bool {
|
||||
return (bool) rand(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return A */
|
||||
function makeA() {
|
||||
return new A;
|
||||
return new A;
|
||||
}
|
||||
|
||||
$a = makeA();
|
||||
|
||||
if ($a === null) {
|
||||
exit;
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($a->foo() || $a->bar()) {}',
|
||||
|
@ -1029,6 +1029,19 @@ class TypeReconciliationTest extends TestCase
|
||||
if (!$data) {}
|
||||
if ($data) {}',
|
||||
],
|
||||
'reconcileWithInstanceof' => [
|
||||
'<?php
|
||||
class A {}
|
||||
class B extends A {
|
||||
public function b() : bool {
|
||||
return (bool) rand(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function bar(?A $a) : void {
|
||||
if (!$a || ($a instanceof B && $a->b())) {}
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user