Opened 6 weeks ago
Last modified 5 days ago
#64704 reviewing defect (bug)
Code Modernization: Replace void in PHPDoc union return types with null in various core files
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.1 | Priority: | normal |
| Severity: | normal | Version: | 3.9 |
| Component: | General | Keywords: | has-patch |
| Focuses: | Cc: |
Description (last modified by )
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 #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:
- Update the
@returnannotation to replacevoidwithnull(or remove|voidwhere the function always returns a typed value) - Change bare
return;statements toreturn null; - 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|voidis arguably correct — terminates whenexit=true, returns whenexit=false. No change needed.escape(): Returns string for scalar, modifies by-reference for array. Unusual pattern.set_spacing_sizes():null|voidis redundant — both mean "no value". Should be justnull.wp_trash_post_comments(): Returnsint|falseon success, bare return on failure. Should beint|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.
Change History (17)
This ticket was mentioned in PR #11012 on WordPress/wordpress-develop by @apermo.
6 weeks ago
#1
- Keywords has-patch added
This ticket was mentioned in PR #11013 on WordPress/wordpress-develop by @nimeshatxecurify.
6 weeks ago
#2
Updates various functions to explicitly return null instead of implicitly returning void when no specific value is meant to be returned.
This change improves type consistency and clarity, better reflecting the function's actual behavior and aligning with modern PHP type-hinting practices.
@nimeshatxecurify commented on PR #11013:
6 weeks ago
#3
Incomplete PR, closing in favor of #11012
@westonruter commented on PR #11012:
5 weeks ago
#6
@apermo Please double-check my additions.
@apermo commented on PR #11012:
5 weeks ago
#7
@westonruter all checked, ran your stricter phpstan.neon on the changes. And resolved all covered issues to remove clutter from the UI.
#9
@
2 weeks ago
- Milestone changed from 7.0 to 7.1
As per today's 7.0 pre-RC1 bug scrub:
As we're a few hours from 7.0 RC1, I'm moving this ticket to milestone 7.1 to give it more time for review.
@westonruter commented on PR #11012:
7 days ago
#13
Committed in r62178 (317adff)
#14
@
7 days ago
- Keywords needs-patch added; has-patch removed
I didn't have r62178 close this ticket, because I found (via Claude) additional instances that still need to be fixed in wp-includes:
void|string (echo/display pattern):
author-template.php:452—wp_list_authors()general-template.php:239,382,522,702,2000,2270—get_search_form(),wp_loginout(),wp_login_form(),wp_register(),wp_get_archives(),get_calendar()comment-template.php:491,1244,2230—comment_class(),trackback_url(),wp_list_comments()post-template.php:40,79,1301,1424—the_title(),the_title_attribute(),wp_list_pages(),wp_page_menu()bookmark-template.php:209—wp_list_bookmarks()link-template.php:3257—wp_get_shortlink()category-template.php:535,713—wp_list_categories(),wp_tag_cloud()nav-menu-template.php:59—wp_nav_menu()
void|false:
functions.php:886—wp_set_post_tags()(alias)general-template.php:25,69,113,165—get_header(),get_footer(),get_sidebar(),get_template_part()theme.php:2692—set_theme_mod()taxonomy.php:3819—clean_object_term_cache()category-template.php:1455—wp_generate_tag_cloud()(false path)comment.php:3093—pingback()user.php:1990—wp_set_password()
void|WP_Error:
class-wp-metadata-lazyloader.php:78,118—queue_objects(),reset_queue()class-wp-image-editor-imagick.php:401—set_quality()class-wp-xmlrpc-server.php:1391rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:73
void|false in wpdb:
class-wpdb.php:1797,3940—show_errors(),print_error()
Other:
class-wp-customize-setting.php:522—save()(void|false)
That's roughly 40+ instances still remaining in wp-includes/.
This ticket was mentioned in PR #11394 on WordPress/wordpress-develop by @sanket.parmar.
6 days ago
#15
- Keywords has-patch added; needs-patch removed
## Summary
Follows up on #64704 and r62178.
In PHP's type system, void means a function does not return a value and cannot be part of a union type. Many functions in wp-includes were documented as returning e.g. string|void while actually returning null implicitly via bare return; statements or falling off the end of the function.
This PR replaces void with null in union @return annotations across 18 files in wp-includes, adds explicit return null; statements where appropriate, and updates @return description text to say "Null" instead of "Void".
## Changes
### void|string → string|null (19 instances)
Echo/display functions that return a string when $display/$echo is false and null when printing directly.
general-template.php—get_search_form(),wp_loginout(),wp_login_form(),wp_register(),wp_get_archives(),get_calendar()post-template.php—the_title(),the_title_attribute(),wp_list_pages(),wp_page_menu()comment-template.php—comment_class(),trackback_url(),wp_list_comments()author-template.php—wp_list_authors()bookmark-template.php—wp_list_bookmarks()link-template.php—paginate_comments_links()category-template.php—wp_list_categories(),wp_tag_cloud()nav-menu-template.php—wp_nav_menu()
### void|false → false|null (11 instances)
Functions that return false on failure and null (implicitly) on success.
general-template.php—get_header(),get_footer(),get_sidebar(),get_template_part()functions.php—do_enclose()taxonomy.php—update_object_term_cache()theme.php—add_theme_support()category-template.php—the_terms()comment.php—do_trackbacks()user.php—update_user_caches()class-wp-customize-setting.php—WP_Customize_Setting::save()
### void|WP_Error / void|IXR_Error → WP_Error|null (5 instances)
Functions that return an error object on failure and null on success.
class-wp-metadata-lazyloader.php—queue_objects(),reset_queue()class-wp-image-editor-imagick.php—thumbnail_image()class-wp-xmlrpc-server.php—_toggle_sticky()rest-api/endpoints/class-wp-rest-edit-site-export-controller.php—export()
## Backward Compatibility
No behavior change. In PHP, a bare return; and return null; are identical at runtime across all PHP versions. This is a documentation-only fix.
## Test Case
No test case updates needed. Existing tests using assertNull() on the success paths of these functions were already written correctly, as null was always the actual runtime return value.
---
Trac ticket: https://core.trac.wordpress.org/ticket/64704
---
## Use of AI Tools
AI assistance: Yes
Tool(s): GitHub Copilot
Model(s): Claude Sonnet 4.6
Used for: Identifying all void union return instances, generating replacements, and drafting the PR description; all changes were reviewed and verified by me before submission.
#16
@
5 days ago
- Milestone changed from 7.1 to 7.0
Since the 7.0 branch hasn't been created yet, the commit r62178 is in trunk and actually will be included in 7.0 unless we revert.
#17
@
5 days ago
- Milestone changed from 7.0 to 7.1
Correction: the 7.0 branch does exist, and this commit is not present in the branch: https://github.com/WordPress/wordpress-develop/commits/7.0/
Co-Authored-By: xateman
Trac ticket: https://core.trac.wordpress.org/ticket/64704
## Use of AI Tools
Used AI for research, documentation and for the replacements. Everything was reviewed by myself and @xateman before opening this PR.