diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 5e5b62247a3..c259752a44d 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -8,6 +8,8 @@ use Psalm\Context; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayChunkReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayColumnReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\ArrayCombineReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\ArrayFillKeysReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayFillReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayFilterReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayMapReturnTypeProvider; @@ -68,6 +70,7 @@ public function __construct() $this->registerClass(ArrayChunkReturnTypeProvider::class); $this->registerClass(ArrayColumnReturnTypeProvider::class); + $this->registerClass(ArrayCombineReturnTypeProvider::class); $this->registerClass(ArrayFilterReturnTypeProvider::class); $this->registerClass(ArrayMapReturnTypeProvider::class); $this->registerClass(ArrayMergeReturnTypeProvider::class); @@ -81,6 +84,7 @@ public function __construct() $this->registerClass(ArrayReverseReturnTypeProvider::class); $this->registerClass(ArrayUniqueReturnTypeProvider::class); $this->registerClass(ArrayFillReturnTypeProvider::class); + $this->registerClass(ArrayFillKeysReturnTypeProvider::class); $this->registerClass(FilterVarReturnTypeProvider::class); $this->registerClass(IteratorToArrayReturnTypeProvider::class); $this->registerClass(ParseUrlReturnTypeProvider::class); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 4c599488444..a53cd915846 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -2,17 +2,21 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; +use Psalm\CodeLocation; +use Psalm\Context; +use Psalm\Internal\Analyzer\SourceAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; 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\TNonEmptyArray; use Psalm\Type\Union; use function count; -use function reset; /** * @internal @@ -36,42 +40,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev ) { return Type::getMixed(); } - - $row_type = $row_shape = null; - $input_array_not_empty = false; - - // calculate row shape - if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) - && $first_arg_type->isSingle() - && $first_arg_type->hasArray() - ) { - $input_array = $first_arg_type->getArray(); - if ($input_array instanceof TKeyedArray) { - $row_type = $input_array->getGenericValueType(); - } elseif ($input_array instanceof TArray) { - $row_type = $input_array->type_params[1]; - } - - if ($row_type && $row_type->isSingle()) { - if ($row_type->hasArray()) { - $row_shape = $row_type->getArray(); - } elseif ($row_type->hasObjectType()) { - $row_shape_union = GetObjectVarsReturnTypeProvider::getGetObjectVarsReturnType( - $row_type, - $statements_source, - $event->getContext(), - $event->getCodeLocation() - ); - if ($row_shape_union->isSingle()) { - $row_shape_union_parts = $row_shape_union->getAtomicTypes(); - $row_shape = reset($row_shape_union_parts); - } - } - } - - $input_array_not_empty = $input_array instanceof TNonEmptyArray || - ($input_array instanceof TKeyedArray && $input_array->isNonEmpty()); - } + $context = $event->getContext(); + $code_location = $event->getCodeLocation(); $value_column_name = null; $value_column_name_is_null = false; @@ -86,6 +56,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } $key_column_name = null; + $key_column_name_is_null = false; $third_arg_type = null; // calculate key column name if (isset($call_args[2])) { @@ -97,9 +68,125 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } elseif ($third_arg_type->isSingleStringLiteral()) { $key_column_name = $third_arg_type->getSingleStringLiteral()->value; } + $key_column_name_is_null = $third_arg_type->isNull(); } } + + $row_type = $row_shape = null; + $input_array_not_empty = false; + + // calculate row shape + if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) + && $first_arg_type->isSingle() + && $first_arg_type->hasArray() + ) { + $input_array = $first_arg_type->getArray(); + if ($input_array instanceof TKeyedArray && !$input_array->fallback_params + && ($value_column_name !== null || $value_column_name_is_null) + && !($third_arg_type && !$key_column_name) + ) { + $properties = []; + $ok = true; + $last_custom_key = -1; + $is_list = $input_array->is_list || $key_column_name !== null; + $had_possibly_undefined = false; + foreach ($input_array->properties as $key => $property) { + $row_shape = self::getRowShape( + $property, + $statements_source, + $context, + $code_location + ); + if (!$row_shape) { + continue; + } + if (!$row_shape instanceof TKeyedArray) { + if ($row_shape instanceof TArray && $row_shape->isEmptyArray()) { + continue; + } + $ok = false; + break; + } + + if ($value_column_name !== null) { + if (isset($row_shape->properties[$value_column_name])) { + $result_element_type = $row_shape->properties[$value_column_name]; + } elseif ($row_shape->fallback_params) { + $ok = false; + break; + } else { + continue; + } + } else { + $result_element_type = $property; + } + + if ($key_column_name !== null) { + if (isset($row_shape->properties[$key_column_name])) { + $result_key_type = $row_shape->properties[$key_column_name]; + if ($result_key_type->isSingleIntLiteral()) { + $key = $result_key_type->getSingleIntLiteral()->value; + if ($is_list && $last_custom_key != $key-1) { + $is_list = false; + } + $last_custom_key = $key; + } elseif ($result_key_type->isSingleStringLiteral()) { + $key = $result_key_type->getSingleStringLiteral()->value; + $is_list = false; + } else { + $ok = false; + break; + } + } else { + $ok = false; + break; + } + } + + $properties[$key] = $result_element_type->setPossiblyUndefined( + $property->possibly_undefined + ); + + if (!$property->possibly_undefined + && $had_possibly_undefined + ) { + $is_list = false; + } + + $had_possibly_undefined = $had_possibly_undefined || $property->possibly_undefined; + } + if ($ok) { + if (!$properties) { + return Type::getEmptyArray(); + } + return new Union([new TKeyedArray( + $properties, + null, + $input_array->fallback_params, + $is_list + )]); + } + } + + if ($input_array instanceof TKeyedArray) { + $row_type = $input_array->getGenericValueType(); + } elseif ($input_array instanceof TArray) { + $row_type = $input_array->type_params[1]; + } + + $row_shape = self::getRowShape( + $row_type, + $statements_source, + $context, + $code_location + ); + + $input_array_not_empty = $input_array instanceof TNonEmptyArray || + ($input_array instanceof TKeyedArray && $input_array->isNonEmpty()); + } + + $result_key_type = Type::getArrayKey(); $result_element_type = null !== $row_type && $value_column_name_is_null ? $row_type : null; $have_at_least_one_res = false; @@ -122,7 +209,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } } - if (isset($call_args[2]) && (string)$third_arg_type !== 'null') { + if ($third_arg_type && !$key_column_name_is_null) { $type = $have_at_least_one_res ? new TNonEmptyArray([$result_key_type, $result_element_type ?? Type::getMixed()]) : new TArray([$result_key_type, $result_element_type ?? Type::getMixed()]); @@ -134,4 +221,28 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return new Union([$type]); } + + /** + * @return TArray|TKeyedArray|TClassStringMap|null + */ + private static function getRowShape( + ?Union $row_type, + SourceAnalyzer $statements_source, + Context $context, + CodeLocation $code_location + ): ?Atomic { + if ($row_type && $row_type->isSingle()) { + if ($row_type->hasArray()) { + return $row_type->getArray(); + } elseif ($row_type->hasObjectType()) { + return GetObjectVarsReturnTypeProvider::getGetObjectVarsReturnType( + $row_type, + $statements_source, + $context, + $code_location + ); + } + } + return null; + } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php new file mode 100644 index 00000000000..ecaff0a97a2 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php @@ -0,0 +1,132 @@ + + */ + public static function getFunctionIds(): array + { + return ['array_combine']; + } + + public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union + { + $statements_source = $event->getStatementsSource(); + $call_args = $event->getCallArgs(); + if (!$statements_source instanceof StatementsAnalyzer + || count($call_args) < 2 + ) { + return Type::getNever(); + } + + if (!$keys_type = $statements_source->node_data->getType($call_args[0]->value)) { + return null; + } + if (!$keys_type->isArray()) { + return null; + } + + $keys = $keys_type->getArray(); + if ($keys instanceof TArray && $keys->isEmptyArray()) { + $keys = []; + } elseif (!$keys instanceof TKeyedArray || $keys->fallback_params) { + return null; + } else { + $keys = $keys->properties; + } + + if (!$values_type = $statements_source->node_data->getType($call_args[1]->value)) { + return null; + } + if (!$values_type->isArray()) { + return null; + } + + $values = $values_type->getArray(); + if ($values instanceof TArray && $values->isEmptyArray()) { + $values = []; + } elseif (!$values instanceof TKeyedArray || $values->fallback_params) { + return null; + } else { + $values = $values->properties; + } + + + $keys_array = []; + $is_list = true; + $prev_key = -1; + foreach ($keys as $key) { + if ($key->possibly_undefined) { + return null; + } + if ($key->isSingleIntLiteral()) { + $key = $key->getSingleIntLiteral()->value; + $keys_array []= $key; + if ($is_list && $key-1 !== $prev_key) { + $is_list = false; + } + $prev_key = $key; + } elseif ($key->isSingleStringLiteral()) { + $keys_array []= $key->getSingleStringLiteral()->value; + $is_list = false; + } else { + return null; + } + } + + foreach ($values as $value) { + if ($value->possibly_undefined) { + return null; + } + } + + if (count($keys_array) !== count($values)) { + IssueBuffer::maybeAdd( + new InvalidArgument( + 'The keys array ' . $keys_type->getId() . ' must have exactly the same ' + . 'number of elements as the values array ' + . $values_type->getId(), + $event->getCodeLocation(), + 'array_combine' + ), + $statements_source->getSuppressedIssues() + ); + return $statements_source->getCodebase()->analysis_php_version_id >= 8_00_00 + ? Type::getNever() + : Type::getFalse(); + } + + $result = array_combine( + $keys_array, + $values + ); + + assert($result !== false); + + if (!$result) { + return Type::getEmptyArray(); + } + + return new Union([new TKeyedArray($result, null, null, $is_list)]); + } +} diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillKeysReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillKeysReturnTypeProvider.php new file mode 100644 index 00000000000..2ca005b4436 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillKeysReturnTypeProvider.php @@ -0,0 +1,90 @@ + + */ + public static function getFunctionIds(): array + { + return ['array_fill_keys']; + } + + public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union + { + $statements_source = $event->getStatementsSource(); + $call_args = $event->getCallArgs(); + if (!$statements_source instanceof StatementsAnalyzer) { + return Type::getMixed(); + } + if (count($call_args) !== 2) { + return Type::getNever(); + } + + $first_arg_type = isset($call_args[0]) ? $statements_source->node_data->getType($call_args[0]->value) : null; + $second_arg_type = isset($call_args[1]) ? $statements_source->node_data->getType($call_args[1]->value) : null; + + if ($first_arg_type + && $first_arg_type->isArray() + && $second_arg_type + ) { + $array = $first_arg_type->getArray(); + if ($array instanceof TArray && $array->isEmptyArray()) { + return $first_arg_type; + } elseif ($array instanceof TKeyedArray && !$array->fallback_params) { + $is_list = $array->is_list; + $array = $array->properties; + } else { + return null; + } + $result = []; + $prev_key = -1; + $had_possibly_undefined = false; + foreach ($array as $key_k) { + if ($had_possibly_undefined && !$key_k->possibly_undefined) { + $is_list = false; + } + $had_possibly_undefined = $had_possibly_undefined || $key_k->possibly_undefined; + + if ($key_k->isSingleIntLiteral()) { + $key = $key_k->getSingleIntLiteral()->value; + if ($prev_key !== $key-1) { + $is_list = false; + } + $prev_key = $key; + } elseif ($key_k->isSingleStringLiteral()) { + $key = $key_k->getSingleStringLiteral()->value; + $is_list = false; + } else { + return null; + } + $result[$key] = $second_arg_type->setPossiblyUndefined( + $key_k->possibly_undefined + ); + } + return new Union([new TKeyedArray( + $result, + null, + null, + $is_list + )]); + } + + return null; + } +} diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php index fe4be738775..5fdda22cde6 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php @@ -8,6 +8,7 @@ use Psalm\Type; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TIntRange; +use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Union; @@ -31,6 +32,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if (!$statements_source instanceof StatementsAnalyzer) { return Type::getMixed(); } + $codebase = $statements_source->getCodebase(); $first_arg_type = isset($call_args[0]) ? $statements_source->node_data->getType($call_args[0]->value) : null; $second_arg_type = isset($call_args[1]) ? $statements_source->node_data->getType($call_args[1]->value) : null; @@ -38,6 +40,39 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $value_type_from_third_arg = $third_arg_type ? $third_arg_type : Type::getMixed(); + if ($first_arg_type && $second_arg_type && $third_arg_type + && $first_arg_type->isSingleIntLiteral() + && $second_arg_type->isSingleIntLiteral() + ) { + $first_arg_type = $first_arg_type->getSingleIntLiteral()->value; + $second_arg_type = $second_arg_type->getSingleIntLiteral()->value; + $is_list = $first_arg_type === 0; + if ($second_arg_type < 0) { + if ($codebase->analysis_php_version_id < 8_00_00) { + return Type::getFalse(); + } + return Type::getNever(); + } + $result = []; + if ($first_arg_type < 0 && $codebase->analysis_php_version_id < 8_00_00) { + $result[$first_arg_type] = $third_arg_type; + $first_arg_type = 0; + $second_arg_type--; + } + while ($second_arg_type > 0) { + $result[$first_arg_type++] = $third_arg_type; + $second_arg_type--; + } + if (!$result) { + return Type::getEmptyArray(); + } + return new Union([new TKeyedArray( + $result, + null, + null, + $is_list + )]); + } if ($first_arg_type && $first_arg_type->isSingleIntLiteral() && $first_arg_type->getSingleIntLiteral()->value === 0 diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php index 7fcd0765e51..541aa1a0172 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php @@ -11,6 +11,7 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\Type; +use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TKeyedArray; @@ -37,15 +38,18 @@ public static function getFunctionIds(): array ]; } - private static ?Union $fallback = null; + private static ?TArray $fallback = null; + /** + * @return TArray|TKeyedArray + */ public static function getGetObjectVarsReturnType( Union $first_arg_type, SourceAnalyzer $statements_source, Context $context, CodeLocation $location - ): Union { - self::$fallback ??= new Union([new TArray([Type::getString(), Type::getMixed()])]); + ): Atomic { + self::$fallback ??= new TArray([Type::getString(), Type::getMixed()]); if ($first_arg_type->isSingle()) { $atomics = $first_arg_type->getAtomicTypes(); @@ -55,9 +59,7 @@ public static function getGetObjectVarsReturnType( if ([] === $object_type->properties) { return self::$fallback; } - return new Union([ - new TKeyedArray($object_type->properties) - ]); + return new TKeyedArray($object_type->properties); } if ($object_type instanceof TNamedObject) { @@ -73,7 +75,7 @@ public static function getGetObjectVarsReturnType( if ([] === $class_storage->appearing_property_ids) { if ($class_storage->final) { - return Type::getEmptyArray(); + return Type::getEmptyArrayAtomic(); } return self::$fallback; @@ -115,19 +117,17 @@ public static function getGetObjectVarsReturnType( if ([] === $properties) { if ($class_storage->final) { - return Type::getEmptyArray(); + return Type::getEmptyArrayAtomic(); } return self::$fallback; } - return new Union([ - new TKeyedArray( - $properties, - null, - $class_storage->final ? null : [Type::getString(), Type::getMixed()], - ) - ]); + return new TKeyedArray( + $properties, + null, + $class_storage->final ? null : [Type::getString(), Type::getMixed()], + ); } } return self::$fallback; @@ -146,12 +146,12 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) && $first_arg_type->isObjectType() ) { - return self::getGetObjectVarsReturnType( + return new Union([self::getGetObjectVarsReturnType( $first_arg_type, $statements_source, $event->getContext(), $event->getCodeLocation() - ); + )]); } return null; diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 784a91408aa..9a2312f25fd 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -174,39 +174,45 @@ public static function isStatus(string $role): bool 'code' => ' [ - '$c' => 'false|non-empty-array', + '$c===' => 'array{a: 1, b: 2, c: 3}', ], 'ignored_issues' => [], 'php_version' => '7.4', ], - 'arrayCombinePHP8' => [ + 'arrayCombineDynamicParams' => [ 'code' => ' */ + function getStrings(): array { return []; } + /** @return array */ + function getInts(): array { return []; } + $c = array_combine(getStrings(), getInts());', 'assertions' => [ - '$c' => 'non-empty-array', + '$c' => 'array|false', ], - 'ignored_issues' => [], - 'php_version' => '8.0', ], - 'arrayCombineNotMatching' => [ + 'arrayCombineDynamicParamsNonEmpty' => [ 'code' => ' */ + function getStrings(): array { return ["test"]; } + /** @return non-empty-array */ + function getInts(): array { return [123, 321]; } + $c = array_combine(getStrings(), getInts());', 'assertions' => [ '$c' => 'false|non-empty-array', ], - 'ignored_issues' => [], - 'php_version' => '7.4', ], - 'arrayCombineDynamicParams' => [ + 'arrayCombineDynamicParamsPHP8' => [ 'code' => ' */ - function getStrings(): array { return []; } - /** @return array */ - function getInts(): array { return []; } + /** @return non-empty-array */ + function getStrings(): array { return ["test"]; } + /** @return non-empty-array */ + function getInts(): array { return [123]; } $c = array_combine(getStrings(), getInts());', 'assertions' => [ - '$c' => 'array|false', + '$c' => 'non-empty-array', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ], 'arrayMergeIntArrays' => [ 'code' => ' ' [ + 'code' => ' [ + '$a===' => 'list{0, 0, 0}', + // Techinically this doesn't cover the case of running on 8.0 but nvm + '$b===' => 'array{-1: 0, 0: 0, 1: 0}', + '$c===' => 'array{-2: 0, 0: 0, 1: 0}', + ], + 'ignored_issues' => [], + 'php_version' => '7.4' + ], + 'arrayFillLiteral80' => [ + 'code' => ' [ + '$a===' => 'list{0, 0, 0}', + '$b===' => 'array{-1: 0, 0: 0, 1: 0}', + '$c===' => 'array{-1: 0, -2: 0, 0: 0}', + ], + 'ignored_issues' => [], + 'php_version' => '8.0' + ], 'implodeMultiDimensionalArray' => [ 'code' => ' [ - '$a' => 'non-empty-list', - '$b' => 'non-empty-list', - '$c' => 'array', + '$a===' => 'list{1, 2, 3}', + '$b===' => 'list{1, 2, 3}', + '$c' => 'array{1: array{a: int}, 2: array{a: int}, 3: array{a: int}}', '$d' => 'array', '$e' => 'array', - '$f' => 'non-empty-array', + '$f' => 'array{a: int, b: int}', '$g' => 'list', '$h' => 'list', '$i' => 'array', @@ -1463,11 +1498,94 @@ function makeKeyedArray(): array { return []; } '$k' => 'list', '$l' => 'list', '$m' => 'list', - '$n' => 'non-empty-list', + '$n' => 'list{string}', '$o' => 'list', '$p' => 'list', ], ], + 'arrayColumnExactInference' => [ + 'code' => ' "a"], + ["v" => "b"], + ["v" => "c"], + ["v" => "d"], + ], "v"); + + $b = array_column([ + ["v" => "a"], + [], + ["v" => "c"], + ["v" => "d"], + ], "v"); + + $c = array_column([ + ["v" => "a"], + 123, + ["v" => "c"], + ["v" => "d"], + ], "v"); + + $d = array_column([ + ["v" => "a", "k" => "A"], + ["v" => "b", "k" => "B"], + ["v" => "c", "k" => "C"], + ["v" => "d", "k" => "D"], + ], "v", "k"); + + $e = array_column([ + ["v" => "a", "k" => 0], + ["v" => "b", "k" => 1], + ["v" => "c", "k" => 2], + ["v" => "d", "k" => 3], + ], "v", "k"); + + $f = array_column([ + ["v" => "a", "k" => 3], + ["v" => "b", "k" => 2], + ["v" => "c", "k" => 1], + ["v" => "d", "k" => 0], + ], "v", "k"); + + $g = array_column([ + ["v" => "a", "k" => 0], + ["v" => "b", "k" => 1], + ["v" => "c", "k" => 2], + ["v" => "d", "k" => 3], + ], null, "k"); + + $h = array_column([ + "a" => ["k" => 0], + "b" => ["k" => 1], + "c" => ["k" => 2], + ], null, "k"); + + /** @var array{a: array{v: 0}, b?: array{v: 1}} */ + $aa = []; + $i = array_column($aa, "v"); + + /** @var array{a: array{v: "a", k: 0}, b?: array{v: "b", k: 1}, c: array{v: "c", k: 2}} */ + $aa = []; + $j = array_column($aa, null, "k"); + + /** @var array{a: array{v: "a", k: 0}, b: array{v: "b", k: 1}, c?: array{v: "c", k: 2}} */ + $aa = []; + $k = array_column($aa, null, "k"); + ', + 'assertions' => [ + '$a===' => "list{'a', 'b', 'c', 'd'}", + '$b===' => "list{'a', 'c', 'd'}", + '$c===' => "list{'a', 'c', 'd'}", + '$d===' => "array{A: 'a', B: 'b', C: 'c', D: 'd'}", + '$e===' => "list{'a', 'b', 'c', 'd'}", + '$f===' => "array{0: 'd', 1: 'c', 2: 'b', 3: 'a'}", + '$g===' => "list{array{k: 0, v: 'a'}, array{k: 1, v: 'b'}, array{k: 2, v: 'c'}, array{k: 3, v: 'd'}}", + '$h===' => "list{array{k: 0}, array{k: 1}, array{k: 2}}", + '$i===' => "array{a: 0, b?: 1}", + '$j===' => "array{0: array{k: 0, v: 'a'}, 1?: array{k: 1, v: 'b'}, 2: array{k: 2, v: 'c'}}", + '$k===' => "list{0: array{k: 0, v: 'a'}, 1: array{k: 1, v: 'b'}, 2?: array{k: 2, v: 'c'}}", + ] + ], 'splatArrayIntersect' => [ 'code' => ' [ 'code' => ' */ $keys = [1, 2, 3]; - $result = array_fill_keys($keys, true);', + $a = array_fill_keys($keys, true); + + $keys = [1, 2, 3]; + $b = array_fill_keys($keys, true); + + $keys = [0, 1, 2]; + $c = array_fill_keys($keys, true); + + $keys = random_int(0, 1) ? [0] : [0, 1]; + $d = array_fill_keys($keys, true); + + $keys = random_int(0, 1) ? ["a"] : ["a", "b"]; + $e = array_fill_keys($keys, true); + ', 'assertions' => [ - '$result' => 'array', + '$a===' => 'array', + '$b===' => 'array{1: true, 2: true, 3: true}', + '$c===' => 'list{true, true, true}', + '$d===' => 'list{0: true, 1?: true}', + '$e===' => 'array{a: true, b?: true}', ], ], 'shuffle' => [ @@ -2596,6 +2732,16 @@ function merger(array $a, array $b) : array { ', 'error_message' => 'InvalidArgument', ], + 'arrayCombineNotMatching' => [ + 'code' => ' 'InvalidArgument', + ], + 'arrayCombineNotMatchingPHP8' => [ + 'code' => ' 'InvalidArgument', + ], ]; } }