From cc4461f756866712a90478001d6a5cb503127180 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 20 Nov 2022 19:01:49 +0100 Subject: [PATCH] Progress --- .../Statements/Expression/ArrayAnalyzer.php | 7 ++- .../Assignment/ArrayAssignmentAnalyzer.php | 2 +- .../ArrayMergeReturnTypeProvider.php | 33 ++++++---- .../Type/SimpleAssertionReconciler.php | 14 ++++- src/Psalm/Internal/Type/TypeCombiner.php | 18 ++++-- src/Psalm/Type.php | 8 ++- src/Psalm/Type/Atomic/TNonEmptyList.php | 63 ------------------- 7 files changed, 59 insertions(+), 86 deletions(-) delete mode 100644 src/Psalm/Type/Atomic/TNonEmptyList.php diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php index 5b89d9a60..9abd1b3d6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -148,7 +148,12 @@ class ArrayAnalyzer if ($array_creation_info->can_be_empty) { $array_type = new TList($item_value_type ?? Type::getMixed()); } else { - $array_type = new TNonEmptyList($item_value_type ?? Type::getMixed()); + $array_type = new TKeyedArray( + [$item_value_type ?? Type::getMixed()], + null, + [Type::getInt(), Type::getMixed()], + true + ); } $stmt_type = new Union([ diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 781368b80..cd8322c28 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -345,7 +345,7 @@ class ArrayAssignmentAnalyzer && $key_values[0] instanceof TLiteralInt ) { $key_value = $key_values[0]; - $count = ($type->count ?? $type->min_count) ?? 1; + $count = $type->getMinCount() ?? 1; if ($key_value->value < $count) { $has_matching_objectlike_property = true; diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index 600d04444..188b8ca51 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -179,15 +179,33 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac continue; } + if ($unpacked_type_part instanceof TMixed + && $unpacked_type_part->from_loop_isset + ) { + $unpacked_type_part = new TArray([ + Type::getArrayKey(), + Type::getMixed(true), + ]); + } if ($unpacked_type_part instanceof TList) { $all_keyed_arrays = false; - if ($unpacked_type_part instanceof TNonEmptyList && !$unpacking_possibly_empty) { - $any_nonempty = true; - } else { - $all_nonempty_lists = false; + if ($is_replace) { + foreach ($generic_properties as $key => $keyed_type) { + if (is_string($key)) { + continue; + } + $generic_properties[$key] = Type::combineUnionTypes( + $keyed_type, + $unpacked_type_part->type_param, + $codebase + ); + } } + + $all_nonempty_lists = false; + } elseif ($unpacked_type_part instanceof TArray) { if ($unpacked_type_part->isEmptyArray()) { continue; @@ -211,13 +229,6 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac if ($unpacked_type_part instanceof TNonEmptyArray && !$unpacking_possibly_empty) { $any_nonempty = true; } - } elseif ($unpacked_type_part instanceof TMixed - && $unpacked_type_part->from_loop_isset - ) { - $unpacked_type_part = new TArray([ - Type::getArrayKey(), - Type::getMixed(true), - ]); } else { return Type::getArray(); } diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index d25125a22..db0f638a4 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -722,15 +722,23 @@ class SimpleAssertionReconciler extends Reconciler $count ); + $existing_var_type->removeType('array'); $existing_var_type->addType( $non_empty_array ); } elseif ($array_atomic_type instanceof TList) { - $non_empty_list = new TNonEmptyList( - $array_atomic_type->type_param, - $count + $properties = []; + for ($x = 0; $x < $count; $x++) { + $properties []= $array_atomic_type->type_param; + } + $non_empty_list = new TKeyedArray( + $properties, + null, + null, + true ); + $existing_var_type->removeType('array'); $existing_var_type->addType( $non_empty_list ); diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index e196b2478..6ef3f1a41 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1520,13 +1520,22 @@ class TypeCombiner [Type::getInt(), $combination->array_type_params[1]], true ); + } else if ($combination->array_counts && count($combination->array_counts) === 1) { + $cnt = array_keys($combination->array_counts)[0]; + $properties = []; + for ($x = 0; $x < $cnt; $x++) { + $properties []= $generic_type_params[1]; + } + $array_type = new TKeyedArray( + $properties, + null, + null, + true + ); } else { /** @psalm-suppress ArgumentTypeCoercion */ $array_type = new TNonEmptyList( $generic_type_params[1], - $combination->array_counts && count($combination->array_counts) === 1 - ? array_keys($combination->array_counts)[0] - : null, $combination->array_min_counts ? min(array_keys($combination->array_min_counts)) : null @@ -1536,9 +1545,6 @@ class TypeCombiner /** @psalm-suppress ArgumentTypeCoercion */ $array_type = new TNonEmptyArray( $generic_type_params, - $combination->array_counts && count($combination->array_counts) === 1 - ? array_keys($combination->array_counts)[0] - : null, $combination->array_min_counts ? min(array_keys($combination->array_min_counts)) : null diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index b00696b0b..14ecdc61a 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -21,6 +21,7 @@ use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TIterable; +use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralClassString; use Psalm\Type\Atomic\TLiteralFloat; @@ -454,7 +455,12 @@ abstract class Type */ public static function getNonEmptyList(): Union { - $type = new TNonEmptyList(new Union([new TMixed])); + $type = new TKeyedArray( + [self::getMixed()], + null, + [self::getInt(), self::getMixed()], + true + ); return new Union([$type]); } diff --git a/src/Psalm/Type/Atomic/TNonEmptyList.php b/src/Psalm/Type/Atomic/TNonEmptyList.php deleted file mode 100644 index c56bd0b56..000000000 --- a/src/Psalm/Type/Atomic/TNonEmptyList.php +++ /dev/null @@ -1,63 +0,0 @@ -type_param = $type_param; - $this->count = $count; - $this->min_count = $min_count; - $this->from_docblock = $from_docblock; - } - - /** - * @param positive-int|null $count - * - * @return static - */ - public function setCount(?int $count): self - { - if ($count === $this->count) { - return $this; - } - $cloned = clone $this; - $cloned->count = $count; - return $cloned; - } - - public function getAssertionString(): string - { - return 'non-empty-list'; - } -}