2017-03-18 18:37:00 +01:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer;
|
2017-03-18 18:37:00 +01:00
|
|
|
|
|
|
|
use PhpParser;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Clause;
|
2017-04-02 21:26:10 +02:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Issue\ParadoxicalCondition;
|
2017-11-28 06:46:41 +01:00
|
|
|
use Psalm\Issue\RedundantCondition;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2018-05-07 07:26:06 +02:00
|
|
|
use Psalm\Type\Algebra;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function array_intersect_key;
|
|
|
|
use function count;
|
|
|
|
use function array_unique;
|
2017-03-18 18:37:00 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class AlgebraAnalyzer
|
2017-03-18 18:37:00 +01:00
|
|
|
{
|
2017-04-02 21:26:10 +02:00
|
|
|
/**
|
|
|
|
* This looks to see if there are any clauses in one formula that contradict
|
2017-11-28 06:46:41 +01:00
|
|
|
* clauses in another formula, or clauses that duplicate previous clauses
|
2017-04-02 21:26:10 +02:00
|
|
|
*
|
|
|
|
* e.g.
|
|
|
|
* if ($a) { }
|
|
|
|
* elseif ($a) { }
|
|
|
|
*
|
|
|
|
* @param array<int, Clause> $formula1
|
|
|
|
* @param array<int, Clause> $formula2
|
2017-11-29 04:17:03 +01:00
|
|
|
* @param array<string, bool> $new_assigned_var_ids
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-04-02 21:26:10 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function checkForParadox(
|
|
|
|
array $formula1,
|
|
|
|
array $formula2,
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2017-11-29 04:17:03 +01:00
|
|
|
PhpParser\Node $stmt,
|
|
|
|
array $new_assigned_var_ids
|
2017-04-02 21:26:10 +02:00
|
|
|
) {
|
2019-01-08 15:57:14 +01:00
|
|
|
try {
|
2020-03-30 01:42:22 +02:00
|
|
|
$negated_formula2 = Algebra::negateFormula($formula2);
|
2019-01-08 15:57:14 +01:00
|
|
|
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-08 00:38:38 +02:00
|
|
|
|
2018-05-07 20:52:45 +02:00
|
|
|
$formula1_hashes = [];
|
|
|
|
|
|
|
|
foreach ($formula1 as $formula1_clause) {
|
2020-08-26 16:41:47 +02:00
|
|
|
$formula1_hashes[$formula1_clause->hash] = true;
|
2018-05-07 20:52:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$formula2_hashes = [];
|
|
|
|
|
|
|
|
foreach ($formula2 as $formula2_clause) {
|
2020-08-26 16:41:47 +02:00
|
|
|
$hash = $formula2_clause->hash;
|
2018-05-07 20:52:45 +02:00
|
|
|
|
|
|
|
if (!$formula2_clause->generated
|
2020-08-26 21:35:29 +02:00
|
|
|
&& !$formula2_clause->wedge
|
|
|
|
&& $formula2_clause->reconcilable
|
2018-05-07 20:52:45 +02:00
|
|
|
&& (isset($formula1_hashes[$hash]) || isset($formula2_hashes[$hash]))
|
|
|
|
&& !array_intersect_key($new_assigned_var_ids, $formula2_clause->possibilities)
|
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new RedundantCondition(
|
|
|
|
$formula2_clause . ' has already been asserted',
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
|
|
|
null
|
2018-05-07 20:52:45 +02:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-05-07 20:52:45 +02:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($formula2_clause->possibilities as $key => $values) {
|
|
|
|
if (!$formula2_clause->generated
|
|
|
|
&& count($values) > 1
|
|
|
|
&& !isset($new_assigned_var_ids[$key])
|
|
|
|
&& count(array_unique($values)) < count($values)
|
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ParadoxicalCondition(
|
|
|
|
'Found a redundant condition when evaluating assertion (' . $formula2_clause . ')',
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt)
|
2018-05-07 20:52:45 +02:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-05-07 20:52:45 +02:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$formula2_hashes[$hash] = true;
|
|
|
|
}
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
// remove impossible types
|
2017-08-08 00:38:38 +02:00
|
|
|
foreach ($negated_formula2 as $clause_a) {
|
2017-12-18 06:47:36 +01:00
|
|
|
if (count($negated_formula2) === 1) {
|
2017-12-18 06:39:00 +01:00
|
|
|
foreach ($clause_a->possibilities as $key => $values) {
|
|
|
|
if (count($values) > 1
|
|
|
|
&& !isset($new_assigned_var_ids[$key])
|
2018-05-07 20:52:45 +02:00
|
|
|
&& count(array_unique($values)) < count($values)
|
2017-12-18 06:39:00 +01:00
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new RedundantCondition(
|
|
|
|
'Found a redundant condition when evaluating ' . $key,
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
|
|
|
null
|
2017-12-18 06:39:00 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2017-12-18 06:39:00 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2017-11-28 06:46:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
if (!$clause_a->reconcilable || $clause_a->wedge) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:38:38 +02:00
|
|
|
foreach ($formula1 as $clause_b) {
|
2017-04-02 21:26:10 +02:00
|
|
|
if ($clause_a === $clause_b || !$clause_b->reconcilable || $clause_b->wedge) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:38:38 +02:00
|
|
|
$clause_a_contains_b_possibilities = true;
|
|
|
|
|
|
|
|
foreach ($clause_b->possibilities as $key => $keyed_possibilities) {
|
|
|
|
if (!isset($clause_a->possibilities[$key])) {
|
|
|
|
$clause_a_contains_b_possibilities = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($clause_a->possibilities[$key] != $keyed_possibilities) {
|
|
|
|
$clause_a_contains_b_possibilities = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($clause_a_contains_b_possibilities) {
|
2020-10-07 18:00:53 +02:00
|
|
|
$clause_a_string = (string) $clause_a;
|
|
|
|
$clause_b_string = (string) $clause_b;
|
|
|
|
|
|
|
|
if ($clause_a_string === $clause_b_string) {
|
2020-10-07 18:04:54 +02:00
|
|
|
$paradox_message = 'Encountered a duplicate check for (' . $clause_a_string . ')';
|
2020-10-07 18:00:53 +02:00
|
|
|
} else {
|
|
|
|
$paradox_message = 'Encountered a paradox when evaluating the conditionals ('
|
|
|
|
. $clause_a_string . ') and (' . $clause_b_string . ')';
|
|
|
|
}
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ParadoxicalCondition(
|
2020-10-07 18:00:53 +02:00
|
|
|
$paradox_message,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt)
|
2017-04-02 21:26:10 +02:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2017-04-02 21:26:10 +02:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-18 18:37:00 +01:00
|
|
|
}
|
|
|
|
}
|