1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Merge pull request #7655 from orklah/enumCollapsing

Enum collapsing
This commit is contained in:
orklah 2022-02-13 11:01:07 +01:00 committed by GitHub
commit 59d3d2aa31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 24 deletions

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser; use PhpParser;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Exception\ComplicatedExpressionException;
use Psalm\Exception\ScopeAnalysisException; use Psalm\Exception\ScopeAnalysisException;
use Psalm\Internal\Algebra; use Psalm\Internal\Algebra;
use Psalm\Internal\Algebra\FormulaGenerator; use Psalm\Internal\Algebra\FormulaGenerator;
@ -15,6 +16,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Clause; use Psalm\Internal\Clause;
use Psalm\Internal\Scope\IfScope; use Psalm\Internal\Scope\IfScope;
use Psalm\Internal\Type\AssertionReconciler; use Psalm\Internal\Type\AssertionReconciler;
use Psalm\Node\Expr\VirtualBooleanNot;
use Psalm\Storage\Assertion\Truthy; use Psalm\Storage\Assertion\Truthy;
use Psalm\Type; use Psalm\Type;
use Psalm\Type\Reconciler; use Psalm\Type\Reconciler;
@ -27,6 +29,7 @@ use function array_keys;
use function array_map; use function array_map;
use function array_merge; use function array_merge;
use function array_values; use function array_values;
use function count;
use function in_array; use function in_array;
use function preg_match; use function preg_match;
use function preg_quote; use function preg_quote;
@ -56,6 +59,7 @@ class TernaryAnalyzer
$context->branch_point ?: (int) $stmt->getAttribute('startFilePos') $context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
); );
// this is the context for stuff that happens within the first operand of the ternary
$if_context = $if_conditional_scope->if_context; $if_context = $if_conditional_scope->if_context;
$cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
@ -64,19 +68,21 @@ class TernaryAnalyzer
return false; return false;
} }
$codebase = $statements_analyzer->getCodebase(); $cond_object_id = spl_object_id($stmt->cond);
$cond_id = spl_object_id($stmt->cond);
$if_clauses = FormulaGenerator::getFormula( $if_clauses = FormulaGenerator::getFormula(
$cond_id, $cond_object_id,
$cond_id, $cond_object_id,
$stmt->cond, $stmt->cond,
$context->self, $context->self,
$statements_analyzer, $statements_analyzer,
$codebase $codebase
); );
if (count($if_clauses) > 200) {
$if_clauses = [];
}
$mixed_var_ids = []; $mixed_var_ids = [];
foreach ($context->vars_in_scope as $var_id => $type) { foreach ($context->vars_in_scope as $var_id => $type) {
@ -95,7 +101,7 @@ class TernaryAnalyzer
/** /**
* @return Clause * @return Clause
*/ */
function (Clause $c) use ($mixed_var_ids, $cond_id): Clause { function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause {
$keys = array_keys($c->possibilities); $keys = array_keys($c->possibilities);
$mixed_var_ids = array_diff($mixed_var_ids, $keys); $mixed_var_ids = array_diff($mixed_var_ids, $keys);
@ -103,7 +109,7 @@ class TernaryAnalyzer
foreach ($keys as $key) { foreach ($keys as $key) {
foreach ($mixed_var_ids as $mixed_var_id) { foreach ($mixed_var_ids as $mixed_var_id) {
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
return new Clause([], $cond_id, $cond_id, true); return new Clause([], $cond_object_id, $cond_object_id, true);
} }
} }
} }
@ -113,6 +119,8 @@ class TernaryAnalyzer
$if_clauses $if_clauses
); );
$entry_clauses = $context->clauses;
// this will see whether any of the clauses in set A conflict with the clauses in set B // this will see whether any of the clauses in set A conflict with the clauses in set B
AlgebraAnalyzer::checkForParadox( AlgebraAnalyzer::checkForParadox(
$context->clauses, $context->clauses,
@ -122,34 +130,58 @@ class TernaryAnalyzer
$assigned_in_conditional_var_ids $assigned_in_conditional_var_ids
); );
$ternary_clauses = array_merge($context->clauses, $if_clauses); $if_clauses = Algebra::simplifyCNF($if_clauses);
$ternary_context_clauses = array_merge($entry_clauses, $if_clauses);
if ($if_context->reconciled_expression_clauses) { if ($if_context->reconciled_expression_clauses) {
$reconciled_expression_clauses = $if_context->reconciled_expression_clauses; $reconciled_expression_clauses = $if_context->reconciled_expression_clauses;
$ternary_clauses = array_values( $ternary_context_clauses = array_values(
array_filter( array_filter(
$ternary_clauses, $ternary_context_clauses,
fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses)
) )
); );
if (count($if_context->clauses) === 1
&& $if_context->clauses[0]->wedge
&& !$if_context->clauses[0]->possibilities
) {
$if_context->clauses = [];
$if_context->reconciled_expression_clauses = [];
}
} }
$ternary_clauses = Algebra::simplifyCNF($ternary_clauses); try {
$if_scope->negated_clauses = Algebra::negateFormula($if_clauses);
} catch (ComplicatedExpressionException $e) {
try {
$if_scope->negated_clauses = FormulaGenerator::getFormula(
$cond_object_id,
$cond_object_id,
new VirtualBooleanNot($stmt->cond),
$context->self,
$statements_analyzer,
$codebase,
false
);
} catch (ComplicatedExpressionException $e) {
$if_scope->negated_clauses = [];
}
}
$negated_clauses = Algebra::negateFormula($if_clauses); $if_scope->negated_types = Algebra::getTruthsFromFormula(
$negated_if_types = Algebra::getTruthsFromFormula(
Algebra::simplifyCNF( Algebra::simplifyCNF(
array_merge($context->clauses, $negated_clauses) array_merge($context->clauses, $if_scope->negated_clauses)
) )
); );
$active_if_types = []; $active_if_types = [];
$reconcilable_if_types = Algebra::getTruthsFromFormula( $reconcilable_if_types = Algebra::getTruthsFromFormula(
$ternary_clauses, $ternary_context_clauses,
$cond_id, $cond_object_id,
$cond_referenced_var_ids, $cond_referenced_var_ids,
$active_if_types $active_if_types
); );
@ -189,16 +221,16 @@ class TernaryAnalyzer
$t_else_context->clauses = Algebra::simplifyCNF( $t_else_context->clauses = Algebra::simplifyCNF(
array_merge( array_merge(
$t_else_context->clauses, $t_else_context->clauses,
$negated_clauses $if_scope->negated_clauses
) )
); );
if ($negated_if_types) {
$changed_var_ids = []; $changed_var_ids = [];
$t_else_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes( if ($if_scope->negated_types) {
$negated_if_types, $else_vars_reconciled = Reconciler::reconcileKeyedTypes(
$negated_if_types, $if_scope->negated_types,
$if_scope->negated_types,
$t_else_context->vars_in_scope, $t_else_context->vars_in_scope,
$t_else_context->references_in_scope, $t_else_context->references_in_scope,
$changed_var_ids, $changed_var_ids,
@ -209,7 +241,7 @@ class TernaryAnalyzer
new CodeLocation($statements_analyzer->getSource(), $stmt->else) new CodeLocation($statements_analyzer->getSource(), $stmt->else)
); );
$t_else_context->vars_in_scope = $t_else_vars_in_scope_reconciled; $t_else_context->vars_in_scope = $else_vars_reconciled;
$t_else_context->clauses = Context::removeReconciledClauses($t_else_context->clauses, $changed_var_ids)[0]; $t_else_context->clauses = Context::removeReconciledClauses($t_else_context->clauses, $changed_var_ids)[0];
} }

