Docs: Add PHPStan templates and generics - #13895
swissspidy wants to merge 3 commits into
Conversation
Adds templates to the functions that return the value they are passed, such as `sanitize_term()` and `sanitize_category()`, and gives `urlencode_deep()`, `rawurlencode_deep()` and `urldecode_deep()` a conditional return type based on `map_deep()`'s. `WP_Widget` becomes generic in the shape of its instance settings, so that a widget can declare its settings with `@extends WP_Widget<...>`. The old instance and the form instance can be empty, the new instance is the raw input, and `update()` can return `false`. The widget registration functions accept `class-string<WP_Widget>`, and `WP_Widget_Factory::register()` ignores the argument count error that follows, since every widget class declares its own constructor. `get_term_to_edit()` now documents the `WP_Term` it returns, rather than `int` or `null`, which it never returns. See #65817. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc
|
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 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. |
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. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Array identity types are unsound, generic widget specializations are rejected by bare annotations, and unregister_widget() remains incomplete.
Review effort: Balanced
Findings: 3
Open (4)
What changed in this PR
Adds PHPStan templates and generics for sanitization, deep URL encoding, and widget APIs.
Changes:
- Models container-aware return types for sanitization and encoding.
- Makes
WP_Widgetsettings generic and narrows widget registration types. - Updates PHPStan baselines for improved inference.
| File | Description |
|---|---|
src/wp-includes/category.php |
Types category sanitization. |
src/wp-includes/class-wp-widget-factory.php |
Narrows widget registration types. |
src/wp-includes/class-wp-widget.php |
Adds generic widget settings. |
src/wp-includes/formatting.php |
Types deep URL transformations. |
src/wp-includes/taxonomy.php |
Types term sanitization results. |
src/wp-includes/widgets.php |
Narrows register_widget(). |
tests/phpstan/baselines/argument.type.neon |
Updates generic diagnostic. |
tests/phpstan/baselines/isset.property.neon |
Updates generic diagnostic. |
tests/phpstan/baselines/property.nonObject.neon |
Removes resolved diagnostics. |
tests/phpstan/baselines/return.unusedType.neon |
Removes obsolete return diagnostics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 Comment by Claude Opus 5.5 Props for this subset, gathered from the full history of
Props for this PR (swissspidy for the split, westonruter for the review that shaped it): Generated by Claude Code |
This no longer holds when a constant array or object is passed. Both, See https://phpstan.org/r/5401ad35-d4b7-415e-b296-811713ca0ea5 I strongly recommend against using |
Both sanitize_term() and sanitize_category() rewrite field values and add a filter key, so returning the template type T was unsound for array shapes. Replace the template with a conditional return type that keeps WP_Term, and otherwise returns array<string, mixed> or object. Also add the class-string<WP_Widget>|WP_Widget parameter type to unregister_widget(), matching register_widget(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BuoHZQvJMvPEYp4Sfjgvd
|
@IanDelMar Thanks, agreed. d2e5e84 removes the template from The Generated by Claude Code |


Split out of #13530, as proposed in this comment (subset 8).
sanitize_term()andsanitize_category()get a conditional return type: aWP_Termstays aWP_Term, an array becomesarray<string, mixed>, and any other object becomesobject. They don't use a template, because both functions rewrite field values and add afilterkey, so returning the input type unchanged would be unsound (feedback). Following from that,get_term_to_edit()now documents theWP_Termit returns, rather than theintandnullit never returns.urlencode_deep(),rawurlencode_deep()andurldecode_deep()get a conditional return type modelled onmap_deep()'s.WP_Widgetbecomes generic in the shape of its instance settings, so a widget can declare its settings with@extends WP_Widget<...>.register_widget(),unregister_widget()andWP_Widget_Factorytakeclass-string<WP_Widget>|WP_Widget.WP_Widget_Factory::register()instantiates the class with no arguments, so it carries an@phpstan-ignore arguments.countexplaining that every widget class declares its own constructor. That ignore was left out of Pass the documented values for deprecated arguments #13872, because the error only exists once this change is in.Changes from #13530
These follow the review analysis:
*_deep()functions no longer claimTin,Tout, since they return transformed strings. They use(T is array ? array<key-of<T>, mixed> : (T is object ? T : string))instead.WP_Widgetgenerics.update()'s$old_instanceandform()'s$instanceareT|array{}, since both can be empty.$new_instanceisarray<string, mixed>, since it is the raw input.update()returnsarray<string, mixed>|false, since it can cancel saving.wp_is_uuid()is left out. It rejected themixedits callers pass, and the@phpstan-assert-if-truealone is enough; it is in the assertions PR.wp_http_validate_url()andrest_sanitize_boolean()are left out, since neither template was used.WP_Widget_Factory::$widgetsloses the second@phpstan-var array<int, WP_Widget>. It was a merge leftover that contradicted the existingarray<non-decimal-int-string, WP_Widget>.sanitize_term()andsanitize_category()are replaced by the conditional return type described above.Effect on the analysis
composer run phpstanpasses.return.unusedTypeloses the twoget_term_to_edit()entries.property.nonObjectloses threeWP_Terms_List_Tableerrors.WP_Widgetentries inargument.typeandisset.propertyare reworded, now that the class is generic.At level 10, compared against trunk, 44 errors are fixed and 22 are introduced. Most of the 22 are existing
WP_Widgeterrors reworded with the generic class name, ormixedvalues passed to the newly typed parameters. (These counts were measured before thesanitize_term()change.)Trac ticket: https://core.trac.wordpress.org/ticket/65817
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Splitting #13530 by tag kind, applying the corrections from the review analysis on #13530, checking each subset with
composer run phpstan(also at level 10, compared against trunk) andphpcs, and drafting the commit message and this description. Reviewed by me.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.
🤖 Generated with Claude Code
https://claude.ai/code/session_015BuoHZQvJMvPEYp4Sfjgvd