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

Fix buggy behaviour around callable tests

This commit is contained in:
Brown 2019-04-09 18:09:57 -04:00
parent 1a33b25264
commit 7f8b47c5cf
9 changed files with 72 additions and 7 deletions

View File

@ -336,7 +336,7 @@ class Config
/** @var array<string, mixed> */
private $predefined_constants;
/** @var array<string, bool> */
/** @var array<callable-string, bool> */
private $predefined_functions = [];
/** @var ClassLoader|null */
@ -1328,7 +1328,7 @@ class Config
}
/**
* @return array<string, bool>
* @return array<callable-string, bool>
*/
public function getPredefinedFunctions()
{

View File

@ -1669,6 +1669,10 @@ class AssertionFinder
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'class-string']];
}
} elseif (self::hasFunctionExistsCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'callable-string']];
}
} elseif (self::hasInArrayCheck($expr)) {
if ($first_var_name && isset($expr->args[1]->value->inferredType)) {
foreach ($expr->args[1]->value->inferredType->getTypes() as $atomic_type) {
@ -2282,6 +2286,20 @@ class AssertionFinder
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*

View File

@ -13,6 +13,7 @@ use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TBool;
use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableString;
use Psalm\Type\Atomic\TEmptyMixed;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
@ -1009,6 +1010,13 @@ class TypeAnalyzer
return true;
}
if ($input_type_part instanceof TCallableString
&& (get_class($container_type_part) === TString::class
|| get_class($container_type_part) === TSingleLetter::class)
) {
return true;
}
if ($container_type_part instanceof TString
&& ($input_type_part instanceof TNumericString
|| $input_type_part instanceof THtmlEscapedString)
@ -1025,7 +1033,39 @@ class TypeAnalyzer
return false;
}
if (($container_type_part instanceof TClassString || $container_type_part instanceof TLiteralClassString)
if ($container_type_part instanceof TCallableString
&& $input_type_part instanceof TLiteralString
) {
$input_callable = self::getCallableFromAtomic($codebase, $input_type_part);
$container_callable = self::getCallableFromAtomic($codebase, $container_type_part);
if ($input_callable && $container_callable) {
$all_types_contain = true;
if (self::compareCallable(
$codebase,
$input_callable,
$container_callable,
$type_coerced,
$type_coerced_from_mixed,
$has_scalar_match,
$all_types_contain
) === false
) {
return false;
}
if (!$all_types_contain) {
return false;
}
}
return true;
}
if (($container_type_part instanceof TClassString
|| $container_type_part instanceof TLiteralClassString
|| $container_type_part instanceof TCallableString)
&& $input_type_part instanceof TString
) {
$type_coerced = true;

View File

@ -3529,7 +3529,7 @@ return [
'get_declared_interfaces' => ['array<int,class-string>'],
'get_declared_traits' => ['array<int,class-string>'],
'get_defined_constants' => ['array<string,int|string|float|bool|null|array|resource>', 'categorize='=>'bool'],
'get_defined_functions' => ['array<string,array<string,string>>', 'exclude_disabled='=>'bool'],
'get_defined_functions' => ['array<string,array<string,callable-string>>', 'exclude_disabled='=>'bool'],
'get_defined_vars' => ['array'],
'get_extension_funcs' => ['array<int,callable-string>', 'extension_name'=>'string'],
'get_headers' => ['array|false', 'url'=>'string', 'format='=>'int', 'context='=>'resource'],

View File

@ -175,6 +175,7 @@ class Functions
$predefined_functions = $statements_analyzer->getCodebase()->config->getPredefinedFunctions();
if (isset($predefined_functions[$function_id])) {
/** @psalm-suppress TypeCoercion */
if ($this->reflection->registerFunction($function_id) === false) {
return false;
}

View File

@ -319,7 +319,7 @@ class Reflection
}
/**
* @param string $function_id
* @param callable-string $function_id
*
* @return false|null
*/

View File

@ -1316,6 +1316,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
return $this->file_storage->functions[$function_id];
} elseif (isset($this->config->getPredefinedFunctions()[$function_id])) {
/** @psalm-suppress TypeCoercion */
$reflection_function = new \ReflectionFunction($function_id);
if ($reflection_function->getFileName() !== $this->file_path) {

View File

@ -59,6 +59,7 @@ abstract class Type
'mixed' => true,
'numeric-string' => true,
'class-string' => true,
'callable-string' => true,
'mysql-escaped-string' => true,
'html-escaped-string' => true,
'boolean' => true,

View File

@ -504,6 +504,10 @@ class Reconciler
$existing_var_type->removeType('class-string');
}
if ($existing_var_type->hasType('callable-string')) {
$existing_var_type->removeType('callable-string');
}
if ($existing_var_type->hasType('string')) {
$existing_var_type->removeType('string');
$existing_var_type->addType(new Type\Atomic\TLiteralString(''));
@ -2144,7 +2148,7 @@ class Reconciler
}
}
}
} elseif ($scalar_type === 'string' || $scalar_type === 'class-string') {
} elseif ($scalar_type === 'string' || $scalar_type === 'class-string' || $scalar_type === 'callable-string') {
if ($existing_var_type->hasMixed() || $existing_var_type->hasScalar()) {
if ($scalar_type === 'class-string') {
return new Type\Union([new Type\Atomic\TLiteralClassString($value)]);
@ -2328,7 +2332,7 @@ class Reconciler
$did_remove_type = true;
}
}
} elseif ($scalar_type === 'string' || $scalar_type === 'class-string') {
} elseif ($scalar_type === 'string' || $scalar_type === 'class-string' || $scalar_type === 'callable-string') {
if ($existing_var_type->hasString() && $existing_string_types = $existing_var_type->getLiteralStrings()) {
$did_match_literal_type = true;