From 39496748cf9d9bbf0ce932f984ccd59f5ec85a85 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Tue, 3 Nov 2026 10:10:18 +0530 Subject: [PATCH 01/33] Code Quality: Remove `null` from `validate_another_blog_signup()` return type. The function returns `true` or `false`. When the user is not logged in, it calls `die()` and does not return. Removing `null` from the `@return` tag removes the `return.unusedType` entry from the PHPStan baseline. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-signup.php | 4 ++-- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/wp-signup.php b/src/wp-signup.php index 4eb8c60b21408..dd4e5d5365f74 100644 --- a/src/wp-signup.php +++ b/src/wp-signup.php @@ -443,8 +443,8 @@ function signup_another_blog( $blogname = '', $blog_title = '', $errors = '' ) { * @global string $domain The new site's domain. * @global string $path The new site's path. * - * @return null|bool True if site signup was validated, false on error. - * The function halts all execution if the user is not logged in. + * @return bool True if site signup was validated, false on error. + * The function halts all execution if the user is not logged in. */ function validate_another_blog_signup() { global $blogname, $blog_title, $errors, $domain, $path; diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 2acebf529a781..023c9a2a093c9 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -93,8 +93,3 @@ parameters: identifier: return.unusedType count: 1 path: ../../../src/wp-includes/user.php - - - message: '#^Function validate_another_blog_signup\(\) never returns null so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-signup.php From 3041f5429f70a4a642f1c24a768a652e16428e0a Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:00 +0530 Subject: [PATCH 02/33] Code Quality: Document arrays in the `plugins_api_result` filter. `plugins_api()` documents that it returns an array for the `hot_tags` action, and the docblock says the `plugins_api_result` filter can change the response object or array. The filter's `@param` tag listed only `object|WP_Error`, so PHPStan concluded that the function never returns an array. This change adds `array` to the filter's `@param` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-admin/includes/plugin-install.php | 6 +++--- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php index ffae71c5b81f6..496b7ffd36679 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -223,9 +223,9 @@ function plugins_api( $action, $args = array() ) { * * @since 2.7.0 * - * @param object|WP_Error $res Response object or WP_Error. - * @param string $action The type of information being requested from the Plugin Installation API. - * @param object $args Plugin API arguments. + * @param object|array|WP_Error $res Response object or array, or WP_Error on failure. + * @param string $action The type of information being requested from the Plugin Installation API. + * @param object $args Plugin API arguments. */ return apply_filters( 'plugins_api_result', $res, $action, $args ); } diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 023c9a2a093c9..65b1f241dce3c 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function plugins_api\(\) never returns array so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-admin/includes/plugin-install.php - message: '#^Function _fix_attachment_links\(\) never returns WP_Error so it can be removed from the return type\.$#' identifier: return.unusedType From 8c42ca17f2042778454d803c45e5f0eb9e243d9d Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:08 +0530 Subject: [PATCH 03/33] Code Quality: Remove `WP_Error` from `_fix_attachment_links()` return type. The function calls `wp_update_post()` without the `$wp_error` argument, so an update failure returns `0` and never a `WP_Error`. This change removes `WP_Error` from the `@return` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-admin/includes/post.php | 2 +- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index a255673aad601..64b5793d2cffc 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -1163,7 +1163,7 @@ function update_meta( $meta_id, $meta_key, $meta_value ) { * @access private * * @param int|WP_Post $post Post ID or post object. - * @return void|int|WP_Error Void if nothing fixed. 0 or WP_Error on update failure. The post ID on update success. + * @return void|int Void if nothing fixed. 0 on update failure. The post ID on update success. */ function _fix_attachment_links( $post ) { $post = get_post( $post, ARRAY_A ); diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 65b1f241dce3c..910aad4a7c9a5 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function _fix_attachment_links\(\) never returns WP_Error so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-admin/includes/post.php - message: '#^Function get_preferred_from_update_core\(\) never returns array so it can be removed from the return type\.$#' identifier: return.unusedType From 41ba3559c64458a13f97c6c7effcebd4d75efebc Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:08 +0530 Subject: [PATCH 04/33] Code Quality: Remove `array` from `get_preferred_from_update_core()` return type. The function returns the first update object from `get_core_updates()`, a placeholder object when there are no updates, or `false`. It never returns an array. This change removes `array` from the `@return` tag and removes the matching `return.unusedType` baseline entry. `WP_Automatic_Updater` reads properties from the result of this function. Before this change, PHPStan reported three `property.nonObject` errors there because the result could be an array. This change also removes those three baseline entries. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-admin/includes/update.php | 2 +- tests/phpstan/baselines/property.nonObject.neon | 15 --------------- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index c3c15c8fe3f2c..04dd3c84d1bd1 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -11,7 +11,7 @@ * * @since 2.7.0 * - * @return object|array|false The response from the API on success, false on failure. + * @return object|false The response from the API on success, false on failure. */ function get_preferred_from_update_core() { $updates = get_core_updates(); diff --git a/tests/phpstan/baselines/property.nonObject.neon b/tests/phpstan/baselines/property.nonObject.neon index 895c9e3656d3d..d04aff6ba2e14 100644 --- a/tests/phpstan/baselines/property.nonObject.neon +++ b/tests/phpstan/baselines/property.nonObject.neon @@ -48,21 +48,6 @@ parameters: identifier: property.nonObject count: 1 path: ../../../src/wp-admin/includes/class-theme-upgrader.php - - - message: '#^Cannot access property \$current on array\|object\.$#' - identifier: property.nonObject - count: 3 - path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php - - - message: '#^Cannot access property \$response on array\|object\.$#' - identifier: property.nonObject - count: 1 - path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php - - - message: '#^Cannot access property \$version on array\|object\.$#' - identifier: property.nonObject - count: 1 - path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php - message: '#^Cannot access property \$info on array\|object\.$#' identifier: property.nonObject diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 910aad4a7c9a5..b4c70df5d28ab 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function get_preferred_from_update_core\(\) never returns array so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-admin/includes/update.php - message: '#^Function get_the_tag_list\(\) never returns WP_Error so it can be removed from the return type\.$#' identifier: return.unusedType From 6349af4763475a87c283652c82cf06158d40dedf Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:18 +0530 Subject: [PATCH 05/33] Code Quality: Correct the `$tag_list` type in the `the_tags` filter. `get_the_tag_list()` passes the result of `get_the_term_list()` to the `the_tags` filter. That result is a string, `false` when there are no terms, or a `WP_Error` on failure. The filter's `@param` tag listed only `string`, so PHPStan concluded that the function never returns `false` or `WP_Error`. This change documents all three types on the filter and removes the two matching `return.unusedType` baseline entries. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/category-template.php | 11 ++++++----- tests/phpstan/baselines/return.unusedType.neon | 10 ---------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/wp-includes/category-template.php b/src/wp-includes/category-template.php index f48ebe6adb3d7..130f55e3fe043 100644 --- a/src/wp-includes/category-template.php +++ b/src/wp-includes/category-template.php @@ -1224,11 +1224,12 @@ function get_the_tag_list( $before = '', $sep = '', $after = '', $post_id = 0 ) * * @since 2.3.0 * - * @param string $tag_list List of tags. - * @param string $before String to use before the tags. - * @param string $sep String to use between the tags. - * @param string $after String to use after the tags. - * @param int $post_id Post ID. + * @param string|false|WP_Error $tag_list A list of tags on success, false if there are no terms, + * WP_Error on failure. + * @param string $before String to use before the tags. + * @param string $sep String to use between the tags. + * @param string $after String to use after the tags. + * @param int $post_id Post ID. */ return apply_filters( 'the_tags', $tag_list, $before, $sep, $after, $post_id ); } diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index b4c70df5d28ab..c003f10fcd0fe 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,16 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function get_the_tag_list\(\) never returns WP_Error so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/category-template.php - - - message: '#^Function get_the_tag_list\(\) never returns false so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/category-template.php - message: '#^Function get_category\(\) never returns null so it can be removed from the return type\.$#' identifier: return.unusedType From 37dc5ce91a843fe44058a9cd5bf6b685aa8bc9d8 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:18 +0530 Subject: [PATCH 06/33] Code Quality: Accept `null` in the `_make_cat_compat()` parameter type. `get_category()` passes the result of `get_term()` to `_make_cat_compat()` by reference. That result is `null` when the category does not exist, and `_make_cat_compat()` leaves `null` unchanged. The parameter type did not include `null`, so PHPStan assumed that the variable could not be `null` after the call. It then reported that `get_category()` never returns `null`. This change adds `null` to the parameter type and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/category.php | 2 +- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/wp-includes/category.php b/src/wp-includes/category.php index e6085418e255d..e93c6c122eb75 100644 --- a/src/wp-includes/category.php +++ b/src/wp-includes/category.php @@ -428,7 +428,7 @@ function clean_category_cache( $id ) { * @since 4.4.0 The `$category` parameter now also accepts a WP_Term object. * @access private * - * @param array|object|WP_Term $category Category row object or array. + * @param array|object|WP_Term|null $category Category row object or array. Null is left unchanged. */ function _make_cat_compat( &$category ) { if ( is_object( $category ) && ! is_wp_error( $category ) ) { diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index c003f10fcd0fe..ea8f0b23ac2c0 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function get_category\(\) never returns null so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/category.php - message: '#^Method WP_Recovery_Mode_Cookie_Service\:\:recovery_mode_hash\(\) never returns false so it can be removed from the return type\.$#' identifier: return.unusedType From 5e739c49e3a0f21116f8a394a01346353ce24c48 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:25 +0530 Subject: [PATCH 07/33] Code Quality: Remove `false` from `recovery_mode_hash()` return type. `WP_Recovery_Mode_Cookie_Service::recovery_mode_hash()` returns the result of `hash_hmac()` with the `sha1` algorithm. `hash_hmac()` returns `false` only for an unknown algorithm, and `sha1` is always available. This change removes `false` from the `@return` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/class-wp-recovery-mode-cookie-service.php | 2 +- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/wp-includes/class-wp-recovery-mode-cookie-service.php b/src/wp-includes/class-wp-recovery-mode-cookie-service.php index a2ee34a723aa8..feefc942b6d52 100644 --- a/src/wp-includes/class-wp-recovery-mode-cookie-service.php +++ b/src/wp-includes/class-wp-recovery-mode-cookie-service.php @@ -195,7 +195,7 @@ private function generate_cookie() { * @since 5.2.0 * * @param string $data Data to hash. - * @return string|false The hashed $data, or false on failure. + * @return string The hashed $data. */ private function recovery_mode_hash( $data ) { $default_keys = array_unique( diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index ea8f0b23ac2c0..3c5f1cf39dc4d 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Method WP_Recovery_Mode_Cookie_Service\:\:recovery_mode_hash\(\) never returns false so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/class-wp-recovery-mode-cookie-service.php - message: '#^Function wp_get_code_editor_settings\(\) never returns false so it can be removed from the return type\.$#' identifier: return.unusedType From be0e86bca5237f1134b4343d0abbde183eaf985d Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:34 +0530 Subject: [PATCH 08/33] Code Quality: Document `false` in the `wp_code_editor_settings` filter. The `wp_code_editor_settings` filter description says that a falsey value disables the code editor, and `wp_enqueue_code_editor()` checks for it. The filter's `@param` tag listed only `array`, so PHPStan concluded that `wp_get_code_editor_settings()` never returns `false`. This change adds `false` to the filter's `@param` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/general-template.php | 6 +++--- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 914b0bff3bcab..fcf0c41289624 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -4760,9 +4760,9 @@ function wp_get_code_editor_settings( $args ) { * * @since 4.9.0 * - * @param array $settings The array of settings passed to the code editor. - * A falsey value disables the editor. - * @param array $args { + * @param array|false $settings The array of settings passed to the code editor. + * A falsey value disables the editor. + * @param array $args { * Args passed when calling `get_code_editor_settings()`. * * @type string $type The MIME type of the file to be edited. diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 3c5f1cf39dc4d..0c874406d47bf 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function wp_get_code_editor_settings\(\) never returns false so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/general-template.php - message: '#^Function get_post_gallery\(\) never returns string so it can be removed from the return type\.$#' identifier: return.unusedType From ec8f92905fee79c541fce9e68d00a859ff91316b Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:41 +0530 Subject: [PATCH 09/33] Code Quality: Correct the `get_post_gallery()` return and filter types. `get_post_gallery()` returns the first item of `get_post_galleries()`. That item is an HTML string when `$html` is true, and an array otherwise. When the post has no gallery, `reset()` returns `false`. The `get_post_gallery` filter's `@param` tag listed only `array`, so PHPStan concluded that the function never returns a string. This change documents `string|array|false` on the filter and on the function, and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/media.php | 11 +++++++---- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 4cd69ade6cbb7..f12b2616d97dc 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5587,7 +5587,9 @@ function get_post_galleries( $post, $html = true ) { * * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. * @param bool $html Optional. Whether to return HTML or data. Default is true. - * @return string|array Gallery data and srcs parsed from the expanded shortcode. + * @return string|array|false Gallery HTML or an array of gallery data and srcs parsed from + * the expanded shortcode, depending on `$html`. False if the post has + * no gallery. */ function get_post_gallery( $post = 0, $html = true ) { $galleries = get_post_galleries( $post, $html ); @@ -5598,9 +5600,10 @@ function get_post_gallery( $post = 0, $html = true ) { * * @since 3.6.0 * - * @param array $gallery The first-found post gallery. - * @param int|WP_Post $post Post ID or object. - * @param array $galleries Associative array of all found post galleries. + * @param string|array|false $gallery The first-found post gallery, or false if the post has no gallery. + * An HTML string when `$html` is true, otherwise an array. + * @param int|WP_Post $post Post ID or object. + * @param array $galleries Associative array of all found post galleries. */ return apply_filters( 'get_post_gallery', $gallery, $post, $galleries ); } diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 0c874406d47bf..c15fe978a4c8a 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Function get_post_gallery\(\) never returns string so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/media.php - message: '#^Function wp_imagecreatetruecolor\(\) never returns GdImage so it can be removed from the return type\.$#' identifier: return.unusedType From d3760b9eb6de53f18950adf9859336acfe2605ae Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:54 +0530 Subject: [PATCH 10/33] Code Quality: Document `false` in the `wp_mime_type_icon` filter. `wp_mime_type_icon()` first reads the icon from the object cache, and `wp_cache_get()` returns `false` on a miss. If no icon file matches the MIME type, `$icon` stays `false` and the function passes it to the `wp_mime_type_icon` filter. The filter's `@param` tag listed only `string`, so PHPStan concluded that the function never returns `false`. This change adds `false` to the filter's `@param` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/post.php | 8 ++++---- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 4cb9e69f5fc44..e010ed5514412 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7771,10 +7771,10 @@ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { * * @since 2.1.0 * - * @param string $icon Path to the mime type icon. - * @param string $mime Mime type. - * @param int $post_id Attachment ID. Will equal 0 if the function passed - * the mime type. + * @param string|false $icon Path to the mime type icon, or false if no icon was found. + * @param string $mime Mime type. + * @param int $post_id Attachment ID. Will equal 0 if the function passed + * the mime type. */ return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id ); } diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index c15fe978a4c8a..a6050f4011fe1 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -23,11 +23,6 @@ parameters: identifier: return.unusedType count: 1 path: ../../../src/wp-includes/media.php - - - message: '#^Function wp_mime_type_icon\(\) never returns false so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/post.php - message: '#^Function _set_preview\(\) never returns false so it can be removed from the return type\.$#' identifier: return.unusedType From 8b55de83e3ec95daa3579929b95b36dbf6e4a9fb Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:14:54 +0530 Subject: [PATCH 11/33] Code Quality: Remove `false` from `_set_preview()` return type. `_set_preview()` returns the post object that it receives, with the autosave content applied when one exists. It has no path that returns `false`. This change removes `false` from the `@return` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/revision.php | 2 +- tests/phpstan/baselines/return.unusedType.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/wp-includes/revision.php b/src/wp-includes/revision.php index 179a32c2832c5..29bf2b07dd417 100644 --- a/src/wp-includes/revision.php +++ b/src/wp-includes/revision.php @@ -868,7 +868,7 @@ function wp_revisions_to_keep( $post ) { * @access private * * @param WP_Post $post - * @return WP_Post|false + * @return WP_Post */ function _set_preview( $post ) { if ( ! is_object( $post ) ) { diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index a6050f4011fe1..2b95eccf75a4c 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -23,11 +23,6 @@ parameters: identifier: return.unusedType count: 1 path: ../../../src/wp-includes/media.php - - - message: '#^Function _set_preview\(\) never returns false so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/revision.php - message: '#^Function get_term_to_edit\(\) never returns int so it can be removed from the return type\.$#' identifier: return.unusedType From beb353adf8c94dd335987ab7a532719841d723e8 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:15:08 +0530 Subject: [PATCH 12/33] Code Quality: Correct the `get_term_to_edit()` return type. `get_term_to_edit()` returns the `WP_Error` from `get_term()`, an empty string when the term does not exist, or the term object from `sanitize_term()`. It never returns an integer or `null`, and the `@return` tag did not list the term object. This change documents `WP_Term|string|WP_Error` and removes the two matching `return.unusedType` baseline entries. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/taxonomy.php | 3 ++- tests/phpstan/baselines/return.unusedType.neon | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 6656a2f8e364a..60a783887003a 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1273,7 +1273,8 @@ function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) { * * @param int|object $id Term ID or object. * @param string $taxonomy Taxonomy name. - * @return string|int|null|WP_Error Will return empty string if $term is not an object. + * @return WP_Term|string|WP_Error The term sanitized for editing, WP_Error if the taxonomy does not exist, + * or an empty string if the term does not exist. */ function get_term_to_edit( $id, $taxonomy ) { $term = get_term( $id, $taxonomy ); diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index 2b95eccf75a4c..f644d636eba1d 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -23,16 +23,6 @@ parameters: identifier: return.unusedType count: 1 path: ../../../src/wp-includes/media.php - - - message: '#^Function get_term_to_edit\(\) never returns int so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/taxonomy.php - - - message: '#^Function get_term_to_edit\(\) never returns null so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/taxonomy.php - message: '#^Function wp_is_password_reset_allowed_for_user\(\) never returns WP_Error so it can be removed from the return type\.$#' identifier: return.unusedType From c95d168a63574a0132ed1ab04fc7c770098ddd09 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:15:15 +0530 Subject: [PATCH 13/33] Code Quality: Document `WP_Error` in the `allow_password_reset` filter. `retrieve_password()` checks whether `wp_is_password_reset_allowed_for_user()` returns a `WP_Error` and passes it on, so a filter callback can return a `WP_Error` to explain why a reset is not allowed. The filter's `@param` tag listed only `bool`, so PHPStan concluded that the function never returns a `WP_Error`. This change adds `WP_Error` to the filter's `@param` tag and removes the matching `return.unusedType` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/user.php | 5 +++-- tests/phpstan/baselines/return.unusedType.neon | 7 +------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 02a412b291a8e..1614ac764b587 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -5341,8 +5341,9 @@ function wp_is_password_reset_allowed_for_user( $user ) { * * @since 2.7.0 * - * @param bool $allow Whether to allow the password to be reset. Default true. - * @param int $user_id The ID of the user attempting to reset a password. + * @param bool|WP_Error $allow Whether to allow the password to be reset, or a WP_Error to explain + * why it is not allowed. Default true. + * @param int $user_id The ID of the user attempting to reset a password. */ return apply_filters( 'allow_password_reset', $allow, $user->ID ); } diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon index f644d636eba1d..76f72f6a8a574 100644 --- a/tests/phpstan/baselines/return.unusedType.neon +++ b/tests/phpstan/baselines/return.unusedType.neon @@ -22,9 +22,4 @@ parameters: message: '#^Function wp_imagecreatetruecolor\(\) never returns GdImage so it can be removed from the return type\.$#' identifier: return.unusedType count: 1 - path: ../../../src/wp-includes/media.php - - - message: '#^Function wp_is_password_reset_allowed_for_user\(\) never returns WP_Error so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/user.php + path: ../../../src/wp-includes/media.php \ No newline at end of file From a6d0682be03dd41eaa01a5ad06f32faa96ea6807 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:16:12 +0530 Subject: [PATCH 14/33] Build/Test Tools: Remove the `return.unusedType` PHPStan baseline. The earlier commits fixed every other entry in this baseline. The last entry reports that `wp_imagecreatetruecolor()` never returns `GdImage`. That report is a false positive. `imagecreatetruecolor()` returns a resource on PHP 7.4 and a `GdImage` instance on PHP 8.0+. PHPStan uses its pre-8.0 function map because `phpVersion.min` is 70400, so it sees only the resource. This change moves that entry to the permanent ignore list in `phpstan.neon.dist` with an explanation, deletes the baseline file, and removes its `includes` line. See #65817. Co-Authored-By: Claude Opus 5.5 --- phpstan.neon.dist | 13 +++++++++- .../phpstan/baselines/return.unusedType.neon | 25 ------------------- 2 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 tests/phpstan/baselines/return.unusedType.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 655bb9bbc807c..29ed8929eba7e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -53,7 +53,6 @@ includes: - tests/phpstan/baselines/property.notFound.neon - tests/phpstan/baselines/property.private.neon - tests/phpstan/baselines/return.type.neon - - tests/phpstan/baselines/return.unusedType.neon - tests/phpstan/baselines/varTag.noVariable.neon - tests/phpstan/baselines/variable.undefined.neon # phpstan:baselines end @@ -116,6 +115,18 @@ parameters: path: src/wp-includes/compat.php reportUnmatched: false + # Level 4: + # wp_imagecreatetruecolor() returns whatever imagecreatetruecolor() returns: a resource on PHP 7.4 and a + # GdImage instance on PHP 8.0+. Its docblock lists both, which is correct for WP's 7.4+ range. Because + # `phpVersion.min` is 70400, PHPStan reads the function from its pre-8.0 map, sees only a resource, and + # reports GdImage as unused. Removing GdImage would make the docblock wrong on every PHP 8 install. + # `reportUnmatched: false` so this entry lapses quietly if PHPStan stops reporting it. + - + message: '#^Function wp_imagecreatetruecolor\(\) never returns GdImage so it can be removed from the return type\.$#' + identifier: return.unusedType + path: src/wp-includes/media.php + reportUnmatched: false + # Level 5: # substr_compare()'s $length has always accepted null, meaning "compare the full length". PHP 7.4 spelled # that `int $length = null`, where the null default makes the parameter implicitly nullable, and PHP 8.0 diff --git a/tests/phpstan/baselines/return.unusedType.neon b/tests/phpstan/baselines/return.unusedType.neon deleted file mode 100644 index 76f72f6a8a574..0000000000000 --- a/tests/phpstan/baselines/return.unusedType.neon +++ /dev/null @@ -1,25 +0,0 @@ -# PHPStan baseline for the `return.unusedType` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/return.unusedType -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=return.unusedType -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Function wp_imagecreatetruecolor\(\) never returns GdImage so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: ../../../src/wp-includes/media.php \ No newline at end of file From fc5d8c1b8899f10a0d82c6a35efc735d0870e172 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 13:34:47 +0530 Subject: [PATCH 15/33] Code Quality: Fix docblock continuation alignment. The continuation lines in the `get_term_to_edit()` `@return` tag and the `allow_password_reset` filter `@param` tag started one column too early. This change aligns them with the description text above. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/taxonomy.php | 2 +- src/wp-includes/user.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 60a783887003a..ea9d4b04d137d 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1274,7 +1274,7 @@ function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) { * @param int|object $id Term ID or object. * @param string $taxonomy Taxonomy name. * @return WP_Term|string|WP_Error The term sanitized for editing, WP_Error if the taxonomy does not exist, - * or an empty string if the term does not exist. + * or an empty string if the term does not exist. */ function get_term_to_edit( $id, $taxonomy ) { $term = get_term( $id, $taxonomy ); diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 1614ac764b587..518278feb9ed5 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -5342,7 +5342,7 @@ function wp_is_password_reset_allowed_for_user( $user ) { * @since 2.7.0 * * @param bool|WP_Error $allow Whether to allow the password to be reset, or a WP_Error to explain - * why it is not allowed. Default true. + * why it is not allowed. Default true. * @param int $user_id The ID of the user attempting to reset a password. */ return apply_filters( 'allow_password_reset', $allow, $user->ID ); From 9656fad7ace1cd4da194498e0c5390778860dd8d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 09:42:11 -0800 Subject: [PATCH 16/33] Add conditional return type to sanitize_term() Mirror sanitize_post() so that sanitize_term() returns the same type it was given: a WP_Term stays a WP_Term, an array stays an array, and any other object stays an object. Previously the return was array|object regardless of input, so get_term_to_edit() was reported as returning array|object instead of its documented WP_Term|string|WP_Error. Also narrow get_term_to_edit() with `@phpstan-return WP_Term|''|WP_Error`, since an empty string is the only string it returns, and note that a WP_Error is also returned when the term ID is empty. This removes 16 PHPStan errors across taxonomy.php, class-wp-term.php, and class-wp-terms-list-table.php without adding any. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/taxonomy.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index dd0ff73485e3b..e745fe45d17a7 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1273,8 +1273,10 @@ function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) { * * @param int|object $id Term ID or object. * @param string $taxonomy Taxonomy name. - * @return WP_Term|string|WP_Error The term sanitized for editing, WP_Error if the taxonomy does not exist, - * or an empty string if the term does not exist. + * @return WP_Term|string|WP_Error The term sanitized for editing, WP_Error if the term ID is empty or the + * taxonomy does not exist, or an empty string if the term does not exist. + * + * @phpstan-return WP_Term|''|WP_Error */ function get_term_to_edit( $id, $taxonomy ) { $term = get_term( $id, $taxonomy ); @@ -1792,7 +1794,13 @@ function term_is_ancestor_of( $term1, $term2, $taxonomy ) { * @param string $context Optional. Context in which to sanitize the term. * Accepts 'raw', 'edit', 'db', 'display', 'rss', * 'attribute', or 'js'. Default 'display'. - * @return array|object Term with all fields sanitized. + * @return array|object Term with all fields sanitized (will be the same type as `$term`). + * + * @phpstan-return ( + * $term is WP_Term ? WP_Term : ( + * $term is array ? array : object + * ) + * ) */ function sanitize_term( $term, $taxonomy, $context = 'display' ) { $fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' ); From 768c852f02a32291de5194f392be106a5799a00b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:03:01 -0800 Subject: [PATCH 17/33] Document sanitize_term() as taking a string-keyed array Add `@phpstan-param object|array $term` and return `array` for the array case, since the function reads and writes term fields by name. This documents what sanitize_term() actually requires. Callers that pass loosely typed arrays (sanitize_category(), wp_insert_term(), and wp_update_term()) are now flagged with argument.type errors; their own array types should be narrowed to match in a follow-up. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/taxonomy.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index e745fe45d17a7..fd36338b8c9b3 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1796,9 +1796,10 @@ function term_is_ancestor_of( $term1, $term2, $taxonomy ) { * 'attribute', or 'js'. Default 'display'. * @return array|object Term with all fields sanitized (will be the same type as `$term`). * + * @phpstan-param object|array $term * @phpstan-return ( * $term is WP_Term ? WP_Term : ( - * $term is array ? array : object + * $term is array ? array : object * ) * ) */ From 59b545aa0eb3b8e29ded46219a1a1c3db6312a75 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:19:08 -0800 Subject: [PATCH 18/33] Document sanitize_term_field() as accepting int values The function casts the integer fields (term_id, parent, count, term_group, term_taxonomy_id, and object_id) to int before anything else, and WP_Term holds those fields as ints, so `$value` is `string|int` rather than just `string`. Widen the `@param` and the `T` template bound to match. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/taxonomy.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index fd36338b8c9b3..61f73d88774cb 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1846,16 +1846,16 @@ function sanitize_term( $term, $taxonomy, $context = 'display' ) { * * @since 2.3.0 * - * @param string $field Term field to sanitize. - * @param string $value Search for this term value. - * @param int $term_id Term ID. - * @param string $taxonomy Taxonomy name. - * @param string $context Context in which to sanitize the term field. - * Accepts 'raw', 'edit', 'db', 'display', 'rss', - * 'attribute', or 'js'. + * @param string $field Term field to sanitize. + * @param string|int $value Search for this term value. + * @param int $term_id Term ID. + * @param string $taxonomy Taxonomy name. + * @param string $context Context in which to sanitize the term field. + * Accepts 'raw', 'edit', 'db', 'display', 'rss', + * 'attribute', or 'js'. * @return mixed Sanitized field. * - * @phpstan-template T of string + * @phpstan-template T of string|int * @phpstan-param T $value * @phpstan-return ( * $field is 'parent'|'term_id'|'count'|'term_group'|'term_taxonomy_id'|'object_id' From 4b86354512a20fe35497c25a2c49bc1ce17eee3a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:42:48 -0800 Subject: [PATCH 19/33] Always pass string|false to the wp_mime_type_icon filter When wp_mime_type_icon() was given an attachment ID (or 0) and no icon matched, `$icon` was never assigned, so the filter received null along with an "Undefined variable $icon" warning. For a MIME type string, the value came straight from wp_cache_get(), which is untyped. Initialize `$icon` to false, and only use the cached value when it is a string, so the filter and the return value are always string|false. Also correct the hook docs: `$icon` is a URL, not a path, and `$mime` is 0 when the attachment does not exist. Add a test covering both the attachment ID and MIME type paths when no icon matches. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 13 ++++++--- tests/phpunit/tests/post/attachments.php | 36 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index fee280a11a5d5..572cb12ee3658 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7636,11 +7636,15 @@ function wp_attachment_is_image( $post = null ) { * * @param string|int $mime MIME type or attachment ID. * @param string $preferred_ext File format to prefer in return. Default '.png'. - * @return string|false Icon, false otherwise. + * @return string|false Icon URL, or false if no icon was found. */ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { + $icon = false; if ( ! is_numeric( $mime ) ) { - $icon = wp_cache_get( "mime_type_icon_$mime" ); + $cached_icon = wp_cache_get( "mime_type_icon_$mime" ); + if ( is_string( $cached_icon ) ) { + $icon = $cached_icon; + } } // Check if preferred file format variable is present and is a validly formatted file extension. @@ -7773,8 +7777,9 @@ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { * * @since 2.1.0 * - * @param string|false $icon Path to the mime type icon, or false if no icon was found. - * @param string $mime Mime type. + * @param string|false $icon URL of the mime type icon, or false if no icon was found. + * @param string|int $mime Mime type, or 0 if an attachment ID was passed for a + * nonexistent attachment. * @param int $post_id Attachment ID. Will equal 0 if the function passed * the mime type. */ diff --git a/tests/phpunit/tests/post/attachments.php b/tests/phpunit/tests/post/attachments.php index 76315dcd7c954..2aeffaf903970 100644 --- a/tests/phpunit/tests/post/attachments.php +++ b/tests/phpunit/tests/post/attachments.php @@ -552,4 +552,40 @@ public function test_wp_mime_type_icon_video_with_preferred_ext() { $this->assertStringContainsString( 'images/media/video.png', $icon1, 'Mime type icon should be correctly returned with ".png" argument.' ); $this->assertStringContainsString( 'images/media/video.png', $icon2, 'Mime type icon should be correctly returned with "png" argument.' ); } + + /** + * Tests that false is passed to the filter and returned when no icon matches. + * + * @dataProvider data_wp_mime_type_icon_without_matching_icon + * + * @param int|string $mime MIME type or attachment ID. + */ + public function test_wp_mime_type_icon_without_matching_icon( $mime ) { + // With no icon files available, not even the default icon can match. + wp_cache_set( 'icon_files', array() ); + + $filtered_icons = array(); + add_filter( + 'wp_mime_type_icon', + static function ( $icon ) use ( &$filtered_icons ) { + $filtered_icons[] = $icon; + return $icon; + } + ); + + $this->assertFalse( wp_mime_type_icon( $mime ), 'Expected false to be returned.' ); + $this->assertSame( array( false ), $filtered_icons, 'Expected false to be passed to the filter.' ); + } + + /** + * Data provider for test_wp_mime_type_icon_without_matching_icon(). + * + * @return array + */ + public static function data_wp_mime_type_icon_without_matching_icon(): array { + return array( + 'nonexistent attachment ID' => array( 0 ), + 'unknown MIME type' => array( 'foo/bar' ), + ); + } } From 9ebbee79e46e9c826e02b5143650fa117510adf1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:45:43 -0800 Subject: [PATCH 20/33] Document wp_match_mime_types() return as string-keyed lists The function returns an array keyed by wildcard MIME type, each holding the list of real MIME types it matched. Declaring it as plain `array` left callers iterating over `mixed`, so wp_mime_type_icon() and media_upload_library_form() reported foreach and array key type errors. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 572cb12ee3658..e04b8e6060390 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -3895,7 +3895,7 @@ function get_post_mime_types() { * @param string|string[] $wildcard_mime_types Mime types, e.g. `audio/mpeg`, `image` (same as `image/*`), * or `flash` (same as `*flash*`). * @param string|string[] $real_mime_types Real post mime type values. - * @return array array(wildcard=>array(real types)). + * @return array array(wildcard=>array(real types)). */ function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) { $matches = array(); From e2a0d82feb922f548032220723e5b832215745fa Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:46:06 -0800 Subject: [PATCH 21/33] Only use string icon URLs from the cached icon files list The `icon_files` list can come from wp_cache_get(), so its values are untyped, and `$icon` reached the wp_mime_type_icon filter as `mixed`. Skip any matched entry that is not a string, so the filter and the return value are string|false on every path. Read the entry once through a local variable rather than checking and then reading `$types[ $wild ]` again. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index e04b8e6060390..b704a2ecc8f79 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7759,11 +7759,12 @@ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { foreach ( $matches as $match => $wilds ) { foreach ( $wilds as $wild ) { - if ( ! isset( $types[ $wild ] ) ) { + $type_icon = $types[ $wild ] ?? null; + if ( ! is_string( $type_icon ) ) { continue; } - $icon = $types[ $wild ]; + $icon = $type_icon; if ( ! is_numeric( $mime ) ) { wp_cache_add( "mime_type_icon_$mime", $icon ); } From 676a4a12135ac3f1eb5c76a7430ce6ef5a51fcfc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:51:29 -0800 Subject: [PATCH 22/33] Remove the unreachable is_object() guard from _set_preview() _set_preview() is the core callback for the the_preview filter, which WP_Query applies to `$this->posts[0]` after the posts have already been converted with get_post(), so it always receives a WP_Post. With `@param WP_Post`, the guard's body was unreachable (PHPStan typed `$post` there as never) and contradicted the narrowed `@return WP_Post`. A survey of plugins in the WordPress.org directory found no the_preview callback that returns a non-object, and the plugins that call _set_preview() directly only pass it post objects. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/revision.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wp-includes/revision.php b/src/wp-includes/revision.php index 95746cf2fd113..03fb126029f77 100644 --- a/src/wp-includes/revision.php +++ b/src/wp-includes/revision.php @@ -873,10 +873,6 @@ function wp_revisions_to_keep( $post ) { * @return WP_Post */ function _set_preview( $post ) { - if ( ! is_object( $post ) ) { - return $post; - } - $preview = wp_get_post_autosave( $post->ID ); if ( is_object( $preview ) ) { From 0eec625c6ed56c143996f12c178a9ea41dbcecf0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:56:11 -0800 Subject: [PATCH 23/33] Use a truly nonexistent attachment ID in the mime type icon test get_post( 0 ) falls back to the global `$post`, so if an earlier test left it set, the "nonexistent attachment ID" case would resolve a real post and never reach the branch where `$mime` becomes 0. Pass PHP_INT_MAX instead, which can never match an attachment. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- tests/phpunit/tests/post/attachments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/post/attachments.php b/tests/phpunit/tests/post/attachments.php index 2aeffaf903970..cc78a2e34af55 100644 --- a/tests/phpunit/tests/post/attachments.php +++ b/tests/phpunit/tests/post/attachments.php @@ -584,7 +584,7 @@ static function ( $icon ) use ( &$filtered_icons ) { */ public static function data_wp_mime_type_icon_without_matching_icon(): array { return array( - 'nonexistent attachment ID' => array( 0 ), + 'nonexistent attachment ID' => array( PHP_INT_MAX ), 'unknown MIME type' => array( 'foo/bar' ), ); } From c78ec6736e189f251dc7a91b0f2b8380a6e31773 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 11:56:20 -0800 Subject: [PATCH 24/33] Allow integer keys in wp_match_mime_types() return type The returned array is keyed by the wildcard MIME types, and PHP casts all-digit string keys to integers, so a wildcard such as an icon named `404.png` produces an int key. Use `array-key` rather than `string` for the key type. The values remain lists of matched MIME type strings. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index b704a2ecc8f79..7785ceb4847b3 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -3895,7 +3895,7 @@ function get_post_mime_types() { * @param string|string[] $wildcard_mime_types Mime types, e.g. `audio/mpeg`, `image` (same as `image/*`), * or `flash` (same as `*flash*`). * @param string|string[] $real_mime_types Real post mime type values. - * @return array array(wildcard=>array(real types)). + * @return array array(wildcard=>array(real types)). */ function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) { $matches = array(); From 3fc39aa599e75958bd6f2b6ea33306f2c00bf0b3 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 13:02:55 -0800 Subject: [PATCH 25/33] Regenerate baselines --- tests/phpstan/baselines/property.nonObject.neon | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/phpstan/baselines/property.nonObject.neon b/tests/phpstan/baselines/property.nonObject.neon index d04aff6ba2e14..e17cdfa2c2458 100644 --- a/tests/phpstan/baselines/property.nonObject.neon +++ b/tests/phpstan/baselines/property.nonObject.neon @@ -58,16 +58,6 @@ parameters: identifier: property.nonObject count: 1 path: ../../../src/wp-admin/includes/class-wp-plugin-install-list-table.php - - - message: '#^Cannot access property \$parent on array\|object\.$#' - identifier: property.nonObject - count: 1 - path: ../../../src/wp-admin/includes/class-wp-terms-list-table.php - - - message: '#^Cannot access property \$term_id on array\|object\.$#' - identifier: property.nonObject - count: 2 - path: ../../../src/wp-admin/includes/class-wp-terms-list-table.php - message: '#^Cannot access property \$info on array\|object\.$#' identifier: property.nonObject From 5b21eadc7a284e9af7c74715a264e4482fdf28f0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 13:05:04 -0800 Subject: [PATCH 26/33] Note that the icon filter's post ID is 0 for a missing attachment The wp_mime_type_icon filter docs already say `$mime` is 0 when an attachment ID is passed for a nonexistent attachment. `$post_id` is also 0 in that case, not only when a MIME type is passed, so say so. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 7785ceb4847b3..1ad980848f4fb 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7782,7 +7782,7 @@ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { * @param string|int $mime Mime type, or 0 if an attachment ID was passed for a * nonexistent attachment. * @param int $post_id Attachment ID. Will equal 0 if the function passed - * the mime type. + * the mime type or the attachment does not exist. */ return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id ); } From 467827fc17599d975943b797c5941680c08e0a39 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 13:05:47 -0800 Subject: [PATCH 27/33] Move sanitize_term()'s term type into its regular @param `array|object` is a plain generic rather than an array shape, so it belongs in the published `@param` tag instead of a separate `@phpstan-param` that duplicated it. PHPStan results are unchanged. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/taxonomy.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 61f73d88774cb..5289aef2a6f00 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1789,14 +1789,13 @@ function term_is_ancestor_of( $term1, $term2, $taxonomy ) { * * @since 2.3.0 * - * @param array|object $term The term to check. - * @param string $taxonomy The taxonomy name to use. - * @param string $context Optional. Context in which to sanitize the term. - * Accepts 'raw', 'edit', 'db', 'display', 'rss', - * 'attribute', or 'js'. Default 'display'. + * @param array|object $term The term to check. + * @param string $taxonomy The taxonomy name to use. + * @param string $context Optional. Context in which to sanitize the term. + * Accepts 'raw', 'edit', 'db', 'display', 'rss', + * 'attribute', or 'js'. Default 'display'. * @return array|object Term with all fields sanitized (will be the same type as `$term`). * - * @phpstan-param object|array $term * @phpstan-return ( * $term is WP_Term ? WP_Term : ( * $term is array ? array : object From 9bdb8d3940d13fbc6a084a0ff12b37391f912259 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 14:05:58 -0800 Subject: [PATCH 28/33] Add a conditional return type to get_post_gallery() get_post_gallery() returns gallery HTML when `$html` is true and an array of gallery data when it is false, but its return type was the union of both. Add a conditional `@phpstan-return` so callers that pass `$html = false` (such as get_post_gallery_images()) get an array, and callers using the default get a string, which clears the echo type errors in the Twenty Thirteen and Twenty Seventeen gallery templates. Type the get_post_gallery filter's array as `array` to match. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/media.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index f12b2616d97dc..c40439fc0f81d 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5590,6 +5590,8 @@ function get_post_galleries( $post, $html = true ) { * @return string|array|false Gallery HTML or an array of gallery data and srcs parsed from * the expanded shortcode, depending on `$html`. False if the post has * no gallery. + * + * @phpstan-return ( $html is false ? array|false : string|false ) */ function get_post_gallery( $post = 0, $html = true ) { $galleries = get_post_galleries( $post, $html ); @@ -5600,10 +5602,10 @@ function get_post_gallery( $post = 0, $html = true ) { * * @since 3.6.0 * - * @param string|array|false $gallery The first-found post gallery, or false if the post has no gallery. - * An HTML string when `$html` is true, otherwise an array. - * @param int|WP_Post $post Post ID or object. - * @param array $galleries Associative array of all found post galleries. + * @param string|array|false $gallery The first-found post gallery, or false if the post has no gallery. + * An HTML string when `$html` is true, otherwise an array. + * @param int|WP_Post $post Post ID or object. + * @param array $galleries Associative array of all found post galleries. */ return apply_filters( 'get_post_gallery', $gallery, $post, $galleries ); } From ccd0bce4281ba8d653bd2e6237aef738cfb18de8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 19:03:50 -0800 Subject: [PATCH 29/33] Allow integer keys in get_post_gallery() data arrays In data mode, each gallery is built from shortcode_parse_atts(), which puts positional shortcode attributes under integer keys, so `[gallery foo ids="1,2"]` yields `0 => 'foo'` alongside the named attributes. Type the gallery array as `array` instead of `array`, in both the conditional return type and the get_post_gallery filter. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/media.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index c40439fc0f81d..3e84e97f40689 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5591,7 +5591,7 @@ function get_post_galleries( $post, $html = true ) { * the expanded shortcode, depending on `$html`. False if the post has * no gallery. * - * @phpstan-return ( $html is false ? array|false : string|false ) + * @phpstan-return ( $html is false ? array|false : string|false ) */ function get_post_gallery( $post = 0, $html = true ) { $galleries = get_post_galleries( $post, $html ); @@ -5602,10 +5602,10 @@ function get_post_gallery( $post = 0, $html = true ) { * * @since 3.6.0 * - * @param string|array|false $gallery The first-found post gallery, or false if the post has no gallery. - * An HTML string when `$html` is true, otherwise an array. - * @param int|WP_Post $post Post ID or object. - * @param array $galleries Associative array of all found post galleries. + * @param string|array|false $gallery The first-found post gallery, or false if the post has no gallery. + * An HTML string when `$html` is true, otherwise an array. + * @param int|WP_Post $post Post ID or object. + * @param array $galleries Associative array of all found post galleries. */ return apply_filters( 'get_post_gallery', $gallery, $post, $galleries ); } From 1dfd08d8e21328eaa587c65f7896d570c19891d5 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 19:04:26 -0800 Subject: [PATCH 30/33] Test that wp_mime_type_icon() ignores non-string cached icons Cover the `is_string()` check on the `mime_type_icon_{$mime}` cache read: when the cache holds true, an array, or an int, the icon must be looked up again rather than returned as-is. Reverting to the unchecked cache read makes all three cases fail. Also add the `@ticket` annotation to the existing no-matching-icon test. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- tests/phpunit/tests/post/attachments.php | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/phpunit/tests/post/attachments.php b/tests/phpunit/tests/post/attachments.php index cc78a2e34af55..979ff4025bcf4 100644 --- a/tests/phpunit/tests/post/attachments.php +++ b/tests/phpunit/tests/post/attachments.php @@ -556,6 +556,8 @@ public function test_wp_mime_type_icon_video_with_preferred_ext() { /** * Tests that false is passed to the filter and returned when no icon matches. * + * @ticket 65817 + * * @dataProvider data_wp_mime_type_icon_without_matching_icon * * @param int|string $mime MIME type or attachment ID. @@ -588,4 +590,35 @@ public static function data_wp_mime_type_icon_without_matching_icon(): array { 'unknown MIME type' => array( 'foo/bar' ), ); } + + /** + * Tests that a cached icon that is not a string is ignored and the icon is looked up again. + * + * @ticket 65817 + * + * @dataProvider data_wp_mime_type_icon_ignores_non_string_cached_icon + * + * @param mixed $cached_icon Value stored in the icon cache for the MIME type. + */ + public function test_wp_mime_type_icon_ignores_non_string_cached_icon( $cached_icon ) { + wp_cache_set( 'mime_type_icon_video/mp4', $cached_icon ); + + $icon = wp_mime_type_icon( 'video/mp4' ); + + $this->assertIsString( $icon, 'Expected the icon to be looked up again rather than taken from the cache.' ); + $this->assertStringContainsString( 'images/media/video.png', $icon ); + } + + /** + * Data provider for test_wp_mime_type_icon_ignores_non_string_cached_icon(). + * + * @return array + */ + public static function data_wp_mime_type_icon_ignores_non_string_cached_icon(): array { + return array( + 'true' => array( true ), + 'array' => array( array( 'http://example.com/icon.png' ) ), + 'int' => array( 1 ), + ); + } } From 103ad9fe60a324e00a5a66d73d26a0528679df49 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 19:12:04 -0800 Subject: [PATCH 31/33] Realign sanitize_term() parameter docs The `$taxonomy` and `$context` lines, and the continuation lines below them, were one column short of the `$term` line after its type was widened to `array|object`. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/taxonomy.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 5289aef2a6f00..a37e51a68e993 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1790,10 +1790,10 @@ function term_is_ancestor_of( $term1, $term2, $taxonomy ) { * @since 2.3.0 * * @param array|object $term The term to check. - * @param string $taxonomy The taxonomy name to use. - * @param string $context Optional. Context in which to sanitize the term. - * Accepts 'raw', 'edit', 'db', 'display', 'rss', - * 'attribute', or 'js'. Default 'display'. + * @param string $taxonomy The taxonomy name to use. + * @param string $context Optional. Context in which to sanitize the term. + * Accepts 'raw', 'edit', 'db', 'display', 'rss', + * 'attribute', or 'js'. Default 'display'. * @return array|object Term with all fields sanitized (will be the same type as `$term`). * * @phpstan-return ( From 7aa5ea92ee8b8ab07487fdf3c6768fc82699d9e6 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 19:12:31 -0800 Subject: [PATCH 32/33] Clarify when the icon filter's MIME type and post ID are 0 wp_mime_type_icon() resolves a numeric `$mime` with get_post(), so 0 (the default) falls back to the global post. `$mime` and `$post_id` are therefore 0 when no post is found for the passed ID, not specifically when an attachment ID is nonexistent. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 1ad980848f4fb..f48ef9379de04 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7779,10 +7779,9 @@ function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) { * @since 2.1.0 * * @param string|false $icon URL of the mime type icon, or false if no icon was found. - * @param string|int $mime Mime type, or 0 if an attachment ID was passed for a - * nonexistent attachment. + * @param string|int $mime Mime type, or 0 if no post was found for the passed ID. * @param int $post_id Attachment ID. Will equal 0 if the function passed - * the mime type or the attachment does not exist. + * the mime type or no post was found for the passed ID. */ return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id ); } From e91c525a69a4dafa304b2bbf75132347f3ce33fd Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Nov 2026 19:49:38 -0800 Subject: [PATCH 33/33] Simplify wp_match_mime_types() return type to array A single-parameter array generic already has `array-key` keys, so `array` is the same type written longer. This matches `array` used for get_post_gallery(). Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Code-Session-Id: 9a95e289-8165-4c03-a202-98d54206ca8c --- src/wp-includes/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 507030cd21819..e0afc05cd1b54 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -3895,7 +3895,7 @@ function get_post_mime_types() { * @param string|string[] $wildcard_mime_types Mime types, e.g. `audio/mpeg`, `image` (same as `image/*`), * or `flash` (same as `*flash*`). * @param string|string[] $real_mime_types Real post mime type values. - * @return array array(wildcard=>array(real types)). + * @return array array(wildcard=>array(real types)). */ function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) { $matches = array();