diff --git a/resources/functionMap.php b/resources/functionMap.php index b4a78d684e..f3b563aa48 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8190,7 +8190,7 @@ 'preg_match_all' => ['0|positive-int|false|null', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delim_char='=>'string'], 'preg_replace' => ['string|array|null', 'regex'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], -'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], +'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_split' => ['list|list}>|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'], 'prev' => ['mixed', '&rw_array_arg'=>'array|object'], diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index ac48bed6ff..bade1e3f2d 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -44,7 +44,7 @@ 'opcache_get_configuration' => ['array{directives: array{\'opcache.enable\': bool, \'opcache.enable_cli\': bool, \'opcache.use_cwd\': bool, \'opcache.validate_timestamps\': bool, \'opcache.validate_permission\': bool, \'opcache.validate_root\'?: bool, \'opcache.dups_fix\': bool, \'opcache.revalidate_path\': bool, \'opcache.log_verbosity_level\': int, \'opcache.memory_consumption\': int, \'opcache.interned_strings_buffer\': int, \'opcache.max_accelerated_files\': int, \'opcache.max_wasted_percentage\': float, \'opcache.consistency_checks\': int, \'opcache.force_restart_timeout\': int, \'opcache.revalidate_freq\': int, \'opcache.preferred_memory_model\': string, \'opcache.blacklist_filename\': string, \'opcache.max_file_size\': int, \'opcache.error_log\': string, \'opcache.protect_memory\': bool, \'opcache.save_comments\': bool, \'opcache.enable_file_override\': bool, \'opcache.optimization_level\': int, \'opcache.lockfile_path\'?: string, \'opcache.mmap_base\'?: string, \'opcache.file_cache\': string, \'opcache.file_cache_only\': bool, \'opcache.file_cache_consistency_checks\': bool, \'opcache.file_cache_fallback\'?: bool, \'opcache.file_update_protection\': int, \'opcache.opt_debug_level\': int, \'opcache.restrict_api\': string, \'opcache.huge_code_pages\'?: bool, \'opcache.preload\': string, \'opcache.preload_user\'?: string, \'opcache.cache_id\'?: string}, version: array{version: non-empty-string, opcache_product_name: non-empty-string}, blacklist: list}|false'], 'password_algos' => ['list'], 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], - 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], + 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], 'sapi_windows_set_ctrl_handler' => ['bool', 'callable'=>'callable(int):void', 'add='=>'bool'], 'ReflectionProperty::getType' => ['?ReflectionType'], diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php index 7bd70492ee..b9aa8f5482 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php @@ -2,6 +2,7 @@ namespace PregReplaceCallbackMatchShapes; +use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; function (string $s): void { @@ -56,3 +57,18 @@ function ($matches) { $string ); } + +function bug14904(string $string): void { + preg_replace_callback( + '/a|(?b)/', + function (array $match): string { + assertType("array{0: non-empty-string, b: 'b'|null, 1: 'b'|null}", $match); + assertNativeType('array', $match); + return ''; + }, + $string, + -1, + $count, + PREG_UNMATCHED_AS_NULL + ); +} diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 436a0125a5..f8f38214d8 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -673,6 +673,13 @@ public function testBug6260(): void $this->analyse([__DIR__ . '/data/bug-6260.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testBug14904(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-14904.php'], []); + } + public function testBug8736(): void { $this->analyse([__DIR__ . '/data/bug-8736.php'], []); diff --git a/tests/PHPStan/Rules/Comparison/data/bug-14904.php b/tests/PHPStan/Rules/Comparison/data/bug-14904.php new file mode 100644 index 0000000000..adbf20f150 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-14904.php @@ -0,0 +1,18 @@ += 8.0 + +namespace Bug14904; + +function doFoo(): void +{ + preg_replace_callback( + '/a|(?b)/', + function (array $match): string { + if ($match['b'] !== null) { + return 'aa'; + } + return 'possible?'; + }, + 'abcd', + flags: PREG_UNMATCHED_AS_NULL, + ); +}