From 9114da7524558881dabbdc8e7ebd8cef0ab0f8d4 Mon Sep 17 00:00:00 2001 From: Brad <28307684+mad-briller@users.noreply.github.com> Date: Sat, 30 Jul 2022 14:28:15 +0100 Subject: [PATCH 01/13] Added stub files and tests for: array_diff_uassoc array_diff_ukey array_intersect_uassoc array_intersect_ukey array_udiff_assoc array_udiff_uassoc array_uintersect_assoc array_uintersect_uassoc array_uintersect --- stubs/arrayFunctions.stub | 141 ++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 151 ++++++++++++++++++ .../Functions/data/array_diff_uassoc.php | 33 ++++ .../Rules/Functions/data/array_diff_ukey.php | 33 ++++ .../Functions/data/array_intersect_uassoc.php | 33 ++++ .../Functions/data/array_intersect_ukey.php | 33 ++++ .../Functions/data/array_udiff_assoc.php | 33 ++++ .../Functions/data/array_udiff_uassoc.php | 45 ++++++ .../Rules/Functions/data/array_uintersect.php | 33 ++++ .../Functions/data/array_uintersect_assoc.php | 33 ++++ .../data/array_uintersect_uassoc.php | 45 ++++++ 11 files changed, 613 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_diff_ukey.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 25c73249ec..d85ccd4eb3 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -67,3 +67,144 @@ function array_udiff( * @return ($value is __always-list ? true : false) */ function array_is_list(array $value): bool {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int<-1, 1> $three + * @return array + */ +function array_diff_uassoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int<-1, 1> $three + * @return array + */ +function array_diff_ukey( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int<-1, 1> $three + * @return array + */ +function array_intersect_uassoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int<-1, 1> $three + * + * @return array + */ +function array_intersect_ukey( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int<-1, 1> $three + * + * @return array + */ +function array_udiff_assoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int<-1, 1> $three + * @param callable(TK, TK): int<-1, 1> $four + * @return array + */ +function array_udiff_uassoc( + array $one, + array $two, + callable $three, + callable $four +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int<-1, 1> $three + * @return array + */ +function array_uintersect_assoc( + array $one, + array $two, + callable $three, +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int<-1, 1> $three + * @param callable(TK, TK): int<-1, 1> $four + * @return array + */ +function array_uintersect_uassoc( + array $one, + array $two, + callable $three, + callable $four +): array {} + +/** + * @template TK of mixed + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int<-1, 1> $three + * @return array + */ +function array_uintersect( + array $one, + array $two, + callable $three, +): array {} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 90859848d0..ea0de6cdf1 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1716,4 +1716,155 @@ 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 $data_comp_func of function array_diff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayDiffUkey(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_diff_ukey.php'], [ + [ + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + 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(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + 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(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUdiffAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_assoc.php'], [ + [ + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUdiffUsssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_uassoc.php'], [ + [ + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 42, + ], + ]); + } + + public function testArrayUintersectAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_assoc.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUintersectUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_uassoc.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 42, + ], + ]); + } + + public function testArrayUintersect(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + } 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..0884cd24fd --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php @@ -0,0 +1,33 @@ + 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; + } +); 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..23a7303a9f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php @@ -0,0 +1,33 @@ + 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; + } +); 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..395d009139 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -0,0 +1,33 @@ + 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; + } +); 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..656999fb02 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php @@ -0,0 +1,33 @@ + 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; + } +); 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..6ce8d8cbdf --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php @@ -0,0 +1,33 @@ + 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; + } +); 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..edbea3ecf3 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php @@ -0,0 +1,45 @@ + '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; + } +); 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..193ee0a3a7 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect.php @@ -0,0 +1,33 @@ + $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; + } +); 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..328d8afb6b --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php @@ -0,0 +1,33 @@ + '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; + } +); 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..d287aa5809 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php @@ -0,0 +1,45 @@ + '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; + } +); From 4efda8b37c1dc31769ee9ac8a5fe738a3c47c944 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 2 Aug 2024 13:37:04 +0200 Subject: [PATCH 02/13] bound TK of array_intersect_ukey etc to array-key --- src/Type/TypehintHelper.php | 7 ++- stubs/arrayFunctions.stub | 18 ++++---- .../CallToFunctionParametersRuleTest.php | 46 +++++++++---------- .../Functions/data/array_diff_uassoc.php | 2 +- .../Rules/Functions/data/array_diff_ukey.php | 2 +- .../Functions/data/array_intersect_uassoc.php | 2 +- .../Functions/data/array_intersect_ukey.php | 2 +- .../Functions/data/array_udiff_assoc.php | 2 +- .../Functions/data/array_udiff_uassoc.php | 2 +- .../Rules/Functions/data/array_uintersect.php | 2 +- .../Functions/data/array_uintersect_assoc.php | 2 +- .../data/array_uintersect_uassoc.php | 2 +- 12 files changed, 46 insertions(+), 43 deletions(-) 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 d85ccd4eb3..db6e3be902 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -69,7 +69,7 @@ function array_udiff( function array_is_list(array $value): bool {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -84,7 +84,7 @@ function array_diff_uassoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -99,7 +99,7 @@ function array_diff_ukey( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -114,7 +114,7 @@ function array_intersect_uassoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -130,7 +130,7 @@ function array_intersect_ukey( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -146,7 +146,7 @@ function array_udiff_assoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -163,7 +163,7 @@ function array_udiff_uassoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -178,7 +178,7 @@ function array_uintersect_assoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one @@ -195,7 +195,7 @@ function array_uintersect_uassoc( ): array {} /** - * @template TK of mixed + * @template TK of array-key * @template TV of mixed * * @param array $one diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ea0de6cdf1..d231474355 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1721,11 +1721,11 @@ public function testArrayDiffUassoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_diff_uassoc.php'], [ [ - 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1736,11 +1736,11 @@ public function testArrayDiffUkey(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_diff_ukey.php'], [ [ - 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1751,11 +1751,11 @@ 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(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1766,11 +1766,11 @@ 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(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1781,34 +1781,34 @@ public function testArrayUdiffAssoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_udiff_assoc.php'], [ [ - 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2, 1|2): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); } - public function testArrayUdiffUsssoc(): void + public function testArrayUdiffUasssoc(): void { $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_udiff_uassoc.php'], [ [ - 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 28, ], [ - 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 31, ], [ - 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 39, ], [ - 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 42, ], ]); @@ -1819,11 +1819,11 @@ public function testArrayUintersectAssoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect_assoc.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1834,19 +1834,19 @@ public function testArrayUintersectUassoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect_uassoc.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 28, ], [ - 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 31, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 39, ], [ - 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 42, ], ]); @@ -1857,11 +1857,11 @@ public function testArrayUintersect(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect expects callable(string, string): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect expects callable(int, int): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php index 0884cd24fd..257fc17c85 100644 --- a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php @@ -1,4 +1,4 @@ - 1, 'b' => 2], diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php index 23a7303a9f..8a98630a88 100644 --- a/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php +++ b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php @@ -1,4 +1,4 @@ - 1, 'b' => 2], diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php index 395d009139..9f8ba1bfa3 100644 --- a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -1,4 +1,4 @@ - 1, 'b' => 2], diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php index 656999fb02..4d81086d24 100644 --- a/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php @@ -1,4 +1,4 @@ - 1, 'b' => 2], diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php index 6ce8d8cbdf..7827734e59 100644 --- a/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php @@ -1,4 +1,4 @@ - 1, 'b' => 2], diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php index edbea3ecf3..f4767621c2 100644 --- a/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php @@ -1,4 +1,4 @@ - 'a', 'b' => 'b'], diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect.php b/tests/PHPStan/Rules/Functions/data/array_uintersect.php index 193ee0a3a7..dbe5307a82 100644 --- a/tests/PHPStan/Rules/Functions/data/array_uintersect.php +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect.php @@ -1,4 +1,4 @@ - '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 index d287aa5809..f079aec189 100644 --- a/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php @@ -1,4 +1,4 @@ - 'a', 'b' => 'b'], From d2d1bb8ece337bb53e1fa8f8ecbfea19e8bf4471 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 2 Aug 2024 13:46:38 +0200 Subject: [PATCH 03/13] add test for issue 7707 --- .../CallToFunctionParametersRuleTest.php | 7 ++ .../PHPStan/Rules/Functions/data/bug-7707.php | 98 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-7707.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index d231474355..4b4aff1730 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1867,4 +1867,11 @@ public function testArrayUintersect(): void ]); } + public function testBug7707(): void + { + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-7707.php'], []); + } + } 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; + } + )); + } +} From eb81c92be1002bed83b2f628d72c41b66ac1c9dc Mon Sep 17 00:00:00 2001 From: schlndh Date: Sat, 3 Aug 2024 12:56:23 +0200 Subject: [PATCH 04/13] revert prev PR changes --- src/Type/TypehintHelper.php | 7 +- stubs/arrayFunctions.stub | 141 ------------------------------------ 2 files changed, 2 insertions(+), 146 deletions(-) diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 257c220fda..09e33f5c9b 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -201,11 +201,8 @@ public static function decideType( } if ( - ($type->isCallable()->yes() && $phpDocType->isCallable()->yes()) - || ( - (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) - && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->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 db6e3be902..25c73249ec 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -67,144 +67,3 @@ function array_udiff( * @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 $one - * @param array $two - * @param callable(TK, TK): int<-1, 1> $three - * @return array - */ -function array_diff_uassoc( - array $one, - array $two, - callable $three -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TK, TK): int<-1, 1> $three - * @return array - */ -function array_diff_ukey( - array $one, - array $two, - callable $three -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TK, TK): int<-1, 1> $three - * @return array - */ -function array_intersect_uassoc( - array $one, - array $two, - callable $three -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TK, TK): int<-1, 1> $three - * - * @return array - */ -function array_intersect_ukey( - array $one, - array $two, - callable $three -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TV, TV): int<-1, 1> $three - * - * @return array - */ -function array_udiff_assoc( - array $one, - array $two, - callable $three -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TV, TV): int<-1, 1> $three - * @param callable(TK, TK): int<-1, 1> $four - * @return array - */ -function array_udiff_uassoc( - array $one, - array $two, - callable $three, - callable $four -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TV, TV): int<-1, 1> $three - * @return array - */ -function array_uintersect_assoc( - array $one, - array $two, - callable $three, -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TV, TV): int<-1, 1> $three - * @param callable(TK, TK): int<-1, 1> $four - * @return array - */ -function array_uintersect_uassoc( - array $one, - array $two, - callable $three, - callable $four -): array {} - -/** - * @template TK of array-key - * @template TV of mixed - * - * @param array $one - * @param array $two - * @param callable(TV, TV): int<-1, 1> $three - * @return array - */ -function array_uintersect( - array $one, - array $two, - callable $three, -): array {} From 014a7c0326bbf3aee89a2b081131d36dc5f4802e Mon Sep 17 00:00:00 2001 From: schlndh Date: Thu, 8 Aug 2024 10:52:30 +0200 Subject: [PATCH 05/13] WIP: add prototype reflection for array_intersect_uassoc and handle middle variadic param --- .../NativeFunctionReflectionProvider.php | 125 ++++++++++++++++++ src/Rules/FunctionCallParametersCheck.php | 25 +++- .../CallToFunctionParametersRuleTest.php | 8 ++ .../Functions/data/array_intersect_uassoc.php | 24 ++++ 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 7c850a582e..0bafa920e9 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -12,12 +12,27 @@ use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; +use PHPStan\Reflection\PassedByReference; +use PHPStan\Reflection\Php\DummyParameter; use PHPStan\TrinaryLogic; +use PHPStan\Type\ArrayType; +use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\CallableType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\Generic\TemplateBenevolentUnionType; +use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Generic\TemplateTypeParameterStrategy; +use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; +use PHPStan\Type\UnionType; use function array_key_exists; use function array_map; use function strtolower; @@ -40,6 +55,116 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef return $this->functionMap[$lowerCasedFunctionName]; } + if ($lowerCasedFunctionName === 'array_intersect_uassoc') { + $genericKeyType = new TemplateBenevolentUnionType( + TemplateTypeScope::createWithFunction($realFunctionName), + new TemplateTypeParameterStrategy(), + TemplateTypeVariance::createInvariant(), + 'TK', + new BenevolentUnionType([new IntegerType(), new StringType()]), + ); + $genericArrayType = new ArrayType( + $genericKeyType, + new TemplateMixedType( + TemplateTypeScope::createWithFunction($realFunctionName), + new TemplateTypeParameterStrategy(), + TemplateTypeVariance::createInvariant(), + 'TV', + new MixedType(), + ), + ); + $nativeArrayType = new ArrayType(new MixedType(), new MixedType()); + $genericCallableType = new CallableType( + [ + new DummyParameter( + 'a', + $genericKeyType, + false, + PassedByReference::createNo(), + false, + null, + ), + new DummyParameter( + 'b', + $genericKeyType, + false, + PassedByReference::createNo(), + false, + null, + ), + ], + IntegerRangeType::fromInterval(-1, 1), + false, + TemplateTypeMap::createEmpty(), + null, + [], + null, + ); + $nativeCallableType = new CallableType(); + return new NativeFunctionReflection( + $realFunctionName, + [ + new FunctionVariantWithPhpDocs( + TemplateTypeMap::createEmpty(), + null, + [ + new NativeParameterWithPhpDocsReflection( + 'array', + false, + $genericArrayType, + $genericArrayType, + $nativeArrayType, + PassedByReference::createNo(), + false, + null, + null, + TrinaryLogic::createNo(), + null, + ), + new NativeParameterWithPhpDocsReflection( + 'arrays', + true, + $genericArrayType, + $genericArrayType, + $nativeArrayType, + PassedByReference::createNo(), + true, + null, + null, + TrinaryLogic::createNo(), + null, + ), + new NativeParameterWithPhpDocsReflection( + 'key_compare_func', + false, + $genericCallableType, + $genericCallableType, + $nativeCallableType, + PassedByReference::createNo(), + false, + null, + null, + TrinaryLogic::createYes(), + null, + ), + ], + true, + $genericArrayType, + $genericArrayType, + $nativeArrayType, + null, + ), + ], + null, + null, + TrinaryLogic::createMaybe(), + false, + Assertions::createEmpty(), + null, + TrinaryLogic::createNo(), + ); + } + if (!$this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName)) { return null; } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4c34fb4f43..c552a456e8 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -493,23 +493,46 @@ 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 = []; + // TODO: check isVariadic usages in below code - they may not be necessary anymore. $namedArgumentAlreadyOccurred = false; foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine]) { if ($argumentName === null) { diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4b4aff1730..df8dfc41b0 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1758,6 +1758,14 @@ public function testArrayIntersectUassoc(): void 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, 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<-1, 1>, array{a: 1, b: 2} given.', + 56, + ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php index 9f8ba1bfa3..7159d5ead1 100644 --- a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -31,3 +31,27 @@ 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] +); From a4282e34d4592782f12dd2698f6f52b6ae240552 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 08:48:09 +0200 Subject: [PATCH 06/13] replace hard-coded reflection with highest-priority stub --- resources/functionMap.php | 3 +- resources/highest-priority-stubs.stub | 19 +++ .../BetterReflectionSourceLocatorFactory.php | 1 + .../NativeFunctionReflectionProvider.php | 128 +----------------- .../SignatureMap/Php8SignatureMapProvider.php | 5 +- src/Testing/TestCaseSourceLocatorFactory.php | 3 + src/Type/TypehintHelper.php | 7 +- 7 files changed, 35 insertions(+), 131 deletions(-) create mode 100644 resources/highest-priority-stubs.stub diff --git a/resources/functionMap.php b/resources/functionMap.php index d17ef13010..b52721db1f 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -277,8 +277,7 @@ '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_uassoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], '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_key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'], diff --git a/resources/highest-priority-stubs.stub b/resources/highest-priority-stubs.stub new file mode 100644 index 0000000000..80d0059d2f --- /dev/null +++ b/resources/highest-priority-stubs.stub @@ -0,0 +1,19 @@ + $array + * @param array ...$arrays + * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +function array_intersect_uassoc( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 5a207d0476..455f1daf32 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -122,6 +122,7 @@ public function create(): SourceLocator } $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../../resources/highest-priority-stubs.stub'); $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 0bafa920e9..203fe215ef 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -12,27 +12,12 @@ use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; -use PHPStan\Reflection\PassedByReference; -use PHPStan\Reflection\Php\DummyParameter; use PHPStan\TrinaryLogic; -use PHPStan\Type\ArrayType; -use PHPStan\Type\BenevolentUnionType; -use PHPStan\Type\CallableType; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateBenevolentUnionType; -use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; -use PHPStan\Type\Generic\TemplateTypeParameterStrategy; -use PHPStan\Type\Generic\TemplateTypeScope; -use PHPStan\Type\Generic\TemplateTypeVariance; -use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; -use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; -use PHPStan\Type\UnionType; use function array_key_exists; use function array_map; use function strtolower; @@ -55,116 +40,6 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef return $this->functionMap[$lowerCasedFunctionName]; } - if ($lowerCasedFunctionName === 'array_intersect_uassoc') { - $genericKeyType = new TemplateBenevolentUnionType( - TemplateTypeScope::createWithFunction($realFunctionName), - new TemplateTypeParameterStrategy(), - TemplateTypeVariance::createInvariant(), - 'TK', - new BenevolentUnionType([new IntegerType(), new StringType()]), - ); - $genericArrayType = new ArrayType( - $genericKeyType, - new TemplateMixedType( - TemplateTypeScope::createWithFunction($realFunctionName), - new TemplateTypeParameterStrategy(), - TemplateTypeVariance::createInvariant(), - 'TV', - new MixedType(), - ), - ); - $nativeArrayType = new ArrayType(new MixedType(), new MixedType()); - $genericCallableType = new CallableType( - [ - new DummyParameter( - 'a', - $genericKeyType, - false, - PassedByReference::createNo(), - false, - null, - ), - new DummyParameter( - 'b', - $genericKeyType, - false, - PassedByReference::createNo(), - false, - null, - ), - ], - IntegerRangeType::fromInterval(-1, 1), - false, - TemplateTypeMap::createEmpty(), - null, - [], - null, - ); - $nativeCallableType = new CallableType(); - return new NativeFunctionReflection( - $realFunctionName, - [ - new FunctionVariantWithPhpDocs( - TemplateTypeMap::createEmpty(), - null, - [ - new NativeParameterWithPhpDocsReflection( - 'array', - false, - $genericArrayType, - $genericArrayType, - $nativeArrayType, - PassedByReference::createNo(), - false, - null, - null, - TrinaryLogic::createNo(), - null, - ), - new NativeParameterWithPhpDocsReflection( - 'arrays', - true, - $genericArrayType, - $genericArrayType, - $nativeArrayType, - PassedByReference::createNo(), - true, - null, - null, - TrinaryLogic::createNo(), - null, - ), - new NativeParameterWithPhpDocsReflection( - 'key_compare_func', - false, - $genericCallableType, - $genericCallableType, - $nativeCallableType, - PassedByReference::createNo(), - false, - null, - null, - TrinaryLogic::createYes(), - null, - ), - ], - true, - $genericArrayType, - $genericArrayType, - $nativeArrayType, - null, - ), - ], - null, - null, - TrinaryLogic::createMaybe(), - false, - Assertions::createEmpty(), - null, - TrinaryLogic::createNo(), - ); - } - if (!$this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName)) { return null; } @@ -176,6 +51,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $asserts = Assertions::createEmpty(); $docComment = null; $returnsByReference = TrinaryLogic::createMaybe(); + $resolvedPhpDoc = null; try { $reflectionFunction = $this->reflector->reflectFunction($functionName); $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); @@ -199,7 +75,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $functionSignaturesResult = $this->signatureMapProvider->getFunctionSignatures($lowerCasedFunctionName, null, $reflectionFunctionAdapter); - $phpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($lowerCasedFunctionName, array_map(static fn (ParameterSignature $parameter): string => $parameter->getName(), $functionSignaturesResult['positional'][0]->getParameters())); + $phpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($lowerCasedFunctionName, array_map(static fn (ParameterSignature $parameter): string => $parameter->getName(), $functionSignaturesResult['positional'][0]->getParameters())) ?? $resolvedPhpDoc; if ($phpDoc !== null) { if ($phpDoc->hasPhpDocString()) { $docComment = $phpDoc->getPhpDocString(); diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index d114609b35..f334a754aa 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -185,7 +185,10 @@ 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) + || ($reflectionFunction !== null && $reflectionFunction->getDocComment() !== false && str_contains($reflectionFunction->getDocComment(), '@phpstan-skip-php8-stubs')) + ) { return $this->functionSignatureMapProvider->getFunctionSignatures($functionName, $className, $reflectionFunction); } diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index fccedd9ec1..7cc00d6acd 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -16,6 +16,7 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker; use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; +use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository; use PHPStan\Reflection\BetterReflection\SourceLocator\PhpVersionBlacklistSourceLocator; use ReflectionClass; use function dirname; @@ -35,6 +36,7 @@ class TestCaseSourceLocatorFactory * @param array{analyse?: array, analyseAndScan?: array}|null $excludePaths */ public function __construct( + private OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, private ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker, private Parser $phpParser, private Parser $php8Parser, @@ -83,6 +85,7 @@ public function create(): SourceLocator $astLocator = new Locator($this->phpParser); $astPhp8Locator = new Locator($this->php8Parser); + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../resources/highest-priority-stubs.stub'); $locators[] = new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); 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 { From dd39d8aa701ad757eef28b352a649c8811807157 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 13:20:33 +0200 Subject: [PATCH 07/13] add stubs for other array intersect/diff functions as well --- resources/functionMap.php | 27 ++-- resources/highest-priority-stubs.stub | 148 ++++++++++++++++++ stubs/arrayFunctions.stub | 13 -- .../CallToFunctionParametersRuleTest.php | 50 +++--- .../Rules/Functions/data/array_udiff.php | 18 +++ 5 files changed, 204 insertions(+), 52 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b52721db1f..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'], @@ -278,8 +276,7 @@ 'array_intersect_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_intersect_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_intersect_uassoc' => ['array', 'array'=>'array', '...arrays'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'], -'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_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'], @@ -303,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/resources/highest-priority-stubs.stub b/resources/highest-priority-stubs.stub index 80d0059d2f..28a52cd903 100644 --- a/resources/highest-priority-stubs.stub +++ b/resources/highest-priority-stubs.stub @@ -2,6 +2,38 @@ // These stubs override phpstorm-stubs as the main reflection source for native code. +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $array + * @param array ...$arrays + * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +function array_diff_ukey( + array $array, + array ...$arrays, + callable $key_compare_func +): array {} + /** * @template TK of array-key * @template TV of mixed @@ -17,3 +49,119 @@ function array_intersect_uassoc( 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<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $value_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $value_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $value_compare_func + * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $value_compare_func + * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +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<-1, 1> $value_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +function array_udiff( + 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<-1, 1> $value_compare_func + * @return array + * @phpstan-skip-php8-stubs + */ +function array_uintersect( + array $array, + array ...$arrays, + callable $value_compare_func, +): array {} diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 25c73249ec..0adacfaf29 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -49,19 +49,6 @@ function uksort(array &$array, callable $callback): bool { } -/** - * @template T of mixed - * - * @param array $one - * @param array $two - * @param callable(T, T): int $three - */ -function array_udiff( - array $one, - array $two, - callable $three -): int {} - /** * @param array $value * @return ($value is __always-list ? true : false) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index df8dfc41b0..9fd40a335a 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<-1, 1>, 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<-1, 1>, 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<-1, 1>, 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<-1, 1>, array{26, 27} given.', + 56, + ], ]); } @@ -1721,11 +1729,11 @@ public function testArrayDiffUassoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_diff_uassoc.php'], [ [ - 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_diff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_diff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1736,11 +1744,11 @@ public function testArrayDiffUkey(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_diff_ukey.php'], [ [ - 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_diff_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $key_compare_func of function array_diff_ukey expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1789,11 +1797,11 @@ public function testArrayUdiffAssoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_udiff_assoc.php'], [ [ - 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2, 1|2): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_udiff_assoc expects callable(1|2, 1|2): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_udiff_assoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1804,19 +1812,19 @@ public function testArrayUdiffUasssoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_udiff_uassoc.php'], [ [ - 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 28, ], [ - 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #4 $key_compare_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 31, ], [ - 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_udiff_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 39, ], [ - 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #4 $key_compare_func of function array_udiff_uassoc expects callable(0|1|2|3, 0|1|2|3): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 42, ], ]); @@ -1827,11 +1835,11 @@ public function testArrayUintersectAssoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect_assoc.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect_assoc expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); @@ -1842,7 +1850,7 @@ public function testArrayUintersectUassoc(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect_uassoc.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 28, ], [ @@ -1850,7 +1858,7 @@ public function testArrayUintersectUassoc(): void 31, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 39, ], [ @@ -1865,11 +1873,11 @@ public function testArrayUintersect(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/array_uintersect.php'], [ [ - 'Parameter #3 $data_compare_func of function array_uintersect expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int<-1, 1>, Closure(int, int): int<-1, 1> given.', 22, ], [ - 'Parameter #3 $data_compare_func of function array_uintersect expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', + 'Parameter #3 $value_compare_func of function array_uintersect expects callable(1|2|3|4, 1|2|3|4): int<-1, 1>, Closure(string, string): int<-1, 1> given.', 30, ], ]); 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], +); From 5d67b4b79e3927cd92c943105e589cd83d04b7b2 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 13:23:41 +0200 Subject: [PATCH 08/13] allow callbacks in array intersect/diff to return int Fixes regression of issue 9697 --- resources/highest-priority-stubs.stub | 24 ++++----- .../CallToFunctionParametersRuleTest.php | 54 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/resources/highest-priority-stubs.stub b/resources/highest-priority-stubs.stub index 28a52cd903..dcceba8d16 100644 --- a/resources/highest-priority-stubs.stub +++ b/resources/highest-priority-stubs.stub @@ -8,7 +8,7 @@ * * @param array $array * @param array ...$arrays - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -24,7 +24,7 @@ function array_diff_uassoc( * * @param array $array * @param array ...$arrays - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -40,7 +40,7 @@ function array_diff_ukey( * * @param array $array * @param array ...$arrays - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -56,7 +56,7 @@ function array_intersect_uassoc( * * @param array $array * @param array ...$arrays - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -72,7 +72,7 @@ function array_intersect_ukey( * * @param array $array * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func + * @param callable(TV, TV): int $value_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -88,7 +88,7 @@ function array_udiff_assoc( * * @param array $array * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func + * @param callable(TV, TV): int $value_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -104,8 +104,8 @@ function array_uintersect_assoc( * * @param array $array1 * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TV, TV): int $value_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -122,8 +122,8 @@ function array_udiff_uassoc( * * @param array $array * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func - * @param callable(TK, TK): int<-1, 1> $key_compare_func + * @param callable(TV, TV): int $value_compare_func + * @param callable(TK, TK): int $key_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -140,7 +140,7 @@ function array_uintersect_uassoc( * * @param array $array * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func + * @param callable(TV, TV): int $value_compare_func * @return array * @phpstan-skip-php8-stubs */ @@ -156,7 +156,7 @@ function array_udiff( * * @param array $array * @param array ...$arrays - * @param callable(TV, TV): int<-1, 1> $value_compare_func + * @param callable(TV, TV): int $value_compare_func * @return array * @phpstan-skip-php8-stubs */ diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 9fd40a335a..f6778eb52b 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -632,11 +632,11 @@ public function testArrayUdiffCallback(): void { $this->analyse([__DIR__ . '/data/array_udiff.php'], [ [ - 'Parameter #3 $value_compare_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int<-1, 1>, 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 $value_compare_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int<-1, 1>, 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, ], [ @@ -648,7 +648,7 @@ public function testArrayUdiffCallback(): void 21, ], [ - 'Parameter #3 $value_compare_func of function array_udiff expects callable(string, string): int<-1, 1>, 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, ], [ @@ -656,7 +656,7 @@ public function testArrayUdiffCallback(): void 53, ], [ - 'Parameter #4 $value_compare_func of function array_udiff expects callable(25|26|27, 25|26|27): int<-1, 1>, array{26, 27} given.', + 'Parameter #4 $value_compare_func of function array_udiff expects callable(25|26|27, 25|26|27): int, array{26, 27} given.', 56, ], ]); @@ -1729,11 +1729,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1744,11 +1744,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1759,11 +1759,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], [ @@ -1771,7 +1771,7 @@ public function testArrayIntersectUassoc(): void 53, ], [ - 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int<-1, 1>, array{a: 1, b: 2} given.', + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\', \'a\'|\'b\'): int, array{a: 1, b: 2} given.', 56, ], ]); @@ -1782,11 +1782,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1797,11 +1797,11 @@ 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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1812,19 +1812,19 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1835,11 +1835,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1850,19 +1850,19 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); @@ -1873,11 +1873,11 @@ 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<-1, 1>, Closure(int, int): int<-1, 1> given.', + '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<-1, 1>, Closure(string, string): int<-1, 1> given.', + '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, ], ]); From deb7a64eb64bc5afc3c319202a9f4b2536453ed7 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 13:42:51 +0200 Subject: [PATCH 09/13] fix broken tests --- conf/config.neon | 1 + .../BetterReflectionSourceLocatorFactory.php | 2 +- .../SignatureMap/NativeFunctionReflectionProvider.php | 3 +-- src/Testing/TestCaseSourceLocatorFactory.php | 2 +- {resources => stubs}/highest-priority-stubs.stub | 3 ++- .../Reflection/SignatureMap/SignatureMapParserTest.php | 7 ++++--- 6 files changed, 10 insertions(+), 8 deletions(-) rename {resources => stubs}/highest-priority-stubs.stub (96%) diff --git a/conf/config.neon b/conf/config.neon index c85cce945d..1f34351d44 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -171,6 +171,7 @@ parameters: universalObjectCratesClasses: - stdClass stubFiles: + - ../stubs/highest-priority-stubs.stub - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClass.stub - ../stubs/ReflectionClassConstant.stub diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 455f1daf32..acc3ed4cd9 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -122,7 +122,7 @@ public function create(): SourceLocator } $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../../resources/highest-priority-stubs.stub'); + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../../stubs/highest-priority-stubs.stub'); $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 203fe215ef..7c850a582e 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -51,7 +51,6 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $asserts = Assertions::createEmpty(); $docComment = null; $returnsByReference = TrinaryLogic::createMaybe(); - $resolvedPhpDoc = null; try { $reflectionFunction = $this->reflector->reflectFunction($functionName); $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); @@ -75,7 +74,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $functionSignaturesResult = $this->signatureMapProvider->getFunctionSignatures($lowerCasedFunctionName, null, $reflectionFunctionAdapter); - $phpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($lowerCasedFunctionName, array_map(static fn (ParameterSignature $parameter): string => $parameter->getName(), $functionSignaturesResult['positional'][0]->getParameters())) ?? $resolvedPhpDoc; + $phpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($lowerCasedFunctionName, array_map(static fn (ParameterSignature $parameter): string => $parameter->getName(), $functionSignaturesResult['positional'][0]->getParameters())); if ($phpDoc !== null) { if ($phpDoc->hasPhpDocString()) { $docComment = $phpDoc->getPhpDocString(); diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index 7cc00d6acd..61c76a25c2 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -85,7 +85,7 @@ public function create(): SourceLocator $astLocator = new Locator($this->phpParser); $astPhp8Locator = new Locator($this->php8Parser); - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../resources/highest-priority-stubs.stub'); + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../stubs/highest-priority-stubs.stub'); $locators[] = new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); diff --git a/resources/highest-priority-stubs.stub b/stubs/highest-priority-stubs.stub similarity index 96% rename from resources/highest-priority-stubs.stub rename to stubs/highest-priority-stubs.stub index dcceba8d16..bcc620ecd5 100644 --- a/resources/highest-priority-stubs.stub +++ b/stubs/highest-priority-stubs.stub @@ -1,6 +1,7 @@ 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())); From 8fa1b093e806f65382d8dacd69445f1351e723c8 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 13:54:31 +0200 Subject: [PATCH 10/13] extend tests for array intersect/diff functions --- .../CallToFunctionParametersRuleTest.php | 100 ++++++++++++++++++ .../Functions/data/array_diff_uassoc.php | 28 +++++ .../Rules/Functions/data/array_diff_ukey.php | 28 +++++ .../Functions/data/array_intersect_uassoc.php | 4 + .../Functions/data/array_intersect_ukey.php | 28 +++++ .../Functions/data/array_udiff_assoc.php | 28 +++++ .../Functions/data/array_udiff_uassoc.php | 40 +++++++ .../Rules/Functions/data/array_uintersect.php | 28 +++++ .../Functions/data/array_uintersect_assoc.php | 28 +++++ .../data/array_uintersect_uassoc.php | 40 +++++++ 10 files changed, 352 insertions(+) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index f6778eb52b..0125bc8056 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1736,6 +1736,18 @@ public function testArrayDiffUassoc(): void '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, + ], ]); } @@ -1751,6 +1763,18 @@ public function testArrayDiffUkey(): void '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, + ], ]); } @@ -1774,6 +1798,10 @@ public function testArrayIntersectUassoc(): void '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, + ], ]); } @@ -1789,6 +1817,18 @@ public function testArrayIntersectUkey(): void '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, + ], ]); } @@ -1804,6 +1844,18 @@ public function testArrayUdiffAssoc(): void '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, + ], ]); } @@ -1827,6 +1879,18 @@ public function testArrayUdiffUasssoc(): void '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, + ], ]); } @@ -1842,6 +1906,18 @@ public function testArrayUintersectAssoc(): void '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, + ], ]); } @@ -1865,6 +1941,18 @@ public function testArrayUintersectUassoc(): void '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, + ], ]); } @@ -1880,6 +1968,18 @@ public function testArrayUintersect(): void '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, + ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php index 257fc17c85..a1ed5f49ae 100644 --- a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php @@ -31,3 +31,31 @@ 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 index 8a98630a88..e8c7e928ac 100644 --- a/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php +++ b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php @@ -31,3 +31,31 @@ 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 index 7159d5ead1..af3ac5f025 100644 --- a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -55,3 +55,7 @@ static function (string $a, string $b): int { }, ['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 index 4d81086d24..d9d39583fb 100644 --- a/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php @@ -31,3 +31,31 @@ 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_assoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php index 7827734e59..a3b500a1c1 100644 --- a/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php @@ -31,3 +31,31 @@ 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 index f4767621c2..b4431cff78 100644 --- a/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php @@ -43,3 +43,43 @@ 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 index dbe5307a82..1e9a0fbb83 100644 --- a/tests/PHPStan/Rules/Functions/data/array_uintersect.php +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect.php @@ -31,3 +31,31 @@ 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 index e410f3827e..2b268fd30c 100644 --- a/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php @@ -31,3 +31,31 @@ 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 index f079aec189..f84d65aa4e 100644 --- a/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php @@ -43,3 +43,43 @@ 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; + }, +); From be327707f365d7b3dd3b6950f3fb92da7b53dc66 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 13:57:00 +0200 Subject: [PATCH 11/13] fix phpstan/phpcs issues --- src/Reflection/SignatureMap/Php8SignatureMapProvider.php | 1 + src/Rules/FunctionCallParametersCheck.php | 1 + src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index f334a754aa..84c1a777ce 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -29,6 +29,7 @@ use function explode; use function is_string; use function sprintf; +use function str_contains; use function strtolower; class Php8SignatureMapProvider implements SignatureMapProvider diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index c552a456e8..21ed5f6348 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; diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index 5d4f65420c..e2420576ac 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -58,6 +58,7 @@ class InvalidPHPStanDocTagRule implements Rule '@phpstan-param-immediately-invoked-callable', '@phpstan-param-later-invoked-callable', '@phpstan-param-closure-this', + '@phpstan-skip-php8-stubs', ]; public function __construct( From 9b03018837f313ebb80ec7cbbf451c5af8566fb3 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 14:04:38 +0200 Subject: [PATCH 12/13] remove useless todo --- src/Rules/FunctionCallParametersCheck.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 21ed5f6348..2808b0ef80 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -533,7 +533,6 @@ private function processArguments( $originalParameters = $expandedOriginalParameters; $newArguments = []; - // TODO: check isVariadic usages in below code - they may not be necessary anymore. $namedArgumentAlreadyOccurred = false; foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine]) { if ($argumentName === null) { From 929d3500fb918df0eac6cc608b9929bb721322d1 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 9 Aug 2024 17:34:27 +0200 Subject: [PATCH 13/13] simplify code --- conf/config.neon | 1 - .../BetterReflectionSourceLocatorFactory.php | 1 - .../SignatureMap/Php8SignatureMapProvider.php | 23 ++- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 1 - src/Testing/TestCaseSourceLocatorFactory.php | 3 - stubs/arrayFunctions.stub | 154 ++++++++++++++++ stubs/highest-priority-stubs.stub | 168 ------------------ 7 files changed, 175 insertions(+), 176 deletions(-) delete mode 100644 stubs/highest-priority-stubs.stub diff --git a/conf/config.neon b/conf/config.neon index 1f34351d44..c85cce945d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -171,7 +171,6 @@ parameters: universalObjectCratesClasses: - stdClass stubFiles: - - ../stubs/highest-priority-stubs.stub - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClass.stub - ../stubs/ReflectionClassConstant.stub diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index acc3ed4cd9..5a207d0476 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -122,7 +122,6 @@ public function create(): SourceLocator } $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../../stubs/highest-priority-stubs.stub'); $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 84c1a777ce..fb6176b470 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -27,9 +27,9 @@ use function array_map; use function count; use function explode; +use function in_array; use function is_string; use function sprintf; -use function str_contains; use function strtolower; class Php8SignatureMapProvider implements SignatureMapProvider @@ -188,7 +188,26 @@ public function getFunctionSignatures(string $functionName, ?string $className, $lowerName = strtolower($functionName); if ( !array_key_exists($lowerName, $this->map->functions) - || ($reflectionFunction !== null && $reflectionFunction->getDocComment() !== false && str_contains($reflectionFunction->getDocComment(), '@phpstan-skip-php8-stubs')) + || ( + $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/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index e2420576ac..5d4f65420c 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -58,7 +58,6 @@ class InvalidPHPStanDocTagRule implements Rule '@phpstan-param-immediately-invoked-callable', '@phpstan-param-later-invoked-callable', '@phpstan-param-closure-this', - '@phpstan-skip-php8-stubs', ]; public function __construct( diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index 61c76a25c2..fccedd9ec1 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -16,7 +16,6 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker; use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; -use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository; use PHPStan\Reflection\BetterReflection\SourceLocator\PhpVersionBlacklistSourceLocator; use ReflectionClass; use function dirname; @@ -36,7 +35,6 @@ class TestCaseSourceLocatorFactory * @param array{analyse?: array, analyseAndScan?: array}|null $excludePaths */ public function __construct( - private OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, private ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker, private Parser $phpParser, private Parser $php8Parser, @@ -85,7 +83,6 @@ public function create(): SourceLocator $astLocator = new Locator($this->phpParser); $astPhp8Locator = new Locator($this->php8Parser); - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate(__DIR__ . '/../../stubs/highest-priority-stubs.stub'); $locators[] = new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 0adacfaf29..27e51e1181 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -49,8 +49,162 @@ function uksort(array &$array, callable $callback): bool { } +/** + * @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( + 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/stubs/highest-priority-stubs.stub b/stubs/highest-priority-stubs.stub deleted file mode 100644 index bcc620ecd5..0000000000 --- a/stubs/highest-priority-stubs.stub +++ /dev/null @@ -1,168 +0,0 @@ - $array - * @param array ...$arrays - * @param callable(TK, TK): int $key_compare_func - * @return array - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -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 - * @phpstan-skip-php8-stubs - */ -function array_udiff( - 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 - * @phpstan-skip-php8-stubs - */ -function array_uintersect( - array $array, - array ...$arrays, - callable $value_compare_func, -): array {}