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

Improve reconciliation of arrays with constant offsets

This commit is contained in:
Brown 2019-12-13 17:17:14 -05:00
parent 6d02aa86e8
commit 2469f04715
5 changed files with 89 additions and 47 deletions

View File

@ -503,35 +503,6 @@ class Context
}
}
/**
* This removes all clauses that are invalidated because
* all possibilities are overwritten by changed var ids
*
* @param Clause[] $clauses
* @param array<string, bool> $changed_var_ids
*
* @return list<Clause>
*/
public static function removeOverwrittenClauses(array $clauses, array $changed_var_ids)
{
$included_clauses = [];
foreach ($clauses as $c) {
if ($c->wedge) {
$included_clauses[] = $c;
continue;
}
if (!\array_diff_key($c->possibilities, $changed_var_ids)) {
continue;
}
$included_clauses[] = $c;
}
return $included_clauses;
}
/**
* @param Clause[] $clauses
* @param array<string, bool> $changed_var_ids

View File

@ -142,13 +142,6 @@ class IfAnalyzer
$entry_clauses = $context->clauses;
if ($if_scope->if_cond_changed_var_ids) {
$entry_clauses = Context::removeOverwrittenClauses(
$entry_clauses,
$if_scope->if_cond_changed_var_ids
);
}
// this will see whether any of the clauses in set A conflict with the clauses in set B
AlgebraAnalyzer::checkForParadox(
$context->clauses,
@ -1085,13 +1078,6 @@ class IfAnalyzer
$cond_assigned_var_ids
);
if ($if_scope->if_cond_changed_var_ids) {
$entry_clauses = Context::removeOverwrittenClauses(
$entry_clauses,
$if_scope->if_cond_changed_var_ids
);
}
$elseif_context_clauses = array_merge($entry_clauses, $elseif_clauses);
if ($elseif_context->reconciled_expression_clauses) {

View File

@ -2,6 +2,7 @@
namespace Psalm\Internal\Analyzer\Statements\Expression\Assignment;
use PhpParser;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
@ -223,6 +224,15 @@ class ArrayAssignmentAnalyzer
if ($object_id) {
$var_id_additions[] = '[' . $object_id . '->' . $child_stmt->dim->name->name . ']';
}
} elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch
&& $child_stmt->dim->name instanceof PhpParser\Node\Identifier
&& $child_stmt->dim->class instanceof PhpParser\Node\Name
) {
$object_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
$child_stmt->dim->class,
$statements_analyzer->getAliases()
);
$var_id_additions[] = '[' . $object_name . '::' . $child_stmt->dim->name->name . ']';
} else {
$var_id_additions[] = '[' . $child_stmt_dim_type . ']';
$full_var_id = false;

View File

@ -105,6 +105,11 @@ class Reconciler
$base_key = array_shift($key_parts);
if ($base_key[0] !== '$' && count($key_parts) > 2 && $key_parts[0] === '::$') {
$base_key .= array_shift($key_parts);
$base_key .= array_shift($key_parts);
}
if (!isset($existing_types[$base_key]) || $existing_types[$base_key]->isNullable()) {
if (!isset($new_types[$base_key])) {
$new_types[$base_key] = [['=isset']];
@ -465,6 +470,11 @@ class Reconciler
$base_key = array_shift($key_parts);
if ($base_key[0] !== '$' && count($key_parts) > 2 && $key_parts[0] === '::$') {
$base_key .= array_shift($key_parts);
$base_key .= array_shift($key_parts);
}
if (!isset($existing_keys[$base_key])) {
if (strpos($base_key, '::')) {
list($fq_class_name, $const_name) = explode('::', $base_key);

View File

@ -2238,7 +2238,39 @@ class ConditionalTest extends \Psalm\Tests\TestCase
echo isset($a);
}'
],
'assertOnStaticClassKey' => [
'assertOnVarStaticClassKey' => [
'<?php
abstract class Obj {
/**
* @param array<class-string, array<string, int>> $arr
* @return array<string, int>
*/
public static function getArr(array $arr) : array {
if (!isset($arr[static::class])) {
$arr[static::class] = ["hello" => 5];
}
return $arr[static::class];
}
}'
],
'assertOnVarVar' => [
'<?php
abstract class Obj {
/**
* @param array<class-string, array<string, int>> $arr
* @return array<string, int>
*/
function getArr(array $arr, string $s) : array {
if (!isset($arr[$s])) {
$arr[$s] = ["hello" => 5];
}
return $arr[$s];
}
}'
],
'assertOnPropertyStaticClassKey' => [
'<?php
abstract class Obj {
/** @var array<class-string, array<string, int>> */
@ -2246,11 +2278,12 @@ class ConditionalTest extends \Psalm\Tests\TestCase
/** @return array<string, int> */
public static function getArr() : array {
if (!isset(self::$arr[static::class])) {
self::$arr[static::class] = ["hello" => 5];
$arr = self::$arr;
if (!isset($arr[static::class])) {
$arr[static::class] = ["hello" => 5];
}
return self::$arr[static::class];
return $arr[static::class];
}
}'
],
@ -2287,6 +2320,38 @@ class ConditionalTest extends \Psalm\Tests\TestCase
}
}'
],
'reconcileEmptinessBetter' => [
'<?php
/**
* @param string|array $valuePath
*/
function combine($valuePath) : void {
if (!empty($valuePath) && is_array($valuePath)) {
} elseif (!empty($valuePath)) {
echo $valuePath;
}
}',
],
'issetAssertionOnStaticProperty' => [
'<?php
class C {
protected static array $cache = [];
/**
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedReturnStatement
* @psalm-suppress MixedInferredReturnType
*/
public static function get(string $k1, string $k2) : ?string {
if (!isset(static::$cache[$k1][$k2])) {
return null;
}
return static::$cache[$k1][$k2];
}
}'
],
];
}