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:
parent
1a33b25264
commit
7f8b47c5cf
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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'],
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -319,7 +319,7 @@ class Reflection
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_id
|
||||
* @param callable-string $function_id
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user