Skip to content

Commit f61c7e1

Browse files
authored
Merge pull request #10805 from weirdan/10461-allow-more-callable-types-as-subtypes-of-callable
2 parents 4b7957b + c9468b6 commit f61c7e1

11 files changed

+66
-9
lines changed

src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Psalm\Type\Atomic\Scalar;
99
use Psalm\Type\Atomic\TArray;
1010
use Psalm\Type\Atomic\TCallable;
11+
use Psalm\Type\Atomic\TCallableInterface;
1112
use Psalm\Type\Atomic\TCallableKeyedArray;
1213
use Psalm\Type\Atomic\TCallableObject;
1314
use Psalm\Type\Atomic\TCallableString;
@@ -191,7 +192,8 @@ public static function isContainedBy(
191192
}
192193

193194
if (($container_type_part instanceof TCallable
194-
&& $input_type_part instanceof TCallable)
195+
&& $input_type_part instanceof TCallableInterface
196+
)
195197
|| ($container_type_part instanceof TClosure
196198
&& $input_type_part instanceof TClosure)
197199
) {

src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php

+19-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Psalm\Type\Atomic\TArray;
2020
use Psalm\Type\Atomic\TCallable;
2121
use Psalm\Type\Atomic\TCallableArray;
22+
use Psalm\Type\Atomic\TCallableInterface;
2223
use Psalm\Type\Atomic\TClassString;
2324
use Psalm\Type\Atomic\TClosure;
2425
use Psalm\Type\Atomic\TKeyedArray;
@@ -41,15 +42,31 @@
4142
final class CallableTypeComparator
4243
{
4344
/**
44-
* @param TCallable|TClosure $input_type_part
45+
* @param TClosure|TCallableInterface $input_type_part
4546
* @param TCallable|TClosure $container_type_part
4647
*/
4748
public static function isContainedBy(
4849
Codebase $codebase,
49-
Atomic $input_type_part,
50+
$input_type_part,
5051
Atomic $container_type_part,
5152
?TypeComparisonResult $atomic_comparison_result
5253
): bool {
54+
if ($container_type_part instanceof TClosure) {
55+
if ($input_type_part instanceof TCallableInterface
56+
&& !$input_type_part instanceof TCallable // it has stricter checks below
57+
) {
58+
if ($atomic_comparison_result) {
59+
$atomic_comparison_result->type_coerced = true;
60+
}
61+
return false;
62+
}
63+
}
64+
if ($input_type_part instanceof TCallableInterface
65+
&& !$input_type_part instanceof TCallable // it has stricter checks below
66+
) {
67+
return true;
68+
}
69+
5370
if ($container_type_part->is_pure && !$input_type_part->is_pure) {
5471
if ($atomic_comparison_result) {
5572
$atomic_comparison_result->type_coerced = $input_type_part->is_pure === null;

src/Psalm/Type/Atomic/TCallable.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*
1515
* @psalm-immutable
1616
*/
17-
final class TCallable extends Atomic
17+
final class TCallable extends Atomic implements TCallableInterface
1818
{
1919
use CallableTrait;
2020

src/Psalm/Type/Atomic/TCallableArray.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* @psalm-immutable
99
*/
10-
final class TCallableArray extends TNonEmptyArray
10+
final class TCallableArray extends TNonEmptyArray implements TCallableInterface
1111
{
1212
/**
1313
* @var string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Psalm\Type\Atomic;
4+
5+
interface TCallableInterface
6+
{
7+
}

src/Psalm/Type/Atomic/TCallableKeyedArray.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* @psalm-immutable
99
*/
10-
final class TCallableKeyedArray extends TKeyedArray
10+
final class TCallableKeyedArray extends TKeyedArray implements TCallableInterface
1111
{
1212
protected const NAME_ARRAY = 'callable-array';
1313
protected const NAME_LIST = 'callable-list';

src/Psalm/Type/Atomic/TCallableList.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* @deprecated Will be removed in Psalm v6, please use TCallableKeyedArrays with is_list=true instead.
1313
* @psalm-immutable
1414
*/
15-
final class TCallableList extends TNonEmptyList
15+
final class TCallableList extends TNonEmptyList implements TCallableInterface
1616
{
1717
public const KEY = 'callable-list';
1818
public function getKeyedArray(): TKeyedArray

src/Psalm/Type/Atomic/TCallableObject.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* @psalm-immutable
99
*/
10-
final class TCallableObject extends TObject
10+
final class TCallableObject extends TObject implements TCallableInterface
1111
{
1212
use HasIntersectionTrait;
1313

src/Psalm/Type/Atomic/TCallableString.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* @psalm-immutable
99
*/
10-
final class TCallableString extends TNonFalsyString
10+
final class TCallableString extends TNonFalsyString implements TCallableInterface
1111
{
1212

1313
public function getKey(bool $include_extra = true): string

tests/CallableTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,21 @@ function f(callable $c): void {
19201920
'ignored_issues' => [],
19211921
'php_version' => '8.0',
19221922
],
1923+
'callableArrayPassedAsCallable' => [
1924+
'code' => <<<'PHP'
1925+
<?php
1926+
function f(callable $c): void {
1927+
$c();
1928+
}
1929+
/** @var object $o */;
1930+
1931+
$ca = [$o::class, 'createFromFormat'];
1932+
if (!is_callable($ca)) {
1933+
exit;
1934+
}
1935+
f($ca);
1936+
PHP,
1937+
],
19231938
];
19241939
}
19251940

tests/TypeComparatorTest.php

+16
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,22 @@ public function getSuccessfulComparisons(): array
163163
'(callable(int,string[]): void)|(callable(int): void)',
164164
'(callable(int): void)|(callable(int,string[]): void)',
165165
],
166+
'callableAcceptsCallableArray' => [
167+
'callable',
168+
"callable-array{0: class-string, 1: 'from'}",
169+
],
170+
'callableAcceptsCallableObject' => [
171+
'callable',
172+
"callable-object",
173+
],
174+
'callableAcceptsCallableString' => [
175+
'callable',
176+
'callable-string',
177+
],
178+
'callableAcceptsCallableKeyedList' => [
179+
'callable',
180+
"callable-list{class-string, 'from'}",
181+
],
166182
];
167183
}
168184

0 commit comments

Comments
 (0)