Repository navigation
Code Quality: Annotate three impure functions for PHPStan - #13074
tstokes8040 wants to merge 3 commits into
Conversation
PHPStan infers `current_user_can()`, `WP_Filesystem_Base::chmod()` and `WP_HTML_Processor::next_token()` as pure, so it remembers what each returned and reuses that value at a later call in the same scope. All three can legitimately answer differently the second time: `current_user_can()` reads the current user and applies the `user_has_cap` filter, `chmod()` changes the permissions that a following `is_writable()` reports, and `next_token()` advances the parser so the token accessors describe a different token.
The annotations go on the mutating call rather than on the accessors PHPStan's tips name. `is_writable()`, `is_tag_closer()`, `expects_closer()`, `get_current_depth()` and `set_bookmark()` are read-only; what invalidates a remembered value is the mutation sitting between the two reads.
This resolves 16 baselined errors across seven identifiers, among them the five `! current_user_can()` guards that re-check a capability already tested earlier in the same file, and the `is_writable()` and `copy()` re-checks that follow a `chmod()` in `copy_dir()`, `WP_Upgrader` and `Language_Pack_Upgrader`. The baselines were regenerated with:
{{{
composer phpstan:baselines -- --identifier=booleanNot.alwaysTrue,booleanNot.alwaysFalse,booleanAnd.leftAlwaysTrue,booleanOr.alwaysTrue,deadCode.unreachable,if.alwaysTrue,ternary.alwaysTrue
}}}
Props tstokes8040.
See #65817.
|
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. |
|
🤖 Comment by Claude Opus 5.5 The This PR will now conflict with trunk. The remaining annotations on |
The `WP_HTML_Processor::next_token()` annotation from this branch landed on trunk in r64335, along with the removal of its baseline entries and of the `booleanOr.alwaysTrue` baseline. The conflicting baselines were resolved by taking trunk's versions and regenerating the affected identifiers with `composer phpstan:baselines`, so this branch now carries only the `current_user_can()` and `WP_Filesystem_Base::chmod()` annotations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PHPStan infers `current_user_can()` and `WP_Filesystem_Base::chmod()` as pure, so it remembers what each returned and reuses that value at a later call in the same scope, reporting the second check as always true or always false. Yet a capability check can legitimately answer differently the second time, since it reads the current user and applies the `user_has_cap` filter, so a plugin hooked in between (for example during the `admin-header.php` load in `wp-admin/link-manager.php`) can change the result. Similarly, `chmod()` changes the permissions that a following `is_writable()` reports. The annotation goes on the call that invalidates the remembered value rather than on read-only accessors like `is_writable()`, and on `current_user_can()` itself since annotating `WP_User::has_cap()` cleared nothing. This resolves 13 baselined errors across five identifiers. Developed in #13074. Follow-up to r63023, r63301, r63357, r64335. Props tstokes8040. See #65817. git-svn-id: https://develop.svn.wordpress.org/trunk@64336 602fd350-edb4-49c9-b593-d223f7449a82
PHPStan infers `current_user_can()` and `WP_Filesystem_Base::chmod()` as pure, so it remembers what each returned and reuses that value at a later call in the same scope, reporting the second check as always true or always false. Yet a capability check can legitimately answer differently the second time, since it reads the current user and applies the `user_has_cap` filter, so a plugin hooked in between (for example during the `admin-header.php` load in `wp-admin/link-manager.php`) can change the result. Similarly, `chmod()` changes the permissions that a following `is_writable()` reports. The annotation goes on the call that invalidates the remembered value rather than on read-only accessors like `is_writable()`, and on `current_user_can()` itself since annotating `WP_User::has_cap()` cleared nothing. This resolves 13 baselined errors across five identifiers. Developed in WordPress/wordpress-develop#13074. Follow-up to r63023, r63301, r63357, r64335. Props tstokes8040. See #65817. Built from https://develop.svn.wordpress.org/trunk@64336 git-svn-id: http://core.svn.wordpress.org/trunk@63486 1a063a9b-81f0-0310-95a4-ce76da25c4cd
✅ Committed in r64336 (d655404).
PHPStan infers
current_user_can()andWP_Filesystem_Base::chmod()as pure, so it remembers what each returned and reuses that value at a later call in the same scope. Both can legitimately answer differently the second time:current_user_can()user_has_capfilter, so a plugin hooked in between can change the answerWP_Filesystem_Base::chmod()is_writable()reportsThe clearest case is
wp-admin/link-manager.php. Line 11 bails out unless the user hasmanage_links, and line 91 checks the same capability again afteradmin-header.phphas been required and its hooks have fired. PHPStan carried the line 11 answer forward and reported the line 91 guard as dead.Note
This PR originally also annotated
WP_HTML_Processor::next_token(). That annotation landed separately in r64335 via #14037, so trunk has been merged in and the PR now carries only the two annotations above.Annotating the mutation, not the accessor
PHPStan's tips name
is_writable()as a candidate. That is a read-only accessor and annotating it would be inaccurate. What actually invalidates a remembered value is the mutating call sitting between the two reads, so the annotation belongs there: inWP_Upgrader::install_package()it is thechmod()between twois_writable()calls.WP_Filesystem_Base::copy()was annotated at first and then removed after re-running the analysis with and without it produced identical results;chmod()alone covers thecopy_dir()case.WP_User::has_cap(), where theuser_has_capfilter is actually applied, was also tried and cleared nothing, so the annotation sits oncurrent_user_can()itself.Impact
13 errors resolved across five identifiers, with no new error of any identifier anywhere in the codebase:
booleanNot.alwaysFalsebooleanNot.alwaysTruebooleanAnd.leftAlwaysTrueif.alwaysTrueternary.alwaysTrueThe
if.alwaysTrueerrors are inwp-admin/my-sites.phpandwp-admin/upload.php(×2), and theternary.alwaysTrueone is inwp-admin/theme-install.php.The baselines are generated, not hand-edited. No baseline reaches zero here, so none is deleted and
phpstan.neon.distis unchanged.Testing instructions
npm run typecheck:phpontrunkreports[OK] No errors, because the occurrences are baselined.npm run typecheck:phpreports[OK] No errorswith the baselines regenerated (composer phpstan:baselines).Documentation-only change; no runtime behavior is affected.
Related: #13057 annotates
WP_HTML_Tag_Processor::parse_next_tag()for the same reason (committed in r63301), and #13071 clears a different cluster of the same boolean baselines.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
Used for: grouping the remaining boolean PHPStan baselines by root cause, identifying this cluster, testing which annotation placements were load-bearing, measuring the before/after error diff across the whole codebase, regenerating the baselines, and drafting this description. The change itself and the verification runs were reviewed and confirmed by me in a local development environment.
The trunk merge after r64335, the baseline regeneration, and this updated description were done with Claude Code (Claude Opus 5.5).
Count of removed errors: 13