mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Make clause fully immutable
This commit is contained in:
parent
b09309aa92
commit
6d43a6696c
@ -158,7 +158,7 @@ class Context
|
||||
/**
|
||||
* A list of hashed clauses that have already been factored in
|
||||
*
|
||||
* @var list<string>
|
||||
* @var list<string|int>
|
||||
*/
|
||||
public $reconciled_expression_clauses = [];
|
||||
|
||||
|
@ -58,6 +58,8 @@ class AlgebraAnalyzer
|
||||
$hash = $formula2_clause->hash;
|
||||
|
||||
if (!$formula2_clause->generated
|
||||
&& !$formula2_clause->wedge
|
||||
&& $formula2_clause->reconcilable
|
||||
&& (isset($formula1_hashes[$hash]) || isset($formula2_hashes[$hash]))
|
||||
&& !array_intersect_key($new_assigned_var_ids, $formula2_clause->possibilities)
|
||||
) {
|
||||
|
@ -52,8 +52,11 @@ class DoAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$cond_id = \spl_object_id($stmt->cond);
|
||||
|
||||
$while_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt->cond),
|
||||
$cond_id,
|
||||
$cond_id,
|
||||
$stmt->cond,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -83,7 +86,7 @@ class DoAnalyzer
|
||||
);
|
||||
|
||||
if (!$while_clauses) {
|
||||
$while_clauses = [new Clause([], true)];
|
||||
$while_clauses = [new Clause([], $cond_id, $cond_id, true)];
|
||||
}
|
||||
|
||||
LoopAnalyzer::analyze(
|
||||
|
@ -107,8 +107,11 @@ class IfAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$cond_object_id = \spl_object_id($stmt->cond);
|
||||
|
||||
$if_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt->cond),
|
||||
$cond_object_id,
|
||||
$cond_object_id,
|
||||
$stmt->cond,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -124,7 +127,7 @@ class IfAnalyzer
|
||||
/**
|
||||
* @return Clause
|
||||
*/
|
||||
function (Clause $c) use ($mixed_var_ids) {
|
||||
function (Clause $c) use ($mixed_var_ids, $cond_object_id) {
|
||||
$keys = array_keys($c->possibilities);
|
||||
|
||||
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
||||
@ -132,7 +135,7 @@ class IfAnalyzer
|
||||
foreach ($keys as $key) {
|
||||
foreach ($mixed_var_ids as $mixed_var_id) {
|
||||
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
||||
return new Clause([], true);
|
||||
return new Clause([], $cond_object_id, $cond_object_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1086,8 +1089,11 @@ class IfAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$elseif_cond_id = \spl_object_id($elseif->cond);
|
||||
|
||||
$elseif_clauses = Algebra::getFormula(
|
||||
\spl_object_id($elseif->cond),
|
||||
$elseif_cond_id,
|
||||
$elseif_cond_id,
|
||||
$elseif->cond,
|
||||
$else_context->self,
|
||||
$statements_analyzer,
|
||||
@ -1098,7 +1104,7 @@ class IfAnalyzer
|
||||
/**
|
||||
* @return Clause
|
||||
*/
|
||||
function (Clause $c) use ($mixed_var_ids) {
|
||||
function (Clause $c) use ($mixed_var_ids, $elseif_cond_id) {
|
||||
$keys = array_keys($c->possibilities);
|
||||
|
||||
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
||||
@ -1106,7 +1112,7 @@ class IfAnalyzer
|
||||
foreach ($keys as $key) {
|
||||
foreach ($mixed_var_ids as $mixed_var_id) {
|
||||
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
||||
return new Clause([], true);
|
||||
return new Clause([], $elseif_cond_id, $elseif_cond_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1120,13 +1126,13 @@ class IfAnalyzer
|
||||
/**
|
||||
* @return Clause
|
||||
*/
|
||||
function (Clause $c) use ($cond_assigned_var_ids) {
|
||||
function (Clause $c) use ($cond_assigned_var_ids, $elseif_cond_id) {
|
||||
$keys = array_keys($c->possibilities);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
foreach ($cond_assigned_var_ids as $conditional_assigned_var_id => $_) {
|
||||
if (preg_match('/^' . preg_quote($conditional_assigned_var_id, '/') . '(\[|-|$)/', $key)) {
|
||||
return new Clause([], true);
|
||||
return new Clause([], $elseif_cond_id, $elseif_cond_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1403,7 +1409,8 @@ class IfAnalyzer
|
||||
if ($reasonable_clause_count && $reasonable_clause_count < 20000 && $elseif_clauses) {
|
||||
$if_scope->reasonable_clauses = Algebra::combineOredClauses(
|
||||
$if_scope->reasonable_clauses,
|
||||
$elseif_clauses
|
||||
$elseif_clauses,
|
||||
\spl_object_id($elseif->cond)
|
||||
);
|
||||
} else {
|
||||
$if_scope->reasonable_clauses = [];
|
||||
|
@ -70,8 +70,11 @@ class LoopAnalyzer
|
||||
|
||||
if ($pre_conditions) {
|
||||
foreach ($pre_conditions as $i => $pre_condition) {
|
||||
$pre_condition_id = \spl_object_id($pre_condition);
|
||||
|
||||
$pre_condition_clauses[$i] = Algebra::getFormula(
|
||||
\spl_object_id($pre_condition),
|
||||
$pre_condition_id,
|
||||
$pre_condition_id,
|
||||
$pre_condition,
|
||||
$loop_scope->loop_context->self,
|
||||
$statements_analyzer,
|
||||
|
@ -283,8 +283,10 @@ class SwitchCaseAnalyzer
|
||||
$case_clauses = [];
|
||||
|
||||
if ($case_equality_expr) {
|
||||
$case_equality_expr_id = \spl_object_id($case_equality_expr);
|
||||
$case_clauses = Algebra::getFormula(
|
||||
\spl_object_id($case_equality_expr),
|
||||
$case_equality_expr_id,
|
||||
$case_equality_expr_id,
|
||||
$case_equality_expr,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -379,9 +381,12 @@ class SwitchCaseAnalyzer
|
||||
try {
|
||||
$negated_case_clauses = Algebra::negateFormula($case_clauses);
|
||||
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
||||
$case_equality_expr_id = \spl_object_id($case_equality_expr);
|
||||
|
||||
try {
|
||||
$negated_case_clauses = Algebra::getFormula(
|
||||
\spl_object_id($case_equality_expr),
|
||||
$case_equality_expr_id,
|
||||
$case_equality_expr_id,
|
||||
new PhpParser\Node\Expr\BooleanNot($case_equality_expr),
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
|
@ -60,8 +60,11 @@ class AndAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$left_cond_id = \spl_object_id($stmt->left);
|
||||
|
||||
$left_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt->left),
|
||||
$left_cond_id,
|
||||
$left_cond_id,
|
||||
$stmt->left,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -110,7 +113,7 @@ class AndAnalyzer
|
||||
|
||||
$left_type_assertions = Algebra::getTruthsFromFormula(
|
||||
$simplified_clauses,
|
||||
\spl_object_id($stmt->left),
|
||||
$left_cond_id,
|
||||
$left_referenced_var_ids,
|
||||
$active_left_assertions
|
||||
);
|
||||
|
@ -34,8 +34,11 @@ class CoalesceAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$stmt_id = \spl_object_id($stmt);
|
||||
|
||||
$if_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt),
|
||||
$stmt_id,
|
||||
$stmt_id,
|
||||
$stmt,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -61,7 +64,7 @@ class CoalesceAnalyzer
|
||||
/**
|
||||
* @return \Psalm\Internal\Clause
|
||||
*/
|
||||
function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) {
|
||||
function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $stmt_id) {
|
||||
$keys = array_keys($c->possibilities);
|
||||
|
||||
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
||||
@ -69,7 +72,7 @@ class CoalesceAnalyzer
|
||||
foreach ($keys as $key) {
|
||||
foreach ($mixed_var_ids as $mixed_var_id) {
|
||||
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
||||
return new \Psalm\Internal\Clause([], true);
|
||||
return new \Psalm\Internal\Clause([], $stmt_id, $stmt_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +111,11 @@ class OrAnalyzer
|
||||
$left_referenced_var_ids = array_diff_key($left_referenced_var_ids, $left_assigned_var_ids);
|
||||
}
|
||||
|
||||
$left_cond_id = \spl_object_id($stmt->left);
|
||||
|
||||
$left_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt->left),
|
||||
$left_cond_id,
|
||||
$left_cond_id,
|
||||
$stmt->left,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -124,7 +127,8 @@ class OrAnalyzer
|
||||
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
||||
try {
|
||||
$negated_left_clauses = Algebra::getFormula(
|
||||
\spl_object_id($stmt->left),
|
||||
$left_cond_id,
|
||||
$left_cond_id,
|
||||
new PhpParser\Node\Expr\BooleanNot($stmt->left),
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -167,7 +171,7 @@ class OrAnalyzer
|
||||
|
||||
$negated_type_assertions = Algebra::getTruthsFromFormula(
|
||||
$clauses_for_right_analysis,
|
||||
\spl_object_id($stmt->left),
|
||||
$left_cond_id,
|
||||
$left_referenced_var_ids,
|
||||
$active_negated_type_assertions
|
||||
);
|
||||
|
@ -805,8 +805,11 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
PhpParser\Node\Arg $first_arg,
|
||||
Context $context
|
||||
) : void {
|
||||
$first_arg_value_id = \spl_object_id($first_arg->value);
|
||||
|
||||
$assert_clauses = \Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($first_arg->value),
|
||||
$first_arg_value_id,
|
||||
$first_arg_value_id,
|
||||
$first_arg->value,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
|
@ -703,7 +703,8 @@ class CallAnalyzer
|
||||
);
|
||||
|
||||
$assert_clauses = \Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($conditional),
|
||||
\mt_rand(0, 1000000),
|
||||
\mt_rand(0, 1000000),
|
||||
$conditional,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -711,6 +712,7 @@ class CallAnalyzer
|
||||
);
|
||||
} else {
|
||||
$assert_clauses = \Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($arg_value),
|
||||
\spl_object_id($arg_value),
|
||||
$arg_value,
|
||||
$context->self,
|
||||
@ -731,6 +733,7 @@ class CallAnalyzer
|
||||
} elseif ($arg_value && $assertion->rule === [['falsy']]) {
|
||||
$assert_clauses = \Psalm\Type\Algebra::negateFormula(
|
||||
\Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($arg_value),
|
||||
\spl_object_id($arg_value),
|
||||
$arg_value,
|
||||
$context->self,
|
||||
|
@ -53,8 +53,11 @@ class TernaryAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$cond_id = \spl_object_id($stmt->cond);
|
||||
|
||||
$if_clauses = \Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($stmt->cond),
|
||||
$cond_id,
|
||||
$cond_id,
|
||||
$stmt->cond,
|
||||
$context->self,
|
||||
$statements_analyzer,
|
||||
@ -80,7 +83,7 @@ class TernaryAnalyzer
|
||||
/**
|
||||
* @return \Psalm\Internal\Clause
|
||||
*/
|
||||
function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) {
|
||||
function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $cond_id) {
|
||||
$keys = array_keys($c->possibilities);
|
||||
|
||||
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
||||
@ -88,7 +91,7 @@ class TernaryAnalyzer
|
||||
foreach ($keys as $key) {
|
||||
foreach ($mixed_var_ids as $mixed_var_id) {
|
||||
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
||||
return new \Psalm\Internal\Clause([], true);
|
||||
return new \Psalm\Internal\Clause([], $cond_id, $cond_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,7 +131,7 @@ class TernaryAnalyzer
|
||||
|
||||
$reconcilable_if_types = Algebra::getTruthsFromFormula(
|
||||
$ternary_clauses,
|
||||
\spl_object_id($stmt->cond),
|
||||
$cond_id,
|
||||
$cond_referenced_var_ids,
|
||||
$active_if_types
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ use function json_encode;
|
||||
use function ksort;
|
||||
use function md5;
|
||||
use function sort;
|
||||
use function mt_rand;
|
||||
use function array_unique;
|
||||
use function strpos;
|
||||
|
||||
@ -22,7 +21,10 @@ use function strpos;
|
||||
*/
|
||||
class Clause
|
||||
{
|
||||
/** @var ?int */
|
||||
/** @var int */
|
||||
public $creating_conditional_id;
|
||||
|
||||
/** @var int */
|
||||
public $creating_object_id;
|
||||
|
||||
/**
|
||||
@ -70,7 +72,7 @@ class Clause
|
||||
/** @var array<string, bool> */
|
||||
public $redefined_vars = [];
|
||||
|
||||
/** @var string */
|
||||
/** @var string|int */
|
||||
public $hash;
|
||||
|
||||
/**
|
||||
@ -82,27 +84,28 @@ class Clause
|
||||
*/
|
||||
public function __construct(
|
||||
array $possibilities,
|
||||
int $creating_conditional_id,
|
||||
int $creating_object_id,
|
||||
$wedge = false,
|
||||
$reconcilable = true,
|
||||
$generated = false,
|
||||
array $redefined_vars = [],
|
||||
?int $creating_object_id = null
|
||||
array $redefined_vars = []
|
||||
) {
|
||||
$this->possibilities = $possibilities;
|
||||
$this->wedge = $wedge;
|
||||
$this->reconcilable = $reconcilable;
|
||||
$this->generated = $generated;
|
||||
$this->redefined_vars = $redefined_vars;
|
||||
$this->creating_conditional_id = $creating_conditional_id;
|
||||
$this->creating_object_id = $creating_object_id;
|
||||
|
||||
if ($wedge || !$reconcilable) {
|
||||
/** @psalm-suppress ImpureFunctionCall as this has to be globally unique */
|
||||
$this->hash = (string) mt_rand(0, 1000000);
|
||||
$this->hash = ($wedge ? 'w' : '') . $creating_object_id;
|
||||
} else {
|
||||
ksort($possibilities);
|
||||
|
||||
foreach ($possibilities as &$possible_types) {
|
||||
sort($possible_types);
|
||||
foreach ($possibilities as $i => $_) {
|
||||
sort($possibilities[$i]);
|
||||
}
|
||||
|
||||
$this->hash = md5((string) json_encode($possibilities));
|
||||
@ -180,11 +183,12 @@ class Clause
|
||||
|
||||
return new self(
|
||||
$possibilities,
|
||||
$this->creating_conditional_id,
|
||||
$this->creating_object_id,
|
||||
$this->wedge,
|
||||
$this->reconcilable,
|
||||
$this->generated,
|
||||
$this->redefined_vars,
|
||||
$this->creating_object_id
|
||||
$this->redefined_vars
|
||||
);
|
||||
}
|
||||
|
||||
@ -195,11 +199,12 @@ class Clause
|
||||
|
||||
return new self(
|
||||
$possibilities,
|
||||
$this->creating_conditional_id,
|
||||
$this->creating_object_id,
|
||||
$this->wedge,
|
||||
$this->reconcilable,
|
||||
$this->generated,
|
||||
$this->redefined_vars,
|
||||
$this->creating_object_id
|
||||
$this->redefined_vars
|
||||
);
|
||||
}
|
||||
|
||||
@ -213,11 +218,12 @@ class Clause
|
||||
|
||||
return new self(
|
||||
$possibilities,
|
||||
$this->creating_conditional_id,
|
||||
$this->creating_object_id,
|
||||
$this->wedge,
|
||||
$this->reconcilable,
|
||||
$this->generated,
|
||||
$this->redefined_vars,
|
||||
$this->creating_object_id
|
||||
$this->redefined_vars
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2164,8 +2164,11 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
break;
|
||||
}
|
||||
|
||||
$cond_id = \spl_object_id($function_stmt->cond);
|
||||
|
||||
$if_clauses = \Psalm\Type\Algebra::getFormula(
|
||||
\spl_object_id($function_stmt->cond),
|
||||
$cond_id,
|
||||
$cond_id,
|
||||
$function_stmt->cond,
|
||||
$this->fq_classlike_names
|
||||
? $this->fq_classlike_names[count($this->fq_classlike_names) - 1]
|
||||
|
@ -91,7 +91,8 @@ class Algebra
|
||||
* @return array<int, Clause>
|
||||
*/
|
||||
public static function getFormula(
|
||||
int $object_id,
|
||||
int $conditional_object_id,
|
||||
int $creating_object_id,
|
||||
PhpParser\Node\Expr $conditional,
|
||||
$this_class_name,
|
||||
FileSource $source,
|
||||
@ -103,7 +104,8 @@ class Algebra
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
|
||||
) {
|
||||
$left_assertions = self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->left),
|
||||
$conditional->left,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -113,7 +115,8 @@ class Algebra
|
||||
);
|
||||
|
||||
$right_assertions = self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->right),
|
||||
$conditional->right,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -131,10 +134,9 @@ class Algebra
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr ||
|
||||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr
|
||||
) {
|
||||
// at the moment we only support formulae in CNF
|
||||
|
||||
$left_clauses = self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->left),
|
||||
$conditional->left,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -144,7 +146,8 @@ class Algebra
|
||||
);
|
||||
|
||||
$right_clauses = self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->right),
|
||||
$conditional->right,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -153,7 +156,7 @@ class Algebra
|
||||
$cache
|
||||
);
|
||||
|
||||
return self::combineOredClauses($left_clauses, $right_clauses);
|
||||
return self::combineOredClauses($left_clauses, $right_clauses, $conditional_object_id);
|
||||
}
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
|
||||
@ -171,7 +174,8 @@ class Algebra
|
||||
);
|
||||
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
$conditional_object_id,
|
||||
$and_expr,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -220,6 +224,8 @@ class Algebra
|
||||
foreach ($anded_types as $orred_types) {
|
||||
$clauses[] = new Clause(
|
||||
[$var => $orred_types],
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->expr),
|
||||
false,
|
||||
true,
|
||||
$orred_types[0][0] === '='
|
||||
@ -227,8 +233,7 @@ class Algebra
|
||||
|| (strlen($orred_types[0]) > 1
|
||||
&& ($orred_types[0][1] === '='
|
||||
|| $orred_types[0][1] === '~')),
|
||||
$redefined ? [$var => true] : [],
|
||||
$object_id
|
||||
$redefined ? [$var => true] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -251,7 +256,8 @@ class Algebra
|
||||
);
|
||||
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->expr),
|
||||
$and_expr,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -263,7 +269,8 @@ class Algebra
|
||||
|
||||
return self::negateFormula(
|
||||
self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->expr),
|
||||
$conditional->expr,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -286,7 +293,8 @@ class Algebra
|
||||
$inside_negation = !$inside_negation;
|
||||
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->left),
|
||||
$conditional->left,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -303,7 +311,8 @@ class Algebra
|
||||
$inside_negation = !$inside_negation;
|
||||
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->right),
|
||||
$conditional->right,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -318,7 +327,8 @@ class Algebra
|
||||
|| $conditional->left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr)
|
||||
) {
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->left),
|
||||
$conditional->left,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -333,7 +343,8 @@ class Algebra
|
||||
|| $conditional->right instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr)
|
||||
) {
|
||||
return self::getFormula(
|
||||
$object_id,
|
||||
$conditional_object_id,
|
||||
\spl_object_id($conditional->right),
|
||||
$conditional->right,
|
||||
$this_class_name,
|
||||
$source,
|
||||
@ -380,6 +391,8 @@ class Algebra
|
||||
foreach ($anded_types as $orred_types) {
|
||||
$clauses[] = new Clause(
|
||||
[$var => $orred_types],
|
||||
$conditional_object_id,
|
||||
$creating_object_id,
|
||||
false,
|
||||
true,
|
||||
$orred_types[0][0] === '='
|
||||
@ -387,8 +400,7 @@ class Algebra
|
||||
|| (strlen($orred_types[0]) > 1
|
||||
&& ($orred_types[0][1] === '='
|
||||
|| $orred_types[0][1] === '~')),
|
||||
$redefined ? [$var => true] : [],
|
||||
$object_id
|
||||
$redefined ? [$var => true] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -396,7 +408,7 @@ class Algebra
|
||||
return $clauses;
|
||||
}
|
||||
|
||||
return [new Clause([], true)];
|
||||
return [new Clause([], $conditional_object_id, $creating_object_id, true)];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -527,7 +539,7 @@ class Algebra
|
||||
*/
|
||||
public static function getTruthsFromFormula(
|
||||
array $clauses,
|
||||
?int $creating_object_id = null,
|
||||
?int $creating_conditional_id = null,
|
||||
array &$cond_referenced_var_ids = [],
|
||||
array &$active_truths = []
|
||||
) {
|
||||
@ -554,7 +566,7 @@ class Algebra
|
||||
$truths[$var] = [[$possible_type]];
|
||||
}
|
||||
|
||||
if ($creating_object_id && $creating_object_id === $clause->creating_object_id) {
|
||||
if ($creating_conditional_id && $creating_conditional_id === $clause->creating_conditional_id) {
|
||||
if (!isset($active_truths[$var])) {
|
||||
$active_truths[$var] = [];
|
||||
}
|
||||
@ -585,7 +597,7 @@ class Algebra
|
||||
/** @var array<int, string> $things_that_can_be_said */
|
||||
$truths[$var] = [$things_that_can_be_said];
|
||||
|
||||
if ($creating_object_id && $creating_object_id === $clause->creating_object_id) {
|
||||
if ($creating_conditional_id && $creating_conditional_id === $clause->creating_conditional_id) {
|
||||
$active_truths[$var] = [$things_that_can_be_said];
|
||||
}
|
||||
}
|
||||
@ -620,10 +632,7 @@ class Algebra
|
||||
foreach ($impossible_types as $impossible_type) {
|
||||
$seed_clause = new Clause(
|
||||
[$var => [$impossible_type]],
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
[],
|
||||
$clause->creating_conditional_id,
|
||||
$clause->creating_object_id
|
||||
);
|
||||
|
||||
@ -660,13 +669,14 @@ class Algebra
|
||||
|
||||
$new_clause = new Clause(
|
||||
$new_clause_possibilities,
|
||||
$clause->creating_conditional_id === $grouped_clause->creating_conditional_id
|
||||
? $clause->creating_conditional_id
|
||||
: $grouped_clause->creating_conditional_id,
|
||||
$clause->creating_object_id,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
[],
|
||||
$clause->creating_object_id === $grouped_clause->creating_object_id
|
||||
? $clause->creating_object_id
|
||||
: null
|
||||
[]
|
||||
);
|
||||
|
||||
$new_clauses[] = $new_clause;
|
||||
@ -694,7 +704,7 @@ class Algebra
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function combineOredClauses(array $left_clauses, array $right_clauses)
|
||||
public static function combineOredClauses(array $left_clauses, array $right_clauses, int $conditional_object_id)
|
||||
{
|
||||
$clauses = [];
|
||||
|
||||
@ -709,7 +719,7 @@ class Algebra
|
||||
}
|
||||
|
||||
if ($all_wedges) {
|
||||
return [new Clause([], true)];
|
||||
return [new Clause([], $conditional_object_id, $conditional_object_id, true)];
|
||||
}
|
||||
|
||||
foreach ($left_clauses as $left_clause) {
|
||||
@ -768,26 +778,27 @@ class Algebra
|
||||
}
|
||||
}
|
||||
|
||||
$creating_object_id = $right_clause->creating_object_id === $left_clause->creating_object_id
|
||||
? $right_clause->creating_object_id
|
||||
: null;
|
||||
$creating_conditional_id = $right_clause->creating_conditional_id === $left_clause->creating_conditional_id
|
||||
? $right_clause->creating_conditional_id
|
||||
: $conditional_object_id;
|
||||
|
||||
$clauses[] = new Clause(
|
||||
$possibilities,
|
||||
$creating_conditional_id,
|
||||
$creating_conditional_id,
|
||||
false,
|
||||
$can_reconcile,
|
||||
$right_clause->generated
|
||||
|| $left_clause->generated
|
||||
|| count($left_clauses) > 1
|
||||
|| count($right_clauses) > 1,
|
||||
[],
|
||||
$creating_object_id
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($has_wedge) {
|
||||
$clauses[] = new Clause([], true);
|
||||
$clauses[] = new Clause([], $conditional_object_id, $conditional_object_id, true);
|
||||
}
|
||||
|
||||
return $clauses;
|
||||
@ -811,13 +822,12 @@ class Algebra
|
||||
* @param array<int, Clause> $clauses
|
||||
*
|
||||
* @return non-empty-list<Clause>
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function negateFormula(array $clauses)
|
||||
{
|
||||
if (!$clauses) {
|
||||
return [new Clause([], true)];
|
||||
$cond_id = \mt_rand(0, 100000000);
|
||||
return [new Clause([], $cond_id, $cond_id, true)];
|
||||
}
|
||||
|
||||
$clauses_with_impossibilities = [];
|
||||
@ -831,13 +841,15 @@ class Algebra
|
||||
$impossible_clauses = self::groupImpossibilities($clauses_with_impossibilities);
|
||||
|
||||
if (!$impossible_clauses) {
|
||||
return [new Clause([], true)];
|
||||
$cond_id = \mt_rand(0, 100000000);
|
||||
return [new Clause([], $cond_id, $cond_id, true)];
|
||||
}
|
||||
|
||||
$negated = self::simplifyCNF($impossible_clauses);
|
||||
|
||||
if (!$negated) {
|
||||
return [new Clause([], true)];
|
||||
$cond_id = \mt_rand(0, 100000000);
|
||||
return [new Clause([], $cond_id, $cond_id, true)];
|
||||
}
|
||||
|
||||
return $negated;
|
||||
|
@ -21,7 +21,7 @@ class AlgebraTest extends TestCase
|
||||
public function testNegateFormula()
|
||||
{
|
||||
$formula = [
|
||||
new Clause(['$a' => ['!falsy']]),
|
||||
new Clause(['$a' => ['!falsy']], 1, 1),
|
||||
];
|
||||
|
||||
$negated_formula = Algebra::negateFormula($formula);
|
||||
@ -30,7 +30,7 @@ class AlgebraTest extends TestCase
|
||||
$this->assertSame(['$a' => ['falsy']], $negated_formula[0]->possibilities);
|
||||
|
||||
$formula = [
|
||||
new Clause(['$a' => ['!falsy'], '$b' => ['!falsy']]),
|
||||
new Clause(['$a' => ['!falsy'], '$b' => ['!falsy']], 1, 1),
|
||||
];
|
||||
|
||||
$negated_formula = Algebra::negateFormula($formula);
|
||||
@ -40,8 +40,8 @@ class AlgebraTest extends TestCase
|
||||
$this->assertSame(['$b' => ['falsy']], $negated_formula[1]->possibilities);
|
||||
|
||||
$formula = [
|
||||
new Clause(['$a' => ['!falsy']]),
|
||||
new Clause(['$b' => ['!falsy']]),
|
||||
new Clause(['$a' => ['!falsy']], 1, 1),
|
||||
new Clause(['$b' => ['!falsy']], 1, 2),
|
||||
];
|
||||
|
||||
$negated_formula = Algebra::negateFormula($formula);
|
||||
@ -50,7 +50,7 @@ class AlgebraTest extends TestCase
|
||||
$this->assertSame(['$b' => ['falsy'], '$a' => ['falsy']], $negated_formula[0]->possibilities);
|
||||
|
||||
$formula = [
|
||||
new Clause(['$a' => ['int', 'string'], '$b' => ['!falsy']]),
|
||||
new Clause(['$a' => ['int', 'string'], '$b' => ['!falsy']], 1, 1),
|
||||
];
|
||||
|
||||
$negated_formula = Algebra::negateFormula($formula);
|
||||
@ -84,6 +84,7 @@ class AlgebraTest extends TestCase
|
||||
$statements_analyzer = new StatementsAnalyzer($file_analyzer, new \Psalm\Internal\Provider\NodeDataProvider());
|
||||
|
||||
$dnf_clauses = Algebra::getFormula(
|
||||
\spl_object_id($dnf_stmt->expr),
|
||||
\spl_object_id($dnf_stmt->expr),
|
||||
$dnf_stmt->expr,
|
||||
null,
|
||||
@ -107,12 +108,16 @@ class AlgebraTest extends TestCase
|
||||
[
|
||||
'$a' => ['!falsy'],
|
||||
'$b' => ['!falsy'],
|
||||
]
|
||||
],
|
||||
1,
|
||||
1
|
||||
))->contains(
|
||||
new Clause(
|
||||
[
|
||||
'$a' => ['!falsy'],
|
||||
]
|
||||
],
|
||||
1,
|
||||
1
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -121,13 +126,17 @@ class AlgebraTest extends TestCase
|
||||
(new Clause(
|
||||
[
|
||||
'$a' => ['!falsy'],
|
||||
]
|
||||
],
|
||||
1,
|
||||
1
|
||||
))->contains(
|
||||
new Clause(
|
||||
[
|
||||
'$a' => ['!falsy'],
|
||||
'$b' => ['!falsy'],
|
||||
]
|
||||
],
|
||||
1,
|
||||
1
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -139,8 +148,8 @@ class AlgebraTest extends TestCase
|
||||
public function testSimplifyCNF()
|
||||
{
|
||||
$formula = [
|
||||
new Clause(['$a' => ['!falsy']]),
|
||||
new Clause(['$a' => ['falsy'], '$b' => ['falsy']]),
|
||||
new Clause(['$a' => ['!falsy']], 1, 1),
|
||||
new Clause(['$a' => ['falsy'], '$b' => ['falsy']], 1, 2),
|
||||
];
|
||||
|
||||
$simplified_formula = Algebra::simplifyCNF($formula);
|
||||
@ -156,22 +165,24 @@ class AlgebraTest extends TestCase
|
||||
[
|
||||
'$a' => ['=array']
|
||||
],
|
||||
1,
|
||||
2,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
[],
|
||||
5377
|
||||
[]
|
||||
))->calculateNegation();
|
||||
|
||||
$clause2 = (new \Psalm\Internal\Clause(
|
||||
[
|
||||
'$b' => ['isset']
|
||||
],
|
||||
1,
|
||||
2,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
[],
|
||||
5377
|
||||
[]
|
||||
))->calculateNegation();
|
||||
|
||||
$result_clauses = Algebra::groupImpossibilities([$clause1, $clause2]);
|
||||
|
@ -2171,6 +2171,10 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'manyNestedWedgeAssertions' => [
|
||||
'<?php
|
||||
if (rand(0, 1) && rand(0, 1)) {}'
|
||||
],
|
||||
'assertionAfterAssertionInsideBooleanNot' => [
|
||||
'<?php
|
||||
class A {}
|
||||
|
@ -1027,6 +1027,12 @@ class TypeAlgebraTest extends \Psalm\Tests\TestCase
|
||||
|| ($i && $j);
|
||||
}'
|
||||
],
|
||||
'fineCheck' => [
|
||||
'<?php
|
||||
function foo(bool $b, bool $c) : void {
|
||||
if ((!$b || rand(0, 1)) && (!$c || rand(0, 1))) {}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user