1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Merge pull request #10805 from weirdan/10461-allow-more-callable-types-as-subtypes-of-callable

This commit is contained in:
Bruce Weirdan 2024-03-20 00:17:24 +01:00 committed by GitHub
commit f61c7e108f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 66 additions and 9 deletions

View File

@ -8,6 +8,7 @@ use Psalm\Type\Atomic;
use Psalm\Type\Atomic\Scalar; use Psalm\Type\Atomic\Scalar;
use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableInterface;
use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TCallableKeyedArray;
use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableObject;
use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TCallableString;
@ -191,7 +192,8 @@ final class AtomicTypeComparator
} }
if (($container_type_part instanceof TCallable if (($container_type_part instanceof TCallable
&& $input_type_part instanceof TCallable) && $input_type_part instanceof TCallableInterface
)
|| ($container_type_part instanceof TClosure || ($container_type_part instanceof TClosure
&& $input_type_part instanceof TClosure) && $input_type_part instanceof TClosure)
) { ) {

View File

@ -19,6 +19,7 @@ use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TCallableArray;
use Psalm\Type\Atomic\TCallableInterface;
use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TKeyedArray;
@ -41,15 +42,31 @@ use function substr;
final class CallableTypeComparator final class CallableTypeComparator
{ {
/** /**
* @param TCallable|TClosure $input_type_part * @param TClosure|TCallableInterface $input_type_part
* @param TCallable|TClosure $container_type_part * @param TCallable|TClosure $container_type_part
*/ */
public static function isContainedBy( public static function isContainedBy(
Codebase $codebase, Codebase $codebase,
Atomic $input_type_part, $input_type_part,
Atomic $container_type_part, Atomic $container_type_part,
?TypeComparisonResult $atomic_comparison_result ?TypeComparisonResult $atomic_comparison_result
): bool { ): bool {
if ($container_type_part instanceof TClosure) {
if ($input_type_part instanceof TCallableInterface
&& !$input_type_part instanceof TCallable // it has stricter checks below
) {
if ($atomic_comparison_result) {
$atomic_comparison_result->type_coerced = true;
}
return false;
}
}
if ($input_type_part instanceof TCallableInterface
&& !$input_type_part instanceof TCallable // it has stricter checks below
) {
return true;
}
if ($container_type_part->is_pure && !$input_type_part->is_pure) { if ($container_type_part->is_pure && !$input_type_part->is_pure) {
if ($atomic_comparison_result) { if ($atomic_comparison_result) {
$atomic_comparison_result->type_coerced = $input_type_part->is_pure === null; $atomic_comparison_result->type_coerced = $input_type_part->is_pure === null;

View File

@ -14,7 +14,7 @@ use Psalm\Type\Union;
* *
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallable extends Atomic final class TCallable extends Atomic implements TCallableInterface
{ {
use CallableTrait; use CallableTrait;

View File

@ -7,7 +7,7 @@ namespace Psalm\Type\Atomic;
* *
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallableArray extends TNonEmptyArray final class TCallableArray extends TNonEmptyArray implements TCallableInterface
{ {
/** /**
* @var string * @var string

View File

@ -0,0 +1,7 @@
<?php
namespace Psalm\Type\Atomic;
interface TCallableInterface
{
}

View File

@ -7,7 +7,7 @@ namespace Psalm\Type\Atomic;
* *
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallableKeyedArray extends TKeyedArray final class TCallableKeyedArray extends TKeyedArray implements TCallableInterface
{ {
protected const NAME_ARRAY = 'callable-array'; protected const NAME_ARRAY = 'callable-array';
protected const NAME_LIST = 'callable-list'; protected const NAME_LIST = 'callable-list';

View File

@ -12,7 +12,7 @@ use function array_fill;
* @deprecated Will be removed in Psalm v6, please use TCallableKeyedArrays with is_list=true instead. * @deprecated Will be removed in Psalm v6, please use TCallableKeyedArrays with is_list=true instead.
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallableList extends TNonEmptyList final class TCallableList extends TNonEmptyList implements TCallableInterface
{ {
public const KEY = 'callable-list'; public const KEY = 'callable-list';
public function getKeyedArray(): TKeyedArray public function getKeyedArray(): TKeyedArray

View File

@ -7,7 +7,7 @@ namespace Psalm\Type\Atomic;
* *
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallableObject extends TObject final class TCallableObject extends TObject implements TCallableInterface
{ {
use HasIntersectionTrait; use HasIntersectionTrait;

View File

@ -7,7 +7,7 @@ namespace Psalm\Type\Atomic;
* *
* @psalm-immutable * @psalm-immutable
*/ */
final class TCallableString extends TNonFalsyString final class TCallableString extends TNonFalsyString implements TCallableInterface
{ {
public function getKey(bool $include_extra = true): string public function getKey(bool $include_extra = true): string

View File

@ -1920,6 +1920,21 @@ class CallableTest extends TestCase
'ignored_issues' => [], 'ignored_issues' => [],
'php_version' => '8.0', 'php_version' => '8.0',
], ],
'callableArrayPassedAsCallable' => [
'code' => <<<'PHP'
<?php
function f(callable $c): void {
$c();
}
/** @var object $o */;
$ca = [$o::class, 'createFromFormat'];
if (!is_callable($ca)) {
exit;
}
f($ca);
PHP,
],
]; ];
} }

View File

@ -163,6 +163,22 @@ class TypeComparatorTest extends TestCase
'(callable(int,string[]): void)|(callable(int): void)', '(callable(int,string[]): void)|(callable(int): void)',
'(callable(int): void)|(callable(int,string[]): void)', '(callable(int): void)|(callable(int,string[]): void)',
], ],
'callableAcceptsCallableArray' => [
'callable',
"callable-array{0: class-string, 1: 'from'}",
],
'callableAcceptsCallableObject' => [
'callable',
"callable-object",
],
'callableAcceptsCallableString' => [
'callable',
'callable-string',
],
'callableAcceptsCallableKeyedList' => [
'callable',
"callable-list{class-string, 'from'}",
],
]; ];
} }