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

Improved understanding of array_key_exists

Fixes #3463
This commit is contained in:
Brown 2020-05-27 09:03:36 -04:00
parent b9ea115487
commit 9b413cfccc
5 changed files with 86 additions and 0 deletions

View File

@ -2021,6 +2021,29 @@ class AssertionFinder
}
}
if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
&& $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
&& $expr->args[0]->value->name->name !== 'class'
) {
$const_type = null;
if ($source instanceof StatementsAnalyzer) {
$const_type = $source->node_data->getType($expr->args[0]->value);
}
if ($const_type) {
if ($const_type->isSingleStringLiteral()) {
$first_var_name = $const_type->getSingleStringLiteral()->value;
} elseif ($const_type->isSingleIntLiteral()) {
$first_var_name = (string) $const_type->getSingleIntLiteral()->value;
} else {
$first_var_name = null;
}
} else {
$first_var_name = null;
}
}
if ($first_var_name !== null
&& $array_root
&& !strpos($first_var_name, '->')

View File

@ -120,6 +120,8 @@ class NegatedAssertionReconciler extends Reconciler
return Type::getEmpty();
} elseif (substr($assertion, 0, 9) === 'in-array-') {
return $existing_var_type;
} elseif (substr($assertion, 0, 14) === 'has-array-key-') {
return $existing_var_type;
}
}

View File

@ -111,6 +111,13 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
);
}
if (substr($assertion, 0, 14) === 'has-array-key-') {
return self::reconcileHasArrayKey(
$existing_var_type,
substr($assertion, 14)
);
}
if ($assertion === 'falsy' || $assertion === 'empty') {
return self::reconcileFalsyOrEmpty(
$assertion,
@ -1263,6 +1270,38 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
return $existing_var_type;
}
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
*/
private static function reconcileHasArrayKey(
Union $existing_var_type,
string $assertion
) : Union {
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\ObjectLike) {
$is_class_string = false;
if (strpos($assertion, '::class')) {
list($assertion) = explode('::', $assertion);
$is_class_string = true;
}
if (isset($atomic_type->properties[$assertion])) {
$atomic_type->properties[$assertion]->possibly_undefined = false;
} else {
$atomic_type->properties[$assertion] = Type::getMixed();
if ($is_class_string) {
$atomic_type->class_strings[$assertion] = true;
}
}
}
}
return $existing_var_type;
}
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation

View File

@ -176,6 +176,15 @@ class Reconciler
} else {
$new_types[$key_parts[2]] = [['=in-array-' . $key_parts[0]]];
}
if ($key_parts[0][0] === '$') {
if (isset($new_types[$key_parts[0]])) {
$new_types[$key_parts[0]][] = ['=has-array-key-' . $key_parts[2]];
} else {
$new_types[$key_parts[0]] = [['=has-array-key-' . $key_parts[2]]];
}
}
}
}
}

View File

@ -800,6 +800,19 @@ class ConstantTest extends TestCase
echo B::VALUES["there"];'
],
'constantArrayKeyExistsWithClassConstant' => [
'<?php
class Foo {
public const F = "key";
}
/** @param array{key?: string} $a */
function one(array $a): void {
if (array_key_exists(Foo::F, $a)) {
echo $a[Foo::F];
}
}'
],
];
}