diff --git a/resources/functionMap.php b/resources/functionMap.php index d17ef13010..869aadc271 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -266,10 +266,8 @@ 'array_diff' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], -'array_diff_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'], -'array_diff_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable'], -'array_diff_ukey' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'], -'array_diff_ukey\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_diff_uassoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_diff_ukey' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'val'=>'mixed'], 'array_fill_keys' => ['array', 'keys'=>'array', 'val'=>'mixed'], 'array_filter' => ['array', 'input'=>'array', 'callback='=>'callable(mixed,mixed):bool|callable(mixed):bool', 'flag='=>'int'], @@ -277,10 +275,8 @@ 'array_intersect' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_intersect_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_intersect_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], -'array_intersect_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], -'array_intersect_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable'], -'array_intersect_ukey' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], -'array_intersect_ukey\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable(mixed,mixed):int'], +'array_intersect_uassoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_intersect_ukey' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'], 'array_key_first' => ['int|string|null', 'array'=>'array'], 'array_key_last' => ['int|string|null', 'array'=>'array'], @@ -304,18 +300,12 @@ 'array_slice' => ['array', 'input'=>'array', 'offset'=>'int', 'length='=>'?int', 'preserve_keys='=>'bool'], 'array_splice' => ['array', '&rw_input'=>'array', 'offset'=>'int', 'length='=>'int', 'replacement='=>'mixed'], 'array_sum' => ['int|float', 'input'=>'array'], -'array_udiff' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'], -'array_udiff\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_udiff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'], -'array_udiff_assoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_udiff_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable', 'key_comp_func'=>'callable(mixed,mixed):int'], -'array_udiff_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_uintersect' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'], -'array_uintersect\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_uintersect_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'], -'array_uintersect_assoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_uintersect_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], -'array_uintersect_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], +'array_udiff' => ['array', 'array'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int'], +'array_udiff_assoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int'], +'array_udiff_uassoc' => ['array', 'array1'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect' => ['array', 'array'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect_assoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int'], +'array_uintersect_uassoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'value_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_unique' => ['array', 'array'=>'array', 'flags='=>'int'], 'array_unshift' => ['positive-int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], 'array_values' => ['list', 'input'=>'array'], diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index d114609b35..fb6176b470 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -27,6 +27,7 @@ use function array_map; use function count; use function explode; +use function in_array; use function is_string; use function sprintf; use function strtolower; @@ -185,7 +186,29 @@ public function getMethodSignatures(string $className, string $methodName, ?Refl public function getFunctionSignatures(string $functionName, ?string $className, ReflectionFunctionAbstract|null $reflectionFunction): array { $lowerName = strtolower($functionName); - if (!array_key_exists($lowerName, $this->map->functions)) { + if ( + !array_key_exists($lowerName, $this->map->functions) + || ( + $className === null + // These functions have a variadic parameter in the middle. This is not well described by php8-stubs. + && in_array( + $functionName, + [ + 'array_diff_uassoc', + 'array_diff_ukey', + 'array_intersect_uassoc', + 'array_intersect_ukey', + 'array_udiff', + 'array_udiff_assoc', + 'array_udiff_uassoc', + 'array_uintersect', + 'array_uintersect_assoc', + 'array_uintersect_uassoc', + ], + true, + ) + ) + ) { return $this->functionSignatureMapProvider->getFunctionSignatures($functionName, $className, $reflectionFunction); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4c34fb4f43..2808b0ef80 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -27,6 +27,7 @@ use PHPStan\Type\VerbosityLevel; use function array_fill; use function array_key_exists; +use function array_pop; use function count; use function implode; use function is_string; @@ -493,21 +494,43 @@ private function processArguments( $originalParameters = $parametersAcceptor instanceof ResolvedFunctionVariant ? $parametersAcceptor->getOriginalParametersAcceptor()->getParameters() : array_fill(0, count($parameters), null); + $expandedParameters = []; + $expandedOriginalParameters = []; $parametersByName = []; $originalParametersByName = []; $unusedParametersByName = []; $errors = []; + $expandVariadicParam = count($arguments) - count($parameters); foreach ($parameters as $i => $parameter) { $parametersByName[$parameter->getName()] = $parameter; - $originalParametersByName[$parameter->getName()] = $originalParameters[$i]; + $originalParameter = $originalParameters[$i]; + $originalParametersByName[$parameter->getName()] = $originalParameter; + $expandedParameters[] = $parameter; + $expandedOriginalParameters[] = $originalParameter; if ($parameter->isVariadic()) { + // This handles variadic parameter followed by a another parameter. This cannot happen in user-defined + // code, but it does happen in built-in functions - e.g. array_intersect_uassoc. Currently, this condition + // doesn't handle the case where the following parameter is optional (it may not be needed). + if ($expandVariadicParam < 0 && array_key_exists($i + 1, $parameters)) { + $expandVariadicParam++; + array_pop($expandedParameters); + array_pop($expandedOriginalParameters); + } else { + for (; $expandVariadicParam > 0; --$expandVariadicParam) { + $expandedParameters[] = $parameter; + $expandedOriginalParameters[] = $originalParameter; + } + } + continue; } $unusedParametersByName[$parameter->getName()] = $parameter; } + $parameters = $expandedParameters; + $originalParameters = $expandedOriginalParameters; $newArguments = []; $namedArgumentAlreadyOccurred = false; diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 09e33f5c9b..257c220fda 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -201,8 +201,11 @@ public static function decideType( } if ( - (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) - && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + ($type->isCallable()->yes() && $phpDocType->isCallable()->yes()) + || ( + (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) + && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + ) ) { $resultType = $phpDocType; } else { diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 25c73249ec..27e51e1181 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -50,20 +50,161 @@ function uksort(array &$array, callable $callback): bool } /** - * @template T of mixed + * @template TK of array-key + * @template TV of mixed * - * @param array $one - * @param array $two - * @param callable(T, T): int $three + * @param array $array + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @return array */ function array_udiff( - array $one, - array $two, - callable $three -): int {} + array $array, + array ...$arrays, + callable $value_compare_func, +): array {} /** * @param array $value * @return ($value is __always-list ? true : false) */ function array_is_list(array $value): bool {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_diff_uassoc( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_diff_ukey( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_intersect_uassoc( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_intersect_ukey( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @return array + */ +function array_udiff_assoc( + array $array, + array ...$arrays, + callable $value_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @return array + */ +function array_uintersect_assoc( + array $array, + array ...$arrays, + callable $value_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array1 + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_udiff_uassoc( + array $array1, + array ...$arrays, + callable $value_compare_func, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @param callable(TK, TK): int $key_compare_func + * @return array + */ +function array_uintersect_uassoc( + array $array, + array ...$arrays, + callable $value_compare_func, + callable $key_compare_func +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TV, TV): int $value_compare_func + * @return array + */ +function array_uintersect( + array $array, + array ...$arrays, + callable $value_compare_func, +): array {} diff --git a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php index 00ed1a4159..ae7869f284 100644 --- a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php @@ -543,11 +543,12 @@ public function testParseAll(int $phpVersionId): void foreach ($signatures as $signature) { self::assertNotInstanceOf(ErrorType::class, $signature->getReturnType(), $functionName); - $optionalOcurred = false; + $optionalOccurred = false; foreach ($signature->getParameters() as $parameter) { + // Required parameter can follow variadic parameter - e.g. array_intersect_uassoc. if ($parameter->isOptional()) { - $optionalOcurred = true; - } elseif ($optionalOcurred) { + $optionalOccurred = $optionalOccurred || !$parameter->isVariadic(); + } elseif ($optionalOccurred) { $this->fail(sprintf('%s contains required parameter after optional.', $functionName)); } self::assertNotInstanceOf(ErrorType::class, $parameter->getType(), sprintf('%s (parameter %s)', $functionName, $parameter->getName())); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 90859848d0..0125bc8056 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -632,25 +632,33 @@ public function testArrayUdiffCallback(): void { $this->analyse([__DIR__ . '/data/array_udiff.php'], [ [ - 'Parameter #3 $data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(string, string): string given.', + 'Parameter #3 $value_compare_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(string, string): string given.', 6, ], [ - 'Parameter #3 $data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): (literal-string&non-falsy-string&numeric-string) given.', + 'Parameter #3 $value_compare_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): (literal-string&non-falsy-string&numeric-string) given.', 14, ], [ - 'Parameter #1 $arr1 of function array_udiff expects array, null given.', + 'Parameter #1 $array of function array_udiff expects array, null given.', 20, ], [ - 'Parameter #2 $arr2 of function array_udiff expects array, null given.', + 'Parameter #2 ...$arrays of function array_udiff expects array, null given.', 21, ], [ - 'Parameter #3 $data_comp_func of function array_udiff expects callable(string, string): int, Closure(string, int): non-empty-string given.', + 'Parameter #3 $value_compare_func of function array_udiff expects callable(string, string): int, Closure(string, int): non-empty-string given.', 22, ], + [ + 'Parameter #3 ...$arrays of function array_udiff expects array<0|1, 25|26|27>, Closure(int, int): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #4 $value_compare_func of function array_udiff expects callable(25|26|27, 25|26|27): int, array{26, 27} given.', + 56, + ], ]); } @@ -1716,4 +1724,270 @@ public function testCountArrayShift(): void $this->analyse([__DIR__ . '/data/count-array-shift.php'], $errors); } + public function testArrayDiffUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_diff_uassoc.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_diff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_diff_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_diff_uassoc expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $key_compare_func of function array_diff_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 56, + ], + [ + 'Function array_diff_uassoc invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayDiffUkey(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_diff_ukey.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_diff_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_diff_ukey expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_diff_ukey expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $key_compare_func of function array_diff_ukey expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 56, + ], + [ + 'Function array_diff_ukey invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayIntersectUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_intersect_uassoc.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_intersect_uassoc expects array<\'a\'|\'b\', 1|2>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: 1, b: 2} given.', + 56, + ], + [ + 'Function array_intersect_uassoc invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayIntersectUkey(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_intersect_ukey.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_intersect_ukey expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 56, + ], + [ + 'Function array_intersect_ukey invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayUdiffAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_assoc.php'], [ + [ + 'Parameter #3 $value_compare_func of function array_udiff_assoc expects callable(1|2, 1|2): int, Closure(string, string): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $value_compare_func of function array_udiff_assoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_udiff_assoc expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $value_compare_func of function array_udiff_assoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 56, + ], + [ + 'Function array_udiff_assoc invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayUdiffUasssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_uassoc.php'], [ + [ + 'Parameter #3 $value_compare_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_compare_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $value_compare_func of function array_udiff_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_compare_func of function array_udiff_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 42, + ], + [ + 'Parameter #2 ...$arrays of function array_udiff_uassoc expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 71, + ], + [ + 'Parameter #3 $value_compare_func of function array_udiff_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 74, + ], + [ + 'Function array_udiff_uassoc invoked with 2 parameters, at least 3 required.', + 80, + ], + ]); + } + + public function testArrayUintersectAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_assoc.php'], [ + [ + 'Parameter #3 $value_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect_assoc expects callable(1|2|3|4, 1|2|3|4): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_uintersect_assoc expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 56, + ], + [ + 'Function array_uintersect_assoc invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testArrayUintersectUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_uassoc.php'], [ + [ + 'Parameter #3 $value_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 42, + ], + [ + 'Parameter #2 ...$arrays of function array_uintersect_uassoc expects array<\'a\'|\'b\', \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 71, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: \'a\', b: \'b\'} given.', + 74, + ], + [ + 'Function array_uintersect_uassoc invoked with 2 parameters, at least 3 required.', + 80, + ], + ]); + } + + public function testArrayUintersect(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect.php'], [ + [ + 'Parameter #3 $value_compare_func of function array_uintersect expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect expects callable(1|2|3|4, 1|2|3|4): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + [ + 'Parameter #2 ...$arrays of function array_uintersect expects array<0|1, \'a\'|\'b\'>, Closure(string, string): int<-1, 1> given.', + 53, + ], + [ + 'Parameter #3 $value_compare_func of function array_uintersect expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{\'a\', \'b\'} given.', + 56, + ], + [ + 'Function array_uintersect invoked with 1 parameter, at least 2 required.', + 59, + ], + ]); + } + + public function testBug7707(): void + { + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-7707.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php new file mode 100644 index 0000000000..a1ed5f49ae --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php @@ -0,0 +1,61 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], +); + +array_diff_uassoc( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php new file mode 100644 index 0000000000..e8c7e928ac --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php @@ -0,0 +1,61 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], +); + +array_diff_ukey( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php new file mode 100644 index 0000000000..af3ac5f025 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -0,0 +1,61 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + ['a' => 1, 'b' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + ['a' => 1, 'b' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 1, 'b' => 2] +); + +array_intersect_uassoc( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php new file mode 100644 index 0000000000..d9d39583fb --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php @@ -0,0 +1,61 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], +); + +array_intersect_ukey( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff.php b/tests/PHPStan/Rules/Functions/data/array_udiff.php index 23af63ccef..105aa20953 100644 --- a/tests/PHPStan/Rules/Functions/data/array_udiff.php +++ b/tests/PHPStan/Rules/Functions/data/array_udiff.php @@ -37,3 +37,21 @@ static function(int $a, int $b): int { ["26","27"], 'strcasecmp', ); + +array_udiff( + [25,26], + [26,27], + [26,27], + static function(int $a, int $b): int { + return $a <=> $b; + }, +); + +array_udiff( + [25,26], + [26,27], + static function(int $a, int $b): int { + return $a <=> $b; + }, + [26,27], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php new file mode 100644 index 0000000000..a3b500a1c1 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php @@ -0,0 +1,61 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], +); + +array_udiff_assoc( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php new file mode 100644 index 0000000000..b4431cff78 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php @@ -0,0 +1,85 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + }, +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect.php b/tests/PHPStan/Rules/Functions/data/array_uintersect.php new file mode 100644 index 0000000000..1e9a0fbb83 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect.php @@ -0,0 +1,61 @@ + $b; + } +); + +array_uintersect( + [1, 2, 3], + [1, 2, 3, 4], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect( + ['a', 'b'], + ['c', 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect( + [1, 2, 3], + [1, 2, 3, 4], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect( + ['a', 'b'], + ['c', 'd'], + ['c', 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect( + ['a', 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect( + ['a', 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a', 'b'], +); + +array_uintersect( + ['a', 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php new file mode 100644 index 0000000000..2b268fd30c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php @@ -0,0 +1,61 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + [1, 2, 3], + [1, 2, 3, 4], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + [1, 2, 3], + [1, 2, 3, 4], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php new file mode 100644 index 0000000000..f84d65aa4e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php @@ -0,0 +1,85 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + }, +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + static function (string $a, string $b): int { + return $a <=> $b; + }, +); diff --git a/tests/PHPStan/Rules/Functions/data/bug-7707.php b/tests/PHPStan/Rules/Functions/data/bug-7707.php new file mode 100644 index 0000000000..1fc7bbab6c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-7707.php @@ -0,0 +1,98 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_diff_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_intersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_intersect_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_udiff_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + + var_dump(array_udiff_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + }, + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + }, + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + } +}