From cac5a1037a203539ce65a96c5d3c118a28de8c0f Mon Sep 17 00:00:00 2001 From: RobChett Date: Sun, 14 May 2023 22:58:33 +0100 Subject: [PATCH 1/6] Remove TCallableArray and TCallableList --- UPGRADING.md | 1 + .../plugins/plugins_type_system.md | 2 -- .../Expression/Call/ArgumentsAnalyzer.php | 5 +--- .../Call/FunctionCallReturnTypeFetcher.php | 5 +--- .../Call/HighOrderFunctionArgHandler.php | 3 +-- .../Comparator/CallableTypeComparator.php | 10 -------- .../Type/SimpleAssertionReconciler.php | 5 ++-- .../Type/SimpleNegatedAssertionReconciler.php | 6 ++--- src/Psalm/Internal/Type/TypeCombiner.php | 24 ++++++++----------- src/Psalm/Type/Atomic.php | 4 +--- src/Psalm/Type/Atomic/TCallableArray.php | 16 ------------- 11 files changed, 20 insertions(+), 61 deletions(-) delete mode 100644 src/Psalm/Type/Atomic/TCallableArray.php diff --git a/UPGRADING.md b/UPGRADING.md index d7c70b165fa..c87c1fc4e00 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -10,6 +10,7 @@ - [BC] The only optional boolean parameter of `TKeyedArray::getGenericArrayType` was removed, and was replaced with a string parameter with a different meaning. - [BC] The `TDependentListKey` type was removed and replaced with an optional property of the `TIntRange` type. +- [BC] `TCallableArray` and `TCallableList` removed and replaced with `TCallableKeyedArray`. - [BC] Value of constant `Psalm\Type\TaintKindGroup::ALL_INPUT` changed to reflect new `TaintKind::INPUT_SLEEP` and `TaintKind::INPUT_XPATH` have been added. Accordingly, default values for `$taint` parameters of `Psalm\Codebase::addTaintSource()` and `Psalm\Codebase::addTaintSink()` have been changed as well. diff --git a/docs/running_psalm/plugins/plugins_type_system.md b/docs/running_psalm/plugins/plugins_type_system.md index 5cf70ad94e7..99e6fa6b807 100644 --- a/docs/running_psalm/plugins/plugins_type_system.md +++ b/docs/running_psalm/plugins/plugins_type_system.md @@ -183,8 +183,6 @@ $a = []; foreach (range(1,1) as $_) $a[(string)rand(0,1)] = rand(0,1); // array ``` -`TCallableArray` - denotes an array that is _also_ `callable`. - `TCallableKeyedArray` - denotes an object-like array that is _also_ `callable`. `TClassStringMap` - Represents an array where the type of each value is a function of its string key value diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index cbaab782dd3..cebfd09dd1e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -39,7 +39,6 @@ use Psalm\Type; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; -use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TKeyedArray; @@ -1528,9 +1527,7 @@ private static function checkArgCount( foreach ($arg_value_type->getAtomicTypes() as $atomic_arg_type) { $packed_var_definite_args_tmp = []; - if ($atomic_arg_type instanceof TCallableArray || - $atomic_arg_type instanceof TCallableKeyedArray - ) { + if ($atomic_arg_type instanceof TCallableKeyedArray) { $packed_var_definite_args_tmp[] = 2; } elseif ($atomic_arg_type instanceof TKeyedArray) { if ($atomic_arg_type->fallback_params !== null) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php index 943567b6f8d..61b90ca98f5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php @@ -25,7 +25,6 @@ use Psalm\Type; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; -use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; @@ -358,9 +357,7 @@ private static function getReturnTypeFromCallMapWithArgs( if (count($atomic_types) === 1) { if (isset($atomic_types['array'])) { - if ($atomic_types['array'] instanceof TCallableArray - || $atomic_types['array'] instanceof TCallableKeyedArray - ) { + if ($atomic_types['array'] instanceof TCallableKeyedArray) { return Type::getInt(false, 2); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php index 3d1a51c4e67..1f43ee90312 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php @@ -292,8 +292,7 @@ private static function isSupported(FunctionLikeParameter $container_param): boo return false; } - if ($a instanceof Type\Atomic\TCallableArray || - $a instanceof Type\Atomic\TCallableString || + if ($a instanceof Type\Atomic\TCallableString || $a instanceof Type\Atomic\TCallableKeyedArray ) { return false; diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index 84dd4616c5a..89fe3362773 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -18,7 +18,6 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; -use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TKeyedArray; @@ -188,15 +187,6 @@ public static function isNotExplicitlyCallableTypeCallable( if (!$input_type_part->type_params[1]->hasString()) { return false; } - - if (!$input_type_part instanceof TCallableArray) { - if ($atomic_comparison_result) { - $atomic_comparison_result->type_coerced_from_mixed = true; - $atomic_comparison_result->type_coerced = true; - } - - return false; - } } elseif ($input_type_part instanceof TKeyedArray) { $method_id = self::getCallableMethodIdFromTKeyedArray($input_type_part); diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 76ea3a9986e..35ed8ea8fb9 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -34,7 +34,6 @@ use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TBool; 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; @@ -2651,11 +2650,11 @@ private static function reconcileCallable( ) { $callable_types[] = $type; $redundant = false; - } elseif ($type instanceof TArray) { + } /*elseif ($type instanceof TArray) { $type = new TCallableArray($type->type_params); $callable_types[] = $type; $redundant = false; - } elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { + } */elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { $type = new TCallableKeyedArray($type->properties); $callable_types[] = $type; $redundant = false; diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 26955bf6d9a..93eaf8a9021 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -26,7 +26,7 @@ use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TBool; 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\TEmptyMixed; @@ -1187,7 +1187,7 @@ private static function reconcileObject( $non_object_types[] = $type; } } elseif ($type instanceof TCallable) { - $non_object_types[] = new TCallableArray([ + $non_object_types[] = new TCallableKeyedArray([ Type::getArrayKey(), Type::getMixed(), ]); @@ -1586,7 +1586,7 @@ private static function reconcileString( $non_string_types[] = new TInt(); $redundant = false; } elseif ($type instanceof TCallable) { - $non_string_types[] = new TCallableArray([ + $non_string_types[] = new TCallableKeyedArray([ Type::getArrayKey(), Type::getMixed(), ]); diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index b32ad3b3d46..e1dbd1b63f6 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -11,7 +11,6 @@ use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TBool; 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; @@ -400,7 +399,6 @@ private static function scrapeTypeProperties( bool $allow_mixed_union, int $literal_limit ): ?Union { - if ($type instanceof TMixed) { if ($type->from_loop_isset) { if ($combination->mixed_from_loop_isset === null) { @@ -544,11 +542,17 @@ private static function scrapeTypeProperties( } } - if ($type instanceof TArray && $type_key === 'array') { - if ($type instanceof TCallableArray && isset($combination->value_types['callable'])) { + if ($type instanceof TCallableKeyedArray) { + if (isset($combination->value_types['callable'])) { return null; } - + if ($combination->all_arrays_callable !== false) { + $combination->all_arrays_callable = true; + } else { + $combination->all_arrays_callable = false; + } + } + if ($type instanceof TArray && $type_key === 'array') { foreach ($type->type_params as $i => $type_param) { // See https://github.com/vimeo/psalm/pull/9439#issuecomment-1464563015 /** @psalm-suppress PropertyTypeCoercion */ @@ -587,14 +591,6 @@ private static function scrapeTypeProperties( $combination->all_arrays_class_string_maps = false; } - if ($type instanceof TCallableArray) { - if ($combination->all_arrays_callable !== false) { - $combination->all_arrays_callable = true; - } - } else { - $combination->all_arrays_callable = false; - } - return null; } @@ -1525,7 +1521,7 @@ private static function getArrayTypeFromGenericParams( } if ($combination->all_arrays_callable) { - $array_type = new TCallableArray($generic_type_params); + $array_type = new TCallableKeyedArray($generic_type_params); } elseif ($combination->array_always_filled || ($combination->array_sometimes_filled && $overwrite_empty_array) || ($combination->objectlike_entries diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 222416cfee2..c2b12b23d56 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -16,7 +16,6 @@ use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TBool; 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; @@ -258,7 +257,7 @@ private static function createInner( ]); case 'callable-array': - return new TCallableArray([ + return new TCallableKeyedArray([ new Union([new TArrayKey($from_docblock)]), new Union([new TMixed(false, $from_docblock)]), ]); @@ -459,7 +458,6 @@ public function isCallableType(): bool return $this instanceof TCallable || $this instanceof TCallableObject || $this instanceof TCallableString - || $this instanceof TCallableArray || $this instanceof TCallableKeyedArray || $this instanceof TClosure; } diff --git a/src/Psalm/Type/Atomic/TCallableArray.php b/src/Psalm/Type/Atomic/TCallableArray.php deleted file mode 100644 index d4992523b06..00000000000 --- a/src/Psalm/Type/Atomic/TCallableArray.php +++ /dev/null @@ -1,16 +0,0 @@ - Date: Sun, 14 May 2023 23:35:45 +0100 Subject: [PATCH 2/6] Improve destructured type of callable-array --- .../Type/SimpleNegatedAssertionReconciler.php | 9 +++--- src/Psalm/Internal/Type/TypeCombiner.php | 6 ++-- src/Psalm/Internal/Type/TypeParser.php | 1 - src/Psalm/Type/Atomic.php | 14 ++++++++-- src/Psalm/Type/Atomic/TCallableKeyedArray.php | 26 ++++++++++++++++- tests/CallableTest.php | 28 +++++++++++++------ 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 93eaf8a9021..8308d915775 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -29,6 +29,7 @@ use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableString; +use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyNumeric; use Psalm\Type\Atomic\TEmptyScalar; @@ -1188,8 +1189,8 @@ private static function reconcileObject( } } elseif ($type instanceof TCallable) { $non_object_types[] = new TCallableKeyedArray([ - Type::getArrayKey(), - Type::getMixed(), + new Union([new TClassString, new TObject]), + Type::getString(), ]); $non_object_types[] = new TCallableString(); $redundant = false; @@ -1587,8 +1588,8 @@ private static function reconcileString( $redundant = false; } elseif ($type instanceof TCallable) { $non_string_types[] = new TCallableKeyedArray([ - Type::getArrayKey(), - Type::getMixed(), + new Union([new TClassString, new TObject]), + Type::getString(), ]); $non_string_types[] = new TCallableObject(); $redundant = false; diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index e1dbd1b63f6..f63d26d1378 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -591,6 +591,7 @@ private static function scrapeTypeProperties( $combination->all_arrays_class_string_maps = false; } + $combination->all_arrays_callable = false; return null; } @@ -952,8 +953,8 @@ private static function scrapeTypeProperties( if ($type instanceof TCallable && $type_key === 'callable') { if (($combination->value_types['string'] ?? null) instanceof TCallableString) { unset($combination->value_types['string']); - } elseif (!empty($combination->array_type_params) && $combination->all_arrays_callable) { - $combination->array_type_params = []; + } elseif (!empty($combination->objectlike_entries) && $combination->all_arrays_callable) { + $combination->objectlike_entries = []; } elseif (isset($combination->value_types['callable-object'])) { unset($combination->value_types['callable-object']); } @@ -1408,7 +1409,6 @@ private static function handleKeyedArrayEntries( $sealed || $fallback_key_type === null || $fallback_value_type === null ? null : [$fallback_key_type, $fallback_value_type], - (bool)$combination->all_arrays_lists, $from_docblock, ); } else { diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 99cf82a4d51..e4fae85ef3b 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -1411,7 +1411,6 @@ private static function getTypeFromKeyedArrayTree( !is_numeric($property_key) || ($had_optional && !$property_maybe_undefined) || $type === 'array' - || $type === 'callable-array' || $previous_property_key != ($property_key - 1) ) ) { diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index c2b12b23d56..936efacb245 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -257,9 +257,19 @@ private static function createInner( ]); case 'callable-array': + $classString = new TClassString( + 'object', + null, + false, + false, + false, + true + ); + $object = new TObject(true); + $string = new TString(true); return new TCallableKeyedArray([ - new Union([new TArrayKey($from_docblock)]), - new Union([new TMixed(false, $from_docblock)]), + new Union([$classString, $object]), + new Union([$string]), ]); case 'list': diff --git a/src/Psalm/Type/Atomic/TCallableKeyedArray.php b/src/Psalm/Type/Atomic/TCallableKeyedArray.php index b549c940357..a4bf95c73f2 100644 --- a/src/Psalm/Type/Atomic/TCallableKeyedArray.php +++ b/src/Psalm/Type/Atomic/TCallableKeyedArray.php @@ -2,6 +2,8 @@ namespace Psalm\Type\Atomic; +use Psalm\Type\Union; + /** * Denotes an object-like array that is _also_ `callable`. * @@ -10,5 +12,27 @@ final class TCallableKeyedArray extends TKeyedArray { protected const NAME_ARRAY = 'callable-array'; - protected const NAME_LIST = 'callable-list'; + protected const NAME_LIST = 'callable-array'; + + /** + * Constructs a new instance of a generic type + * + * @param non-empty-array $properties + * @param array{Union, Union}|null $fallback_params + * @param array $class_strings + */ + public function __construct( + array $properties, + ?array $class_strings = null, + ?array $fallback_params = null, + bool $from_docblock = false + ) { + parent::__construct( + $properties, + $class_strings, + $fallback_params, + true, + $from_docblock, + ); + } } diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 5815222b1ad..bb065224def 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1827,16 +1827,16 @@ function withVariadic(int $a, int $b, int ...$rest): int { return 0; } - + /** @param Closure(int, int): int $f */ function int_int(Closure $f): void {} - + /** @param Closure(int, int, int): int $f */ function int_int_int(Closure $f): void {} - + /** @param Closure(int, int, int, int): int $f */ function int_int_int_int(Closure $f): void {} - + int_int(withVariadic(...)); int_int_int(withVariadic(...)); int_int_int_int(withVariadic(...));', @@ -1844,6 +1844,18 @@ function int_int_int_int(Closure $f): void {} 'ignored_issues' => [], 'php_version' => '8.0', ], + 'callableArrayTypes' => [ + 'code' => ' [ + '$a' => 'class-string|object', + '$b' => 'string', + '$c' => 'list{class-string|object, string}', + ], + ] ]; } @@ -2291,16 +2303,16 @@ function add(int $a, int $b, int ...$rest): int { return 0; } - + /** @param Closure(int, int, string, int, int): int $f */ function int_int_string_int_int(Closure $f): void {} - + /** @param Closure(int, int, int, string, int): int $f */ function int_int_int_string_int(Closure $f): void {} - + /** @param Closure(int, int, int, int, string): int $f */ function int_int_int_int_string(Closure $f): void {} - + int_int_string_int_int(add(...)); int_int_int_string_int(add(...)); int_int_int_int_string(add(...));', From 922e57d86b7849d58e479f202f3d04791e91b667 Mon Sep 17 00:00:00 2001 From: RobChett Date: Sun, 14 May 2023 23:44:12 +0100 Subject: [PATCH 3/6] Formatting --- src/Psalm/Internal/Type/SimpleAssertionReconciler.php | 6 +----- src/Psalm/Type/Atomic.php | 2 +- tests/CallableTest.php | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 35ed8ea8fb9..074096c46bb 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -2650,11 +2650,7 @@ private static function reconcileCallable( ) { $callable_types[] = $type; $redundant = false; - } /*elseif ($type instanceof TArray) { - $type = new TCallableArray($type->type_params); - $callable_types[] = $type; - $redundant = false; - } */elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { + } elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { $type = new TCallableKeyedArray($type->properties); $callable_types[] = $type; $redundant = false; diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 936efacb245..f7ad08526d8 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -263,7 +263,7 @@ private static function createInner( false, false, false, - true + true, ); $object = new TObject(true); $string = new TString(true); diff --git a/tests/CallableTest.php b/tests/CallableTest.php index bb065224def..a6c4f421fde 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1855,7 +1855,7 @@ function int_int_int_int(Closure $f): void {} '$b' => 'string', '$c' => 'list{class-string|object, string}', ], - ] + ], ]; } From df6b5fbb759af61d9355d5cd87cfd521196b8f83 Mon Sep 17 00:00:00 2001 From: RobChett Date: Mon, 15 May 2023 01:58:04 +0100 Subject: [PATCH 4/6] Update tests for new callable-array shape --- tests/TypeParseTest.php | 2 +- tests/TypeReconciliation/ReconcilerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TypeParseTest.php b/tests/TypeParseTest.php index b65e63d2410..3aecbf7b08b 100644 --- a/tests/TypeParseTest.php +++ b/tests/TypeParseTest.php @@ -480,7 +480,7 @@ public function testTKeyedArrayNonList(): void public function testTKeyedCallableArrayNonList(): void { $this->assertSame( - 'callable-array{0: class-string, 1: string}', + 'callable-array{class-string, string}', (string)Type::parseString('callable-array{0: class-string, 1: string}'), ); } diff --git a/tests/TypeReconciliation/ReconcilerTest.php b/tests/TypeReconciliation/ReconcilerTest.php index a3b722ff3fd..1eb3a1031b9 100644 --- a/tests/TypeReconciliation/ReconcilerTest.php +++ b/tests/TypeReconciliation/ReconcilerTest.php @@ -156,7 +156,7 @@ public function providerTestReconcilation(): array 'nullableClassStringTruthy' => ['class-string', new Truthy(), 'class-string|null'], 'iterableToArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'iterable'], 'iterableToTraversable' => ['Traversable', new IsType(new TNamedObject('Traversable')), 'iterable'], - 'callableToCallableArray' => ['callable-array{0: class-string|object, 1: string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], + 'callableToCallableArray' => ['callable-array{class-string|object, string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], 'SmallKeyedArrayAndCallable' => ['array{test: string}', new IsType(new TKeyedArray(['test' => Type::getString()])), 'callable'], 'BigKeyedArrayAndCallable' => ['array{foo: string, test: string, thing: string}', new IsType(new TKeyedArray(['foo' => Type::getString(), 'test' => Type::getString(), 'thing' => Type::getString()])), 'callable'], 'callableOrArrayToCallableArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable|array'], From 889bdca4618ef3d24c34aa6a00eb810f8b394be6 Mon Sep 17 00:00:00 2001 From: robchett Date: Sat, 17 Jun 2023 09:44:45 +0100 Subject: [PATCH 5/6] The function in a callable-array is a non-empty-string --- src/Psalm/Internal/Type/SimpleAssertionReconciler.php | 4 ++-- src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php | 4 ++-- src/Psalm/Type/Atomic.php | 2 +- tests/TypeReconciliation/ReconcilerTest.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 074096c46bb..eb45e931a15 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -2304,7 +2304,7 @@ private static function reconcileArray( } elseif ($type instanceof TCallable) { $array_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $redundant = false; @@ -2428,7 +2428,7 @@ private static function reconcileList( } elseif ($type instanceof TCallable) { $array_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $redundant = false; diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 8308d915775..eee9e3a169e 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -1190,7 +1190,7 @@ private static function reconcileObject( } elseif ($type instanceof TCallable) { $non_object_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $non_object_types[] = new TCallableString(); $redundant = false; @@ -1589,7 +1589,7 @@ private static function reconcileString( } elseif ($type instanceof TCallable) { $non_string_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $non_string_types[] = new TCallableObject(); $redundant = false; diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index f7ad08526d8..6889a8f17d8 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -266,7 +266,7 @@ private static function createInner( true, ); $object = new TObject(true); - $string = new TString(true); + $string = new TNonEmptyString(true); return new TCallableKeyedArray([ new Union([$classString, $object]), new Union([$string]), diff --git a/tests/TypeReconciliation/ReconcilerTest.php b/tests/TypeReconciliation/ReconcilerTest.php index 1eb3a1031b9..0f6e9e88065 100644 --- a/tests/TypeReconciliation/ReconcilerTest.php +++ b/tests/TypeReconciliation/ReconcilerTest.php @@ -156,7 +156,7 @@ public function providerTestReconcilation(): array 'nullableClassStringTruthy' => ['class-string', new Truthy(), 'class-string|null'], 'iterableToArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'iterable'], 'iterableToTraversable' => ['Traversable', new IsType(new TNamedObject('Traversable')), 'iterable'], - 'callableToCallableArray' => ['callable-array{class-string|object, string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], + 'callableToCallableArray' => ['callable-array{class-string|object, non-empty-string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], 'SmallKeyedArrayAndCallable' => ['array{test: string}', new IsType(new TKeyedArray(['test' => Type::getString()])), 'callable'], 'BigKeyedArrayAndCallable' => ['array{foo: string, test: string, thing: string}', new IsType(new TKeyedArray(['foo' => Type::getString(), 'test' => Type::getString(), 'thing' => Type::getString()])), 'callable'], 'callableOrArrayToCallableArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable|array'], From 0f8a3204f37c2290f96979bec099315037e5b176 Mon Sep 17 00:00:00 2001 From: robchett Date: Tue, 17 Oct 2023 21:47:12 +0100 Subject: [PATCH 6/6] Rollback some changes --- src/Psalm/Internal/Type/SimpleAssertionReconciler.php | 4 ++++ src/Psalm/Internal/Type/TypeParser.php | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index eb45e931a15..d9209720230 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -2650,6 +2650,10 @@ private static function reconcileCallable( ) { $callable_types[] = $type; $redundant = false; + } elseif ($type instanceof TArray) { + $type = new TCallableKeyedArray($type->type_params); + $callable_types[] = $type; + $redundant = false; } elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { $type = new TCallableKeyedArray($type->properties); $callable_types[] = $type; diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index e4fae85ef3b..99cf82a4d51 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -1411,6 +1411,7 @@ private static function getTypeFromKeyedArrayTree( !is_numeric($property_key) || ($had_optional && !$property_maybe_undefined) || $type === 'array' + || $type === 'callable-array' || $previous_property_key != ($property_key - 1) ) ) {