[wp-trac] [WordPress Trac] #64704: Code Modernization: Replace void in PHPDoc union return types with null in various core files
WordPress Trac
noreply at wordpress.org
Mon Mar 30 03:43:56 UTC 2026
#64704: Code Modernization: Replace void in PHPDoc union return types with null in
various core files
--------------------------+---------------------
Reporter: apermo | Owner: (none)
Type: defect (bug) | Status: new
Priority: normal | Milestone: 7.1
Component: General | Version: 3.9
Severity: normal | Resolution:
Keywords: has-patch | Focuses:
--------------------------+---------------------
Description changed by westonruter:
Old description:
> Following up on [#64694] which fixed `paginate_links()`, this ticket
> addresses 34 instances across various `wp-includes` files where `@return`
> annotations incorrectly use `void` as part of a union type.
>
> The `|void` pattern in these files spans from WordPress 3.9
> (`unregister_default_headers()`) through WordPress 6.8 (`handle_error()`,
> `WP_Widget::form()`), with two instances on unreleased trunk (`wp_die()`,
> `print_late_styles()`). The bulk originated in WordPress 4.3 via [32568]
> — "Use `void` instead of `null` where appropriate when pipe-delimiting
> `@return` types".
>
> === Why this matters ===
>
> In PHP's type system, `void` means "the function does not return a value"
> and '''cannot''' be part of a union type (this is a compile error in PHP
> 8.0+). When a function uses bare `return;` or falls off the end without
> returning, the actual runtime value is `null`. Therefore `@return
> Type|void` should be `@return Type|null`.
>
> This affects:
> * Static analysis tools (PHPStan, Psalm)
> * IDE autocompletion and type inference
> * Developer expectations about return values
>
> === Backward Compatibility ===
>
> This is a safe, non-breaking change. As demonstrated by @westonruter in
> the [https://github.com/WordPress/wordpress-
> develop/pull/10999#pullrequestreview-3839142928 #64694 PR review],
> `return;` and `return null;` are identical at runtime across every PHP
> version from 4.3.0 through 8.5.3: https://3v4l.org/3KQC8
>
> === Proposed Changes ===
>
> For each instance:
> 1. Update the `@return` annotation to replace `void` with `null` (or
> remove `|void` where the function always returns a typed value)
> 2. Change bare `return;` statements to `return null;`
> 3. Update description text to say "null" instead of "void" / "nothing"
>
> === Affected Functions ===
>
> ||= File =||= Function =||= Line =||= Current =||= Recommendation =||=
> Since =||
> || `functions.php` || `wp_ext2type()` || 2997 || `string|void` ||
> `string|null` || 4.3 ||
> || `functions.php` || `wp_die()` || 3767 || `never|void` || No change
> needed || trunk ||
> || `functions.php` || `wp_update_php_annotation()` || 8556 ||
> `string|void` || `string|null` || 6.4 ||
> || `capabilities.php` || `add_role()` || 1133 || `WP_Role|void` ||
> `WP_Role|null` || 6.1 ||
> || `class-wp-roles.php` || `add_role()` || 174 || `WP_Role|void` ||
> `WP_Role|null` || 6.1 ||
> || `formatting.php` || `sanitize_hex_color()` || 6235 || `string|void` ||
> `string|null` || 4.6 ||
> || `canonical.php` || `redirect_canonical()` || 40 || `string|void` ||
> `string|null` || 4.3 ||
> || `revision.php` || `wp_save_post_revision()` || 128 ||
> `int|WP_Error|void` || `int|WP_Error|null` || 4.3 ||
> || `class-wp-scripts.php` || `print_scripts_l10n()` || 204 ||
> `bool|string|void` || `bool|string|null` || 6.1 ||
> || `class-wp-scripts.php` || `print_extra_script()` || 220 ||
> `bool|string|void` || `bool|string|null` || 6.1 ||
> || `script-loader.php` || `print_late_styles()` || 2374 ||
> `string[]|void` || `string[]|null` || trunk ||
> || `ms-functions.php` || `get_active_blog_for_user()` || 43 ||
> `WP_Site|void` || `WP_Site|null` || 4.5 ||
> || `ms-functions.php` || `add_existing_user_to_blog()` || 2272 ||
> `true|WP_Error|void` || `true|WP_Error|null` || 5.4 ||
> || `class-wp-block-type.php` || `__get()` || 361 ||
> `string|string[]|null|void` || `string|string[]|null` || 6.2 ||
> || `class-wp-plugin-dependencies.php` || `get_dependency_api_data()` ||
> 646 || `array|void` || `array|null` || 6.5 ||
> || `class-wp-recovery-mode.php` || `handle_error()` || 164 ||
> `true|WP_Error|void` || `true|WP_Error|null` || 6.8 ||
> || `class-wp-xmlrpc-server.php` || `escape()` || 356 || `string|void` ||
> `string|null` || 4.3 ||
> || `class-wp-theme-json.php` || `set_spacing_sizes()` || 4270 ||
> `null|void` || `null` (redundant) || 6.1 ||
> || `class-wp-style-engine-css-rules-store.php` || `get_store()` || 54 ||
> `WP_Style_Engine_CSS_Rules_Store|void` || `...|null` || 6.1 ||
> || `class-wp-style-engine-css-rules-store.php` || `add_rule()` || 131 ||
> `WP_Style_Engine_CSS_Rule|void` || `...|null` || 6.3 ||
> || `widgets.php` || `wp_widget_description()` || 449 || `string|void` ||
> `string|null` || 4.4 ||
> || `widgets.php` || `wp_sidebar_description()` || 474 || `string|void` ||
> `string|null` || 4.4 ||
> || `class-wp-widget.php` || `WP_Widget::form()` || 141 || `string|void`
> || `string` (void incorrect) || 6.8 ||
> || `theme.php` || `remove_theme_support()` || 3064 || `bool|void` ||
> `bool` (void incorrect) || 4.3 ||
> || `theme.php` || `unregister_default_headers()` || 1623 || `bool|void`
> || `bool|null` || 3.9 ||
> || `media.php` || `wp_audio_shortcode()` || 3392 || `string|void` ||
> `string|null` || 4.3 ||
> || `media.php` || `wp_video_shortcode()` || 3617 || `string|void` ||
> `string|null` || 4.3 ||
> || `media.php` || `wp_prepare_attachment_for_js()` || 4467 ||
> `array|void` || `array|null` || 5.6 ||
> || `post.php` || `get_post_custom_keys()` || 2853 || `array|void` ||
> `array|null` || 4.4 ||
> || `post.php` || `wp_trash_post_comments()` || 4218 || `mixed|void` ||
> `int|false|null` || 4.4 ||
> || `post.php` || `wp_untrash_post_comments()` || 4279 || `true|void` ||
> `true|null` || 4.4 ||
> || `comment.php` || `wp_update_comment_count()` || 2809 || `bool|void` ||
> `bool|null` || 4.5 ||
> || `comment.php` || `trackback()` || 3316 || `int|false|void` ||
> `int|false|null` || 4.4 ||
>
> === Notes on special cases ===
>
> * **`wp_die()`**: `never|void` is arguably correct — terminates when
> `exit=true`, returns when `exit=false`. No change needed.
> * **`escape()`**: Returns string for scalar, modifies by-reference for
> array. Unusual pattern.
> * **`set_spacing_sizes()`**: `null|void` is redundant — both mean "no
> value". Should be just `null`.
> * **`wp_trash_post_comments()`**: Returns `int|false` on success, bare
> return on failure. Should be `int|false|null`.
> * **`wp_untrash_post_comments()`**: Returns true, bare return, or falls
> off end. Possible bug at end of function.
> * **`unregister_default_headers()`**: Returns bool for single header,
> void for array input (recursive). Oldest instance (WP 3.9).
>
> See [#64694] for prior art.
New description:
Following up on #64694 which fixed `paginate_links()`, this ticket
addresses 34 instances across various `wp-includes` files where `@return`
annotations incorrectly use `void` as part of a union type.
The `|void` pattern in these files spans from WordPress 3.9
(`unregister_default_headers()`) through WordPress 6.8 (`handle_error()`,
`WP_Widget::form()`), with two instances on unreleased trunk (`wp_die()`,
`print_late_styles()`). The bulk originated in WordPress 4.3 via [32568] —
"Use `void` instead of `null` where appropriate when pipe-delimiting
`@return` types".
=== Why this matters ===
In PHP's type system, `void` means "the function does not return a value"
and '''cannot''' be part of a union type (this is a compile error in PHP
8.0+). When a function uses bare `return;` or falls off the end without
returning, the actual runtime value is `null`. Therefore `@return
Type|void` should be `@return Type|null`.
This affects:
* Static analysis tools (PHPStan, Psalm)
* IDE autocompletion and type inference
* Developer expectations about return values
=== Backward Compatibility ===
This is a safe, non-breaking change. As demonstrated by @westonruter in
the [https://github.com/WordPress/wordpress-
develop/pull/10999#pullrequestreview-3839142928 #64694 PR review],
`return;` and `return null;` are identical at runtime across every PHP
version from 4.3.0 through 8.5.3: https://3v4l.org/3KQC8
=== Proposed Changes ===
For each instance:
1. Update the `@return` annotation to replace `void` with `null` (or
remove `|void` where the function always returns a typed value)
2. Change bare `return;` statements to `return null;`
3. Update description text to say "null" instead of "void" / "nothing"
=== Affected Functions ===
||= File =||= Function =||= Line =||= Current =||= Recommendation =||=
Since =||
|| `functions.php` || `wp_ext2type()` || 2997 || `string|void` ||
`string|null` || 4.3 ||
|| `functions.php` || `wp_die()` || 3767 || `never|void` || No change
needed || trunk ||
|| `functions.php` || `wp_update_php_annotation()` || 8556 ||
`string|void` || `string|null` || 6.4 ||
|| `capabilities.php` || `add_role()` || 1133 || `WP_Role|void` ||
`WP_Role|null` || 6.1 ||
|| `class-wp-roles.php` || `add_role()` || 174 || `WP_Role|void` ||
`WP_Role|null` || 6.1 ||
|| `formatting.php` || `sanitize_hex_color()` || 6235 || `string|void` ||
`string|null` || 4.6 ||
|| `canonical.php` || `redirect_canonical()` || 40 || `string|void` ||
`string|null` || 4.3 ||
|| `revision.php` || `wp_save_post_revision()` || 128 ||
`int|WP_Error|void` || `int|WP_Error|null` || 4.3 ||
|| `class-wp-scripts.php` || `print_scripts_l10n()` || 204 ||
`bool|string|void` || `bool|string|null` || 6.1 ||
|| `class-wp-scripts.php` || `print_extra_script()` || 220 ||
`bool|string|void` || `bool|string|null` || 6.1 ||
|| `script-loader.php` || `print_late_styles()` || 2374 || `string[]|void`
|| `string[]|null` || trunk ||
|| `ms-functions.php` || `get_active_blog_for_user()` || 43 ||
`WP_Site|void` || `WP_Site|null` || 4.5 ||
|| `ms-functions.php` || `add_existing_user_to_blog()` || 2272 ||
`true|WP_Error|void` || `true|WP_Error|null` || 5.4 ||
|| `class-wp-block-type.php` || `__get()` || 361 ||
`string|string[]|null|void` || `string|string[]|null` || 6.2 ||
|| `class-wp-plugin-dependencies.php` || `get_dependency_api_data()` ||
646 || `array|void` || `array|null` || 6.5 ||
|| `class-wp-recovery-mode.php` || `handle_error()` || 164 ||
`true|WP_Error|void` || `true|WP_Error|null` || 6.8 ||
|| `class-wp-xmlrpc-server.php` || `escape()` || 356 || `string|void` ||
`string|null` || 4.3 ||
|| `class-wp-theme-json.php` || `set_spacing_sizes()` || 4270 ||
`null|void` || `null` (redundant) || 6.1 ||
|| `class-wp-style-engine-css-rules-store.php` || `get_store()` || 54 ||
`WP_Style_Engine_CSS_Rules_Store|void` || `...|null` || 6.1 ||
|| `class-wp-style-engine-css-rules-store.php` || `add_rule()` || 131 ||
`WP_Style_Engine_CSS_Rule|void` || `...|null` || 6.3 ||
|| `widgets.php` || `wp_widget_description()` || 449 || `string|void` ||
`string|null` || 4.4 ||
|| `widgets.php` || `wp_sidebar_description()` || 474 || `string|void` ||
`string|null` || 4.4 ||
|| `class-wp-widget.php` || `WP_Widget::form()` || 141 || `string|void` ||
`string` (void incorrect) || 6.8 ||
|| `theme.php` || `remove_theme_support()` || 3064 || `bool|void` ||
`bool` (void incorrect) || 4.3 ||
|| `theme.php` || `unregister_default_headers()` || 1623 || `bool|void` ||
`bool|null` || 3.9 ||
|| `media.php` || `wp_audio_shortcode()` || 3392 || `string|void` ||
`string|null` || 4.3 ||
|| `media.php` || `wp_video_shortcode()` || 3617 || `string|void` ||
`string|null` || 4.3 ||
|| `media.php` || `wp_prepare_attachment_for_js()` || 4467 || `array|void`
|| `array|null` || 5.6 ||
|| `post.php` || `get_post_custom_keys()` || 2853 || `array|void` ||
`array|null` || 4.4 ||
|| `post.php` || `wp_trash_post_comments()` || 4218 || `mixed|void` ||
`int|false|null` || 4.4 ||
|| `post.php` || `wp_untrash_post_comments()` || 4279 || `true|void` ||
`true|null` || 4.4 ||
|| `comment.php` || `wp_update_comment_count()` || 2809 || `bool|void` ||
`bool|null` || 4.5 ||
|| `comment.php` || `trackback()` || 3316 || `int|false|void` ||
`int|false|null` || 4.4 ||
=== Notes on special cases ===
* **`wp_die()`**: `never|void` is arguably correct — terminates when
`exit=true`, returns when `exit=false`. No change needed.
* **`escape()`**: Returns string for scalar, modifies by-reference for
array. Unusual pattern.
* **`set_spacing_sizes()`**: `null|void` is redundant — both mean "no
value". Should be just `null`.
* **`wp_trash_post_comments()`**: Returns `int|false` on success, bare
return on failure. Should be `int|false|null`.
* **`wp_untrash_post_comments()`**: Returns true, bare return, or falls
off end. Possible bug at end of function.
* **`unregister_default_headers()`**: Returns bool for single header, void
for array input (recursive). Oldest instance (WP 3.9).
See [#64694] for prior art.
--
--
Ticket URL: <https://core.trac.wordpress.org/ticket/64704#comment:10>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list