From 9adf7351bbddf63dfe899dc4092548e5976f1d90 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 1 Oct 2026 08:53:28 +0000 Subject: [PATCH 1/2] Docs: Add PHPStan type assertions and purity annotations. Adds `@phpstan-assert-if-true` to functions that check their argument, such as `taxonomy_exists()`, `shortcode_exists()` and `wp_is_uuid()`, and `@phpstan-pure` and `@phpstan-impure` to functions whose result depends only on their arguments, or does not. `addslashes_gpc()` is not marked as pure, since it calls `_deprecated_function()`. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc --- src/wp-admin/includes/bookmark.php | 2 ++ src/wp-admin/includes/menu.php | 1 + src/wp-includes/formatting.php | 8 ++++++++ src/wp-includes/functions.php | 10 ++++++++++ src/wp-includes/option.php | 2 ++ src/wp-includes/pluggable.php | 8 ++++++++ src/wp-includes/rest-api.php | 1 + src/wp-includes/shortcodes.php | 6 ++++++ src/wp-includes/taxonomy.php | 1 + src/wp-includes/user.php | 2 ++ 10 files changed, 41 insertions(+) diff --git a/src/wp-admin/includes/bookmark.php b/src/wp-admin/includes/bookmark.php index aae87b9abc60f..89929fffea67b 100644 --- a/src/wp-admin/includes/bookmark.php +++ b/src/wp-admin/includes/bookmark.php @@ -56,6 +56,8 @@ function edit_link( $link_id = 0 ) { * @since 2.0.0 * * @return stdClass Default link object. + * + * @phpstan-impure */ function get_default_link_to_edit() { $link = new stdClass(); diff --git a/src/wp-admin/includes/menu.php b/src/wp-admin/includes/menu.php index f1c2985e93e8e..1e71c782ec8ea 100644 --- a/src/wp-admin/includes/menu.php +++ b/src/wp-admin/includes/menu.php @@ -211,6 +211,7 @@ * * @phpstan-template T of string * @phpstan-param T $class_to_add + * @phpstan-pure * @phpstan-return ( $classes is empty ? T : non-empty-string ) */ function add_cssclass( $class_to_add, $classes ) { diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index c39beaf7b1d68..5a59ae5de3332 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -2842,6 +2842,8 @@ function zeroise( $number, $threshold ) { * * @param string $value Value to which backslashes will be added. * @return string String with backslashes inserted. + * + * @phpstan-pure */ function backslashit( $value ) { if ( isset( $value[0] ) && $value[0] >= '0' && $value[0] <= '9' ) { @@ -2863,6 +2865,8 @@ function backslashit( $value ) { * * @param string $value Value to which trailing slash will be added. * @return string String with trailing slash added. + * + * @phpstan-pure */ function trailingslashit( $value ) { return untrailingslashit( $value ) . '/'; @@ -2878,6 +2882,8 @@ function trailingslashit( $value ) { * * @param string $value Value from which trailing slashes will be removed. * @return string String without the trailing slashes. + * + * @phpstan-pure */ function untrailingslashit( $value ) { return rtrim( $value, '/\\' ); @@ -5912,6 +5918,8 @@ function sanitize_trackback_urls( $to_ping ) { * T is array ? array, ( value-of is string ? string : value-of )> : T * ) * ) + * + * @phpstan-pure */ function wp_slash( $value ) { if ( is_array( $value ) ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 393f50a3aad58..dd3804059446f 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1613,6 +1613,7 @@ function get_num_queries() { * @param string $yn Character string containing either 'y' (yes) or 'n' (no). * @return bool True if 'y', false on anything else. * + * @phpstan-pure * @phpstan-return ( $yn is 'y'|'Y' ? true : false ) */ function bool_from_yn( $yn ) { @@ -2146,6 +2147,7 @@ function wp_mkdir_p( $target ) { * @param string $path File path. * @return bool True if path is absolute, false is not absolute. * + * @phpstan-assert-if-true =non-falsy-string $path * @phpstan-return ( $path is non-falsy-string ? bool : false ) */ function path_is_absolute( $path ) { @@ -5433,6 +5435,7 @@ function _wp_to_kebab_case( $input_string ) { * * @phpstan-assert-if-true array $data * + * @phpstan-pure * @phpstan-return ( $data is array ? true : false ) */ function wp_is_numeric_array( $data ): bool { @@ -7584,6 +7587,8 @@ function _device_can_upload() { * * @param string $path The resource path or URL. * @return bool True if the path is a stream URL. + * + * @phpstan-assert-if-true =non-falsy-string $path */ function wp_is_stream( $path ) { $scheme_separator = strpos( $path, '://' ); @@ -8172,6 +8177,8 @@ function wp_raise_memory_limit( $context = 'admin' ) { * @since 7.0.0 Uses wp_rand if available. * * @return string UUID. + * + * @phpstan-impure */ function wp_generate_uuid4() { static $backup_randomizer = false; @@ -8210,6 +8217,7 @@ function wp_generate_uuid4() { * to accept any UUID version. Otherwise, only version allowed is `4`. * @return bool The string is a valid UUID or false on failure. * + * @phpstan-assert-if-true =lowercase-string&non-falsy-string $uuid * @phpstan-return ( $version is 4|null ? bool : false ) */ function wp_is_uuid( $uuid, $version = null ) { @@ -8244,6 +8252,7 @@ function wp_is_uuid( $uuid, $version = null ) { * @param string $prefix Prefix for the returned ID. * @return string Unique ID. * + * @phpstan-impure * @phpstan-return ( * ( $prefix is ''|numeric-string ? numeric-string : string ) * & non-falsy-string @@ -8269,6 +8278,7 @@ function wp_unique_id( $prefix = '' ) { * @param string $prefix Optional. Prefix for the returned ID. Default empty string. * @return string Incremental ID per prefix. * + * @phpstan-impure * @phpstan-return ( * ( $prefix is ''|numeric-string ? numeric-string : string ) * & non-falsy-string diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index d5c179c645af3..36143dfe87cd3 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -1427,6 +1427,8 @@ function delete_transient( $transient ) { * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. + * + * @phpstan-impure */ function get_transient( $transient ) { diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 4126f25d7c0fc..8089b110c21e7 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -191,6 +191,8 @@ function cache_users( $user_ids ) { * @param string|string[] $attachments Optional. Paths to files to attach. * @param string|string[] $embeds Optional. Paths to files to embed. * @return bool Whether the email was sent successfully. + * + * @phpstan-impure */ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) { // Compact the input, apply the filters, and extract them back out. @@ -2447,6 +2449,8 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) * * @param string|int $action Optional. The nonce action. Default -1. * @return float Float value rounded up to the next highest integer. + * + * @phpstan-impure */ function wp_nonce_tick( $action = -1 ) { /** @@ -2967,6 +2971,8 @@ function wp_password_needs_rehash( $hash, $user_id = '' ) { * @param bool $extra_special_chars Optional. Whether to include other special characters. * Used when generating secret keys and salts. Default false. * @return string The random password. + * + * @phpstan-impure */ function wp_generate_password( $length = 12, $special_chars = true, $extra_special_chars = false ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -3012,6 +3018,8 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci * @param int $max Optional. Upper limit for the generated number. * Accepts positive integers. Defaults to 4294967295. * @return int A random non-negative number between min and max. + * + * @phpstan-impure */ function wp_rand( $min = null, $max = null ) { global $rnd_value; diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 36b5b23de9f00..dbfbbeaeb576d 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -1534,6 +1534,7 @@ function rest_is_ip_address( $ip ) { * @param bool|string|int $value The value being evaluated. * @return bool Returns the proper associated boolean value. * + * @phpstan-pure * @phpstan-return ( * $value is false|''|'0'|0|'false'|'False'|'FALSE' * ? false diff --git a/src/wp-includes/shortcodes.php b/src/wp-includes/shortcodes.php index 2727a992fa99d..06ca6657c9d36 100644 --- a/src/wp-includes/shortcodes.php +++ b/src/wp-includes/shortcodes.php @@ -129,6 +129,8 @@ function remove_all_shortcodes() { * * @param string $tag Shortcode tag to check. * @return bool Whether the given shortcode exists. + * + * @phpstan-assert-if-true =non-empty-string $tag */ function shortcode_exists( $tag ) { global $shortcode_tags; @@ -146,6 +148,8 @@ function shortcode_exists( $tag ) { * @param string $tag Shortcode tag to check. * @return bool Whether the passed content contains the given shortcode. * + * @phpstan-assert-if-true =non-falsy-string $content + * @phpstan-assert-if-true =non-empty-string $tag * @phpstan-return ( $tag is '' ? false : ( $content is empty ? false : bool ) ) */ function has_shortcode( $content, $tag ) { @@ -591,6 +595,8 @@ function unescape_invalid_shortcodes( $content ) { * @since 4.4.0 * * @return string The shortcode attribute regular expression. + * + * @phpstan-pure */ function get_shortcode_atts_regex() { return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/'; diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 1159ca64e6d2d..7fbd50133fab6 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -375,6 +375,7 @@ function get_taxonomy( $taxonomy ) { * @param string $taxonomy Name of taxonomy object. * @return bool Whether the taxonomy exists. * + * @phpstan-assert-if-true =non-falsy-string $taxonomy * @phpstan-return ( $taxonomy is non-falsy-string ? bool : false ) */ function taxonomy_exists( $taxonomy ) { diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 92d091165525b..acfb3a02e6f07 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -3125,6 +3125,8 @@ function wp_get_password_hint() { * * @param WP_User $user User to retrieve password reset key for. * @return string|WP_Error Password reset key on success. WP_Error on error. + * + * @phpstan-impure */ function get_password_reset_key( $user ) { if ( ! ( $user instanceof WP_User ) ) { From f3baa2b497c836e4757860692e1597b5708c35a9 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 8 Oct 2026 07:43:07 +0000 Subject: [PATCH 2/2] Docs: Remove PHPStan purity annotations. Drop `@phpstan-impure` until there is agreement on which functions core should mark as impure, given how much of WordPress depends on globals and the database. Drop `@phpstan-pure` too, since with the default `rememberPossiblyImpureFunctionValues: true` it does not change whether PHPStan remembers a function's return value. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH --- src/wp-admin/includes/bookmark.php | 2 -- src/wp-admin/includes/menu.php | 1 - src/wp-includes/formatting.php | 7 ------- src/wp-includes/functions.php | 5 ----- src/wp-includes/option.php | 2 -- src/wp-includes/pluggable.php | 8 -------- src/wp-includes/rest-api.php | 1 - src/wp-includes/shortcodes.php | 1 - src/wp-includes/user.php | 2 -- 9 files changed, 29 deletions(-) diff --git a/src/wp-admin/includes/bookmark.php b/src/wp-admin/includes/bookmark.php index 89929fffea67b..aae87b9abc60f 100644 --- a/src/wp-admin/includes/bookmark.php +++ b/src/wp-admin/includes/bookmark.php @@ -56,8 +56,6 @@ function edit_link( $link_id = 0 ) { * @since 2.0.0 * * @return stdClass Default link object. - * - * @phpstan-impure */ function get_default_link_to_edit() { $link = new stdClass(); diff --git a/src/wp-admin/includes/menu.php b/src/wp-admin/includes/menu.php index 1e71c782ec8ea..f1c2985e93e8e 100644 --- a/src/wp-admin/includes/menu.php +++ b/src/wp-admin/includes/menu.php @@ -211,7 +211,6 @@ * * @phpstan-template T of string * @phpstan-param T $class_to_add - * @phpstan-pure * @phpstan-return ( $classes is empty ? T : non-empty-string ) */ function add_cssclass( $class_to_add, $classes ) { diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 3e01bfc4de679..552c5f17c21a3 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -2855,8 +2855,6 @@ function zeroise( $number, $threshold ) { * * @param string $value Value to which backslashes will be added. * @return string String with backslashes inserted. - * - * @phpstan-pure */ function backslashit( $value ) { if ( isset( $value[0] ) && $value[0] >= '0' && $value[0] <= '9' ) { @@ -2880,7 +2878,6 @@ function backslashit( $value ) { * @return string String with trailing slash added. * * @phpstan-return non-falsy-string - * @phpstan-pure */ function trailingslashit( $value ) { return untrailingslashit( $value ) . '/'; @@ -2896,8 +2893,6 @@ function trailingslashit( $value ) { * * @param string $value Value from which trailing slashes will be removed. * @return string String without the trailing slashes. - * - * @phpstan-pure */ function untrailingslashit( $value ) { return rtrim( $value, '/\\' ); @@ -5950,8 +5945,6 @@ function sanitize_trackback_urls( $to_ping ) { * T is array ? array, ( value-of is string ? string : value-of )> : T * ) * ) - * - * @phpstan-pure */ function wp_slash( $value ) { if ( is_array( $value ) ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 54bac67cebacc..b9f9aad6bac74 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1616,7 +1616,6 @@ function get_num_queries() { * @param string $yn Character string containing either 'y' (yes) or 'n' (no). * @return bool True if 'y', false on anything else. * - * @phpstan-pure * @phpstan-return ( $yn is 'y'|'Y' ? true : false ) */ function bool_from_yn( $yn ) { @@ -5445,7 +5444,6 @@ function _wp_to_kebab_case( $input_string ) { * * @phpstan-assert-if-true array $data * - * @phpstan-pure * @phpstan-return ( $data is array ? true : false ) */ function wp_is_numeric_array( $data ): bool { @@ -8190,7 +8188,6 @@ function wp_raise_memory_limit( $context = 'admin' ) { * @return string UUID. * * @phpstan-return lowercase-string&non-falsy-string - * @phpstan-impure */ function wp_generate_uuid4() { static $backup_randomizer = false; @@ -8267,7 +8264,6 @@ function wp_is_uuid( $uuid, $version = null ) { * @param string $prefix Prefix for the returned ID. * @return string Unique ID. * - * @phpstan-impure * @phpstan-return ( * ( $prefix is ''|numeric-string ? numeric-string : string ) * & non-falsy-string @@ -8293,7 +8289,6 @@ function wp_unique_id( $prefix = '' ) { * @param string $prefix Optional. Prefix for the returned ID. Default empty string. * @return string Incremental ID per prefix. * - * @phpstan-impure * @phpstan-return ( * ( $prefix is ''|numeric-string ? numeric-string : string ) * & non-falsy-string diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 998765870f788..49ecfb65977f4 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -1429,8 +1429,6 @@ function delete_transient( $transient ) { * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. - * - * @phpstan-impure */ function get_transient( $transient ) { diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index f01a02dcbdf4f..d13769cf84afe 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -191,8 +191,6 @@ function cache_users( $user_ids ) { * @param string|string[] $attachments Optional. Paths to files to attach. * @param string|string[] $embeds Optional. Paths to files to embed. * @return bool Whether the email was sent successfully. - * - * @phpstan-impure */ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) { // Compact the input, apply the filters, and extract them back out. @@ -2449,8 +2447,6 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) * * @param string|int $action Optional. The nonce action. Default -1. * @return float Float value rounded up to the next highest integer. - * - * @phpstan-impure */ function wp_nonce_tick( $action = -1 ) { /** @@ -2975,8 +2971,6 @@ function wp_password_needs_rehash( $hash, $user_id = '' ) { * @param bool $extra_special_chars Optional. Whether to include other special characters. * Used when generating secret keys and salts. Default false. * @return string The random password. - * - * @phpstan-impure */ function wp_generate_password( $length = 12, $special_chars = true, $extra_special_chars = false ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -3022,8 +3016,6 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci * @param int $max Optional. Upper limit for the generated number. * Accepts positive integers. Defaults to 4294967295. * @return int A random non-negative number between min and max. - * - * @phpstan-impure */ function wp_rand( $min = null, $max = null ) { global $rnd_value; diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index dbfbbeaeb576d..36b5b23de9f00 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -1534,7 +1534,6 @@ function rest_is_ip_address( $ip ) { * @param bool|string|int $value The value being evaluated. * @return bool Returns the proper associated boolean value. * - * @phpstan-pure * @phpstan-return ( * $value is false|''|'0'|0|'false'|'False'|'FALSE' * ? false diff --git a/src/wp-includes/shortcodes.php b/src/wp-includes/shortcodes.php index 93658980f6888..d6bb8db1e6b91 100644 --- a/src/wp-includes/shortcodes.php +++ b/src/wp-includes/shortcodes.php @@ -601,7 +601,6 @@ function unescape_invalid_shortcodes( $content ) { * @return string The shortcode attribute regular expression. * * @phpstan-return non-falsy-string - * @phpstan-pure */ function get_shortcode_atts_regex() { return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/'; diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index b2a816c281562..1d0523cfc9a9a 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -3127,8 +3127,6 @@ function wp_get_password_hint() { * * @param WP_User $user User to retrieve password reset key for. * @return string|WP_Error Password reset key on success. WP_Error on error. - * - * @phpstan-impure */ function get_password_reset_key( $user ) { if ( ! ( $user instanceof WP_User ) ) {