From 6d43a6696c0d050791895e8ac4102ea38166119d Mon Sep 17 00:00:00 2001 From: Brown Date: Wed, 26 Aug 2020 15:35:29 -0400 Subject: [PATCH] Make clause fully immutable --- src/Psalm/Context.php | 2 +- .../Internal/Analyzer/AlgebraAnalyzer.php | 2 + .../Analyzer/Statements/Block/DoAnalyzer.php | 7 +- .../Analyzer/Statements/Block/IfAnalyzer.php | 25 +++-- .../Statements/Block/LoopAnalyzer.php | 5 +- .../Statements/Block/SwitchCaseAnalyzer.php | 9 +- .../Expression/BinaryOp/AndAnalyzer.php | 7 +- .../Expression/BinaryOp/CoalesceAnalyzer.php | 9 +- .../Expression/BinaryOp/OrAnalyzer.php | 10 +- .../Expression/Call/FunctionCallAnalyzer.php | 5 +- .../Statements/Expression/CallAnalyzer.php | 5 +- .../Statements/Expression/TernaryAnalyzer.php | 11 +- src/Psalm/Internal/Clause.php | 36 ++++--- .../Internal/PhpVisitor/ReflectorVisitor.php | 5 +- src/Psalm/Type/Algebra.php | 100 ++++++++++-------- tests/AlgebraTest.php | 41 ++++--- tests/TypeReconciliation/ConditionalTest.php | 4 + tests/TypeReconciliation/TypeAlgebraTest.php | 6 ++ 18 files changed, 185 insertions(+), 104 deletions(-) diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index 61c0e9659..26b8fb4ec 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -158,7 +158,7 @@ class Context /** * A list of hashed clauses that have already been factored in * - * @var list + * @var list */ public $reconciled_expression_clauses = []; diff --git a/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php b/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php index a5ac1a3c9..1d5b81d0e 100644 --- a/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php @@ -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) ) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php index 1564f919f..5581acc86 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php @@ -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( diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php index e868ce797..4eb4a90bb 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfAnalyzer.php @@ -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 = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php index 1454a1b3d..0115109d1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php @@ -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, diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php index 784a23105..dd24ebbda 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php @@ -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, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php index 6a121fd1d..cccccb1ac 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php @@ -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 ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php index d988ea1ea..3c5829599 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php @@ -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); } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php index d38d53ec2..569e2694d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php @@ -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 ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 024688228..581a555ed 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -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, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index a4ce35039..4354b2101 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -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, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php index dbd141620..26ff7ee58 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -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 ); diff --git a/src/Psalm/Internal/Clause.php b/src/Psalm/Internal/Clause.php index abfcd49d2..7501d3b40 100644 --- a/src/Psalm/Internal/Clause.php +++ b/src/Psalm/Internal/Clause.php @@ -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 */ 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 ); } diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index 9f50b83d5..e0ac6848c 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -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] diff --git a/src/Psalm/Type/Algebra.php b/src/Psalm/Type/Algebra.php index 5b19a6630..4420323d0 100644 --- a/src/Psalm/Type/Algebra.php +++ b/src/Psalm/Type/Algebra.php @@ -91,7 +91,8 @@ class Algebra * @return array */ 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 $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 $clauses * * @return non-empty-list - * - * @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; diff --git a/tests/AlgebraTest.php b/tests/AlgebraTest.php index bc80f61dd..d0bfa54d1 100644 --- a/tests/AlgebraTest.php +++ b/tests/AlgebraTest.php @@ -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]); diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 0a36b2535..a315cdd26 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2171,6 +2171,10 @@ class ConditionalTest extends \Psalm\Tests\TestCase } }' ], + 'manyNestedWedgeAssertions' => [ + ' [ ' [ + '