1
0
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:
Matthew Brown 2018-09-10 00:13:59 -04:00
parent 1d3e66c798
commit 334c463e08
4 changed files with 92 additions and 14 deletions

View File

@ -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),

View File

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

View File

@ -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()) {}',

View File

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