Code Quality: Complete the conditional return types for post and term queries - #13216
Code Quality: Complete the conditional return types for post and term queries#13216swissspidy wants to merge 2 commits into
Conversation
… queries. `get_posts()` and `WP_Query::query()` narrow their return type on the `fields` argument, but only recognise `'ids'`. `'id=>parent'` also returns an array of integers (parent post IDs keyed by post ID), and was being reported as `WP_Post[]`. Add it to both conditions. `WP_Term_Query::query()` takes the query arguments directly, so its return type can be resolved the same way. Annotate it with the mapping already documented on `WP_Term_Query::get_terms()`: * `'count'` returns `0|numeric-string`. The integer covers the early `return 0;` in `WP_Term_Query::get_terms()`, taken when `'child_of'` or `'parent'` names a term with no descendants — every other count path returns the numeric string the docblock describes. * `'ids'`, `'tt_ids'` and `'id=>parent'` return arrays of integers. * `'names'`, `'slugs'`, `'id=>name'` and `'id=>slug'` return arrays of strings. * Everything else returns an array of `WP_Term` objects. The sibling `WP_Query::get_posts()` and `WP_Term_Query::get_terms()` methods read the query vars off the instance rather than taking them as an argument, so their return types cannot be narrowed this way. Follow-up to r62648. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZsZWUzYLafHeu6RXnLMUy
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @claude. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| * | ||
| * @phpstan-return ( | ||
| * $query is array{ fields: 'ids', ... } ? int[] : WP_Post[] | ||
| * $query is array{ fields: 'ids'|'id=>parent', ... } ? int[] : WP_Post[] |
There was a problem hiding this comment.
Shouldn't there be an additional case for'id=>parent' which returns an explicit array<int, int>? Granted, int[] also describes this, but in a less specific way: the keys could be strings too.
There was a problem hiding this comment.
Actually, it seems like when an object cache is in use, it returns the raw wp_cache_get_multiple() result, which is keyed by the
cache keys, i.e. "post_parent:{$id}" strings. So int[] would be nore correct. Or array<non-falsy-string, int>|array<int, int>
| * | ||
| * @phpstan-return ( | ||
| * $args is array{ fields: 'ids', ... } ? int[] : WP_Post[] | ||
| * $args is array{ fields: 'ids'|'id=>parent', ... } ? int[] : WP_Post[] |
There was a problem hiding this comment.
Same here about the object cache case
… …>`. Address review feedback on the conditional return type added to `WP_Term_Query::query()`. `int[]` and `string[]` are shorthand for `array<array-key, int>` and `array<array-key, string>`, which say nothing about the keys. For the three `'id=>…'` fields the keys are the point: `WP_Term_Query::format_terms()` builds them from `WP_Term::$term_id`, which `sanitize_term_field()` casts to `int`. Give those fields their own branches returning `array<int, int>` and `array<int, string>`, and leave `'ids'`, `'tt_ids'`, `'names'` and `'slugs'` as `int[]`/`string[]`, whose keys are positional and carry no meaning. While here, correct the field list on `WP_Term_Query::get_terms()`, which grouped `'id=>parent'` under "an array of numeric strings". It returns parent term IDs as integers, as the `$fields` argument is already documented to do in `WP_Term_Query::__construct()`. Follow-up to r62648. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LUwqNiMhXKBTa8GJyKzEZH
Follow-up to r62648, which added conditional return types keyed on the
fieldsargument toget_posts()andWP_Query::query().'id=>parent'was missing from both conditions. Both only recognise'ids', but'id=>parent'also returns an array of integers — parent post IDs keyed by post ID, per theswitchinWP_Query::get_posts()and the$post_parentsreturn a few hundred lines below it. Todayget_posts( [ 'fields' => 'id=>parent' ] )is reported asWP_Post[], which is wrong in a way that is worse than no annotation at all.WP_Term_Query::query()can be annotated the same way. LikeWP_Query::query(), it takes the query arguments directly, so the mapping already documented in prose onWP_Term_Query::get_terms()can be expressed as a type:'count'returns0|numeric-string.'ids','tt_ids'and'id=>parent'return arrays of integers.'names','slugs','id=>name'and'id=>slug'return arrays of strings.WP_Termobjects.The integer in the count branch is not a typo.
WP_Term_Query::get_terms()bails out early with an integer rather than the numeric string the docblock describes whenchild_oforparentnames a term with no descendants:so
get_terms( [ 'fields' => 'count', 'parent' => $id ] )really can return0. Every other count path returns$wpdb->get_var()'s numeric string. Annotatingnumeric-stringalone would have been a promise core does not keep. (The@returnline on both methods says onlystringfor this case, and the$fieldsentry inWP_Term_Query::__construct()saysint— that inconsistency predates this patch and is left alone here.)WP_Query::get_posts()andWP_Term_Query::get_terms()read the query vars off the instance rather than taking them as an argument, so there is nothing to narrow their return types on and they are unchanged.Analysis impact
Verified with PHPStan 2.2 against extracted copies of the three annotated docblocks:
phpDoc.parseError.{ fields: 'ids', ... }) still match when other query vars are present.get_terms()intaxonomy.php— the only core caller ofWP_Term_Query::query()— produces no new errors before or after, including the widened count branch. PHPStan already normalises the existing@return WP_Term[]|int[]|string[]|stringinto a single array type unioned withstring, so nothing widens. (Confirmed thereturn.typerule was actually live in that harness by deliberately breaking it.)'id=>parent'(wp_edit_posts_query()viawp(), and_get_term_hierarchy()viaget_terms()) goes through the changed conditions, so no baseline entries are affected.The full core PHPStan run has not been executed against this branch.
Why this matters downstream
php-stubs/wordpress-stubscarries its ownfunctionMap.phpentry forget_posts()that covers both'ids'and'id=>parent'. Once the stubs bump past 7.0.1, that entry becomes a duplicate of the core annotation and will be removed as obsolete — exactly what php-stubs/wordpress-stubs#475 did forWP_Theme::get(). Without this patch, that removal would silently regress'id=>parent'toWP_Post[]. The matching stubs change is at swissspidy/wordpress-stubs#1.Trac ticket: https://core.trac.wordpress.org/ticket/65817
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Used for: Locating the gap and writing the three docblock annotations
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.