From 20c788911f7e43cf18225c44392c34ebc2f02756 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Sun, 10 Mar 2024 22:17:38 +0100 Subject: [PATCH 1/2] Allow more callable types as subtypes of `callable` Fixes vimeo/psalm#10461 --- .../Type/Comparator/AtomicTypeComparator.php | 8 +++++- .../Comparator/CallableTypeComparator.php | 25 ++++++++++++++++++- tests/CallableTest.php | 15 +++++++++++ tests/TypeComparatorTest.php | 16 ++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php index 0b3478ea9..85000bec7 100644 --- a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -8,6 +8,7 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\Scalar; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; +use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableString; @@ -191,7 +192,12 @@ final class AtomicTypeComparator } if (($container_type_part instanceof TCallable - && $input_type_part instanceof TCallable) + && ($input_type_part instanceof TCallable + || $input_type_part instanceof TCallableArray + || $input_type_part instanceof TCallableObject + || $input_type_part instanceof TCallableString + || $input_type_part instanceof TCallableKeyedArray + )) || ($container_type_part instanceof TClosure && $input_type_part instanceof TClosure) ) { diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index 4c7c3b8c7..393c83691 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -19,6 +19,9 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallableArray; +use Psalm\Type\Atomic\TCallableKeyedArray; +use Psalm\Type\Atomic\TCallableObject; +use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TKeyedArray; @@ -41,7 +44,7 @@ use function substr; final class CallableTypeComparator { /** - * @param TCallable|TClosure $input_type_part + * @param TCallable|TClosure|TCallableArray|TCallableString|TCallableKeyedArray|TCallableObject $input_type_part * @param TCallable|TClosure $container_type_part */ public static function isContainedBy( @@ -50,6 +53,26 @@ final class CallableTypeComparator Atomic $container_type_part, ?TypeComparisonResult $atomic_comparison_result ): bool { + if ($container_type_part instanceof TClosure) { + if ($input_type_part instanceof TCallableArray + || $input_type_part instanceof TCallableString + || $input_type_part instanceof TCallableKeyedArray + || $input_type_part instanceof TCallableObject + ) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; + } + return false; + } + } + if ($input_type_part instanceof TCallableArray + || $input_type_part instanceof TCallableString + || $input_type_part instanceof TCallableKeyedArray + || $input_type_part instanceof TCallableObject + ) { + return true; + } + if ($container_type_part->is_pure && !$input_type_part->is_pure) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = $input_type_part->is_pure === null; diff --git a/tests/CallableTest.php b/tests/CallableTest.php index e10784b0b..eff0e838a 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1920,6 +1920,21 @@ class CallableTest extends TestCase 'ignored_issues' => [], 'php_version' => '8.0', ], + 'callableArrayPassedAsCallable' => [ + 'code' => <<<'PHP' + [ + 'callable', + "callable-array{0: class-string, 1: 'from'}", + ], + 'callableAcceptsCallableObject' => [ + 'callable', + "callable-object", + ], + 'callableAcceptsCallableString' => [ + 'callable', + 'callable-string', + ], + 'callableAcceptsCallableKeyedList' => [ + 'callable', + "callable-list{class-string, 'from'}", + ], ]; } From c9468b63e523dcdb67fd94f9bad877639460fa56 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Tue, 12 Mar 2024 12:51:49 +0100 Subject: [PATCH 2/2] Introduce `TCallableInterface` `TClosure` does not implement it at this point. It's intentional, to maintain the separation between callables and closures that our code relies on. --- .../Type/Comparator/AtomicTypeComparator.php | 10 +++------- .../Comparator/CallableTypeComparator.php | 20 +++++++------------ src/Psalm/Type/Atomic/TCallable.php | 2 +- src/Psalm/Type/Atomic/TCallableArray.php | 2 +- src/Psalm/Type/Atomic/TCallableInterface.php | 7 +++++++ src/Psalm/Type/Atomic/TCallableKeyedArray.php | 2 +- src/Psalm/Type/Atomic/TCallableList.php | 2 +- src/Psalm/Type/Atomic/TCallableObject.php | 2 +- src/Psalm/Type/Atomic/TCallableString.php | 2 +- 9 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 src/Psalm/Type/Atomic/TCallableInterface.php diff --git a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php index 85000bec7..15c451242 100644 --- a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -8,7 +8,7 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\Scalar; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; -use Psalm\Type\Atomic\TCallableArray; +use Psalm\Type\Atomic\TCallableInterface; use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableString; @@ -192,12 +192,8 @@ final class AtomicTypeComparator } if (($container_type_part instanceof TCallable - && ($input_type_part instanceof TCallable - || $input_type_part instanceof TCallableArray - || $input_type_part instanceof TCallableObject - || $input_type_part instanceof TCallableString - || $input_type_part instanceof TCallableKeyedArray - )) + && $input_type_part instanceof TCallableInterface + ) || ($container_type_part instanceof TClosure && $input_type_part instanceof TClosure) ) { diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index 393c83691..7573743ac 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -19,9 +19,7 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallableArray; -use Psalm\Type\Atomic\TCallableKeyedArray; -use Psalm\Type\Atomic\TCallableObject; -use Psalm\Type\Atomic\TCallableString; +use Psalm\Type\Atomic\TCallableInterface; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TKeyedArray; @@ -44,20 +42,18 @@ use function substr; final class CallableTypeComparator { /** - * @param TCallable|TClosure|TCallableArray|TCallableString|TCallableKeyedArray|TCallableObject $input_type_part + * @param TClosure|TCallableInterface $input_type_part * @param TCallable|TClosure $container_type_part */ public static function isContainedBy( Codebase $codebase, - Atomic $input_type_part, + $input_type_part, Atomic $container_type_part, ?TypeComparisonResult $atomic_comparison_result ): bool { if ($container_type_part instanceof TClosure) { - if ($input_type_part instanceof TCallableArray - || $input_type_part instanceof TCallableString - || $input_type_part instanceof TCallableKeyedArray - || $input_type_part instanceof TCallableObject + 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; @@ -65,10 +61,8 @@ final class CallableTypeComparator return false; } } - if ($input_type_part instanceof TCallableArray - || $input_type_part instanceof TCallableString - || $input_type_part instanceof TCallableKeyedArray - || $input_type_part instanceof TCallableObject + if ($input_type_part instanceof TCallableInterface + && !$input_type_part instanceof TCallable // it has stricter checks below ) { return true; } diff --git a/src/Psalm/Type/Atomic/TCallable.php b/src/Psalm/Type/Atomic/TCallable.php index 7ef847d4a..bcd925b26 100644 --- a/src/Psalm/Type/Atomic/TCallable.php +++ b/src/Psalm/Type/Atomic/TCallable.php @@ -14,7 +14,7 @@ use Psalm\Type\Union; * * @psalm-immutable */ -final class TCallable extends Atomic +final class TCallable extends Atomic implements TCallableInterface { use CallableTrait; diff --git a/src/Psalm/Type/Atomic/TCallableArray.php b/src/Psalm/Type/Atomic/TCallableArray.php index d4992523b..695a9d0e8 100644 --- a/src/Psalm/Type/Atomic/TCallableArray.php +++ b/src/Psalm/Type/Atomic/TCallableArray.php @@ -7,7 +7,7 @@ namespace Psalm\Type\Atomic; * * @psalm-immutable */ -final class TCallableArray extends TNonEmptyArray +final class TCallableArray extends TNonEmptyArray implements TCallableInterface { /** * @var string diff --git a/src/Psalm/Type/Atomic/TCallableInterface.php b/src/Psalm/Type/Atomic/TCallableInterface.php new file mode 100644 index 000000000..e25f076d1 --- /dev/null +++ b/src/Psalm/Type/Atomic/TCallableInterface.php @@ -0,0 +1,7 @@ +