diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index 96bac15be..b4672a2d7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -386,8 +386,6 @@ class ForeachAnalyzer /** * @param PhpParser\Node\Stmt\Foreach_|PhpParser\Node\Expr\YieldFrom $stmt * - * @psalm-suppress ComplexMethod - * * @return false|null */ public static function checkIteratorType( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 04301d8c4..fe7396406 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -597,21 +597,30 @@ class ArrayAssignmentAnalyzer && $atomic_root_types['array']->isNonEmpty() ) ) { + $prop_count = null; + if ($atomic_root_types['array'] instanceof TNonEmptyArray) { + $prop_count = $atomic_root_types['array']->count; + } else { + $min_count = $atomic_root_types['array']->getMinCount(); + if ($min_count === $atomic_root_types['array']->getMaxCount()) { + $prop_count = $min_count; + } + } if ($array_atomic_type_array) { $array_atomic_type = new TNonEmptyArray( $array_atomic_type_array, - $atomic_root_types['array']->count + $prop_count ); - } else { + } else if ($prop_count !== null) { $array_atomic_type = new TKeyedArray( array_fill( 0, - $atomic_root_types['array']->count, + $prop_count, $array_atomic_type_list ), null, [ - new Union([new TIntRange($atomic_root_types['array']->count, null)]), + new Union([new TIntRange($prop_count, null)]), $array_atomic_type_list ], true @@ -623,7 +632,7 @@ class ArrayAssignmentAnalyzer if ($array_atomic_type_array) { $array_atomic_type = new TNonEmptyArray( $array_atomic_type_array, - $atomic_root_types['array']->count + count($atomic_root_types['array']->properties) ); } elseif ($atomic_root_types['array']->is_list) { $array_atomic_type = $atomic_root_types['array']; @@ -634,12 +643,12 @@ class ArrayAssignmentAnalyzer $array_atomic_type = new TKeyedArray( array_fill( 0, - $atomic_root_types['array']->count, + count($atomic_root_types['array']->properties), $array_atomic_type_list ), null, [ - new Union([new TIntRange($atomic_root_types['array']->count, null)]), + new Union([new TIntRange(count($atomic_root_types['array']->properties), null)]), $array_atomic_type_list ], true diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 822bfb18f..89783dae5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -127,8 +127,8 @@ class ArgumentAnalyzer $array_type = $param_type->getAtomicTypes()['array']; if ($array_type instanceof TKeyedArray && $array_type->is_list) { - $param_type = $array_type->type_param; - } else { + $param_type = $array_type->getGenericValueType(); + } elseif ($array_type instanceof TArray) { $param_type = $array_type->type_params[1]; } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php index 694a23154..32545bb79 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -225,7 +225,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface if (($array_arg_atomic_type instanceof TKeyedArray && $array_arg_atomic_type->is_list) || count($call_args) !== 2 ) { - if ($array_arg_atomic_type->isNonEmpty()) { + if ($array_arg_atomic_type instanceof TKeyedArray && $array_arg_atomic_type->isNonEmpty()) { return Type::getNonEmptyList( $mapping_return_type ); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index e95108cb1..ea052d166 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -217,10 +217,6 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac $inner_value_type = null; if ($inner_key_types) { - /** - * Truthy&array-shape-list doesn't reconcile correctly, will be fixed for 5.x by #8050. - * @psalm-suppress InvalidScalarArgument - */ $inner_key_type = TypeCombiner::combine($inner_key_types, $codebase, true); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php index 15138def2..0ce77dada 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php @@ -53,7 +53,7 @@ class ArrayReverseReturnTypeProvider implements FunctionReturnTypeProviderInterf return $first_arg_type; } - if ($first_arg_array instanceof TKeyedArray && $first_arg_array->is_list) { + if ($first_arg_array->is_list) { $second_arg = $call_args[1]->value ?? null; if (!$second_arg @@ -64,7 +64,7 @@ class ArrayReverseReturnTypeProvider implements FunctionReturnTypeProviderInterf return $first_arg_type; } - return $first_arg_array->setProperties(array_reverse($first_arg_array->properties)); + return new Union([$first_arg_array->setProperties(array_reverse($first_arg_array->properties))]); } return new Union([$first_arg_array->getGenericArrayType()]); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php index beaea1718..ee0fd0ba6 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php @@ -33,7 +33,7 @@ class ArraySpliceReturnTypeProvider implements FunctionReturnTypeProviderInterfa $first_arg = $call_args[0]->value ?? null; - $first_arg_array = $first_arg + $array_type = $first_arg && ($first_arg_type = $statements_source->node_data->getType($first_arg)) && $first_arg_type->hasType('array') && ($array_atomic_type = $first_arg_type->getAtomicTypes()['array']) @@ -42,18 +42,12 @@ class ArraySpliceReturnTypeProvider implements FunctionReturnTypeProviderInterfa ? $array_atomic_type : null; - if (!$first_arg_array) { + if (!$array_type) { return Type::getArray(); } - if ($first_arg_array instanceof TKeyedArray) { - $first_arg_array = $first_arg_array->getGenericArrayType(); - } - - if ($first_arg_array instanceof TArray) { - $array_type = new TArray($first_arg_array->type_params); - } else { - $array_type = new TArray([Type::getInt(), $first_arg_array->type_param]); + if ($array_type instanceof TKeyedArray) { + $array_type = $array_type->getGenericArrayType(); } if (!$array_type->type_params[0]->hasString()) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php index 705fcbf0d..edf13037e 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php @@ -55,24 +55,6 @@ class ArrayUniqueReturnTypeProvider implements FunctionReturnTypeProviderInterfa return new Union([$first_arg_array]); } - if ($first_arg_array instanceof TKeyedArray && $first_arg_array->is_list) { - if (!$first_arg_array->properties[0]->possibly_undefined) { - return new Union([ - new TNonEmptyArray([ - Type::getInt(), - $first_arg_array->type_param - ]) - ]); - } - - return new Union([ - new TArray([ - Type::getInt(), - $first_arg_array->type_param - ]) - ]); - } - return new Union([$first_arg_array->getGenericArrayType()]); } } diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index 29d7c1413..1276c8119 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -3,22 +3,16 @@ namespace Psalm\Internal\Type\Comparator; use Psalm\Codebase; -use Psalm\Type; use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TClassStringMap; use Psalm\Type\Atomic\TKeyedArray; -use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; -use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Union; -use function array_map; -use function range; - /** * @internal */ @@ -128,40 +122,7 @@ class ArrayTypeComparator ); } - if ($input_type_part instanceof TList - && $container_type_part instanceof TList - ) { - if (!UnionTypeComparator::isContainedBy( - $codebase, - $input_type_part->type_param, - $container_type_part->type_param, - $input_type_part->type_param->ignore_nullable_issues, - $input_type_part->type_param->ignore_falsable_issues, - $atomic_comparison_result, - $allow_interface_equality - )) { - return false; - } - - return $input_type_part instanceof TNonEmptyList - || !$container_type_part instanceof TNonEmptyList; - } - if ($container_type_part instanceof TKeyedArray) { - if ($container_type_part->is_list) { - $container_type_part = $container_type_part->isNonEmpty() - ? Type::getNonEmptyListAtomic($container_type_part->getGenericValueType()) - : Type::getListAtomic($container_type_part->getGenericValueType()); - - return self::isContainedBy( - $codebase, - $input_type_part, - $container_type_part, - $allow_interface_equality, - $atomic_comparison_result - ); - } - $container_type_part = $container_type_part->getGenericArrayType(); } @@ -183,37 +144,6 @@ class ArrayTypeComparator ]); } - if ($container_type_part instanceof TList) { - $all_types_contain = false; - - if ($atomic_comparison_result) { - $atomic_comparison_result->type_coerced = true; - } - - $container_type_part = new TArray([Type::getInt(), $container_type_part->type_param]); - } - - if ($input_type_part instanceof TList) { - if ($input_type_part instanceof TNonEmptyList) { - // if the array has a known size < 10, make sure the array keys are literal ints - if ($input_type_part->count !== null && $input_type_part->count < 10) { - $literal_ints = array_map( - static fn($i): TLiteralInt => new TLiteralInt($i), - range(0, $input_type_part->count - 1) - ); - - $input_type_part = new TNonEmptyArray([ - new Union($literal_ints), - $input_type_part->type_param - ]); - } else { - $input_type_part = new TNonEmptyArray([Type::getInt(), $input_type_part->type_param]); - } - } else { - $input_type_part = new TArray([Type::getInt(), $input_type_part->type_param]); - } - } - foreach ($input_type_part->type_params as $i => $input_param) { if ($i > 1) { break; diff --git a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php index 27e048295..94e00ebc5 100644 --- a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -20,13 +20,11 @@ use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TKeyOf; use Psalm\Type\Atomic\TKeyedArray; -use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; -use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObjectWithProperties; @@ -740,18 +738,6 @@ class AtomicTypeComparator Atomic $type2_part, bool $allow_interface_equality = true ): bool { - if ((get_class($type1_part) === TList::class - && $type2_part instanceof TNonEmptyList) - || (get_class($type2_part) === TList::class - && $type1_part instanceof TNonEmptyList) - ) { - return UnionTypeComparator::canExpressionTypesBeIdentical( - $codebase, - $type1_part->type_param, - $type2_part->type_param - ); - } - if ((get_class($type1_part) === TArray::class && $type2_part instanceof TNonEmptyArray) || (get_class($type2_part) === TArray::class diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index a9a0352a7..fa0ed4d39 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -46,7 +46,6 @@ 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\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TLowercaseString; @@ -359,7 +358,7 @@ class SimpleAssertionReconciler extends Reconciler if ($assertion_type instanceof TKeyedArray && $assertion_type->is_list - && $assertion_type->type_param->isMixed() + && $assertion_type->getGenericValueType()->isMixed() ) { return self::reconcileList( $assertion, @@ -690,6 +689,9 @@ class SimpleAssertionReconciler extends Reconciler return $existing_var_type->freeze(); } + /** + * @param array $suppressed_issues + */ private static function reconcileExactlyCountable( Union $existing_var_type, HasExactCount $assertion, @@ -747,12 +749,10 @@ class SimpleAssertionReconciler extends Reconciler $array_atomic_type->properties ) )); - } elseif ($existing_var_type->is_list) { + } elseif ($array_atomic_type->is_list) { $properties = $array_atomic_type->properties; for ($x = $prop_min_count; $x < $assertion->count; $x++) { - $properties[$x] = isset($properties[$x]) - ? $properties[$x]->setPossiblyUndefined(false) - : $array_atomic_type->fallback_params[1]; + $properties[$x] = $properties[$x]->setPossiblyUndefined(false); } $array_atomic_type = new TKeyedArray( $properties, diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 92a6e81e0..01817f9a4 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -59,13 +59,10 @@ use Psalm\Type\Reconciler; use Psalm\Type\Union; use function assert; -use function count; use function get_class; use function max; use function strpos; -use const INF; - /** * @internal */ diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 8c1bf8299..891f9faac 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1488,12 +1488,21 @@ class TypeCombiner true ); } else { - /** @psalm-suppress ArgumentTypeCoercion */ - $array_type = Type::getNonEmptyListAtomic( - $generic_type_params[1], - $combination->array_min_counts - ? min(array_keys($combination->array_min_counts)) - : null + $cnt = $combination->array_min_counts + ? min(array_keys($combination->array_min_counts)) + : 0; + $properties = []; + for ($x = 0; $x < $cnt; $x++) { + $properties []= $generic_type_params[1]; + } + if (!$properties) { + $properties []= $generic_type_params[1]->setPossiblyUndefined(true); + } + $array_type = new TKeyedArray( + $properties, + null, + [new Union([new TIntRange($cnt, null)]), $generic_type_params[1]], + true ); } } else { diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 011a6972c..a3ea4e7d2 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -464,7 +464,8 @@ abstract class Type [$of->setPossiblyUndefined(true)], null, [self::getInt(), $of], - true + true, + $from_docblock ); } @@ -477,26 +478,11 @@ abstract class Type [$of->setPossiblyUndefined(false)], null, [self::getInt(), $of], - true + true, + $from_docblock ); } - /** - * @psalm-pure - */ - public static function getCallableListAtomic(Union $of, bool $from_docblock = false): Atomic - { - // The following code will be uncommented in Psalm 5.1 - //$of = $of->setPossiblyUndefined(false); - //return new TCallableKeyedArray( - // [$of, $of], - // null, - // [self::getInt(), $of], - // true - //); - return new TCallableList($of, null, null, $from_docblock); - } - /** * @psalm-pure */ diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index cafa7e459..18f5f4ea4 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -370,6 +370,9 @@ class TKeyedArray extends Atomic return false; } + /** + * @return int<0, max> + */ public function getMinCount(): int { if ($this->is_list) { @@ -380,7 +383,7 @@ class TKeyedArray extends Atomic } } $prop_min_count = 0; - foreach ($this->properties as $k => $property) { + foreach ($this->properties as $property) { if (!$property->possibly_undefined) { $prop_min_count++; } @@ -390,6 +393,7 @@ class TKeyedArray extends Atomic /** * Returns null if there is no upper limit. + * @return int<1, max>|null */ public function getMaxCount(): ?int {