1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 03:17:02 +01:00
psalm/src/Psalm/Internal/Analyzer/AlgebraAnalyzer.php

160 lines
5.5 KiB
PHP
Raw Normal View History

<?php
2018-11-06 03:57:36 +01:00
namespace Psalm\Internal\Analyzer;
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;
use Psalm\Issue\RedundantCondition;
use Psalm\IssueBuffer;
2018-05-07 07:26:06 +02:00
use Psalm\Type\Algebra;
use function array_intersect_key;
use function count;
use function array_unique;
/**
* @internal
*/
2018-11-06 03:57:36 +01:00
class AlgebraAnalyzer
{
2017-04-02 21:26:10 +02:00
/**
* This looks to see if there are any clauses in one formula that contradict
* 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
2018-11-11 18:01:14 +01:00
* @param StatementsAnalyzer $statements_analyzer,
2017-04-02 21:26:10 +02:00
* @param PhpParser\Node $stmt
* @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,
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 {
$negated_formula2 = Algebra::negateFormula($formula2);
2019-01-08 15:57:14 +01:00
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
return;
}
2018-05-07 20:52:45 +02:00
$formula1_hashes = [];
foreach ($formula1 as $formula1_clause) {
$formula1_hashes[$formula1_clause->hash] = true;
2018-05-07 20:52:45 +02:00
}
$formula2_hashes = [];
foreach ($formula2 as $formula2_clause) {
$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',
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
}
}
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
foreach ($negated_formula2 as $clause_a) {
if (count($negated_formula2) === 1) {
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)
) {
if (IssueBuffer::accepts(
new RedundantCondition(
'Found a redundant condition when evaluating ' . $key,
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer, $stmt)
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
2017-04-02 21:26:10 +02:00
if (!$clause_a->reconcilable || $clause_a->wedge) {
continue;
}
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;
}
$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) {
2017-04-02 21:26:10 +02:00
if (IssueBuffer::accepts(
new ParadoxicalCondition(
2018-05-07 07:26:06 +02:00
'Encountered a paradox when evaluating the conditionals ('
. $clause_a . ') and (' . $clause_b . ')',
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;
}
}
}
}
}