View File

@ -18,6 +18,7 @@ use Psalm\Type\Atomic\TCallableString;
use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TClassStringMap; use Psalm\Type\Atomic\TClassStringMap;
use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyMixed;
use Psalm\Type\Atomic\TEnumCase;
use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TGenericObject;
@ -334,6 +335,11 @@ class TypeCombiner
} }
if ($combination->named_object_types !== null) { if ($combination->named_object_types !== null) {
foreach ($combination->value_types as $key => $atomic_type) {
if ($atomic_type instanceof TEnumCase && isset($combination->named_object_types[$atomic_type->value])) {
unset($combination->value_types[$key]);
}
}
$combination->value_types += $combination->named_object_types; $combination->value_types += $combination->named_object_types;
} }

View File

@ -382,6 +382,29 @@ class EnumTest extends TestCase
'ignored_issues' => [], 'ignored_issues' => [],
'php_version' => '8.1', 'php_version' => '8.1',
], ],
'EnumCollapsing' => [
'code' => '<?php
enum Code: int
{
case Ok = 0;
case Fatal = 1;
}
function foo(): int|Code|null
{
return null;
}
$code = foo();
if(!isset($code)){
$code = Code::Ok;
}',
'assertions' => [
'$code' => 'Code|int',
],
'ignored_issues' => [],
'php_version' => '8.1',
],
]; ];
} }