Skip to content

Code Quality: Annotate WP_HTML_Tag_Processor::parse_next_tag() as impure - #13057

Closed
tstokes8040 wants to merge 1 commit into
WordPress:trunkfrom
tstokes8040:phpstan/tag-processor-impure
Closed

Code Quality: Annotate WP_HTML_Tag_Processor::parse_next_tag() as impure#13057
tstokes8040 wants to merge 1 commit into
WordPress:trunkfrom
tstokes8040:phpstan/tag-processor-impure

Conversation

@tstokes8040

@tstokes8040 tstokes8040 commented Aug 14, 2026

Copy link
Copy Markdown

✅ Committed in r63301 (3ecb392).


PHPStan infers WP_HTML_Tag_Processor::parse_next_tag() as pure, so it retains the STATE_READY narrowing assigned to $parser_state on line 981 across the call on line 989 — even though parse_next_tag() reassigns that property throughout its body. Every state comparison after the call is then decided at analysis time rather than at runtime.

The effects compound. The three !== comparisons on lines 1004–1006 each resolve to true, so both && nodes in that condition resolve to true, so the early return true on line 1008 is treated as unconditional. Everything from line 1012 to the end of base_class_next_token() is consequently analyzed as unreachable, which in turn makes skip_rawtext() and skip_script_data() appear uncalled and $skip_newline_at appear never assigned an int.

@phpstan-impure is the annotation PHPStan's own tip recommends here, and there is precedent for it in this very file: next_tag() already carries one.

Ten baselined errors across six identifiers are resolved:

Identifier Removed
booleanAnd.alwaysTrue 2
notIdentical.alwaysTrue 3
identical.alwaysFalse 1
deadCode.unreachable 1
method.unused 2
property.unusedType 1

booleanAnd.alwaysTrue and property.unusedType reach zero, so as the baseline headers direct, both files are deleted along with their includes entries in phpstan.neon.dist. The baselines were regenerated with composer phpstan:baselines -- --identifier=..., not edited by hand.

Testing instructions

  1. npm run typecheck:php on trunk reports [OK] No errors, because the occurrences are baselined.
  2. Delete tests/phpstan/baselines/booleanAnd.alwaysTrue.neon and its includes entry, then re-run: PHPStan reports Result of && is always true. twice at src/wp-includes/html-api/class-wp-html-tag-processor.php:1004.
  3. With this branch applied, npm run typecheck:php reports [OK] No errors across 1289 files with both baselines gone. composer lint is also clean for the modified file.
  4. npm run test:php — 30865 tests, 4558966 assertions, 86 warnings, 44 skipped, and one failure: Tests_Script_Modules_WpScriptModules::test_default_script_module_files_exist, looking for src/wp-includes/js/dist/script-modules/a11y/index.js. That path is gitignored build output that is unbuilt in my checkout, and the test fails identically on unmodified trunk, so it is unrelated to this change. npm run test:php -- --group html-api passes 1714 tests, 6107 assertions, 0 failures.

Documentation-only change; no runtime behaviour is affected.

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: locating the occurrence via the PHPStan booleanAnd.alwaysTrue baseline, diagnosing the purity-inference cause and its knock-on effects, regenerating the baselines, and drafting this description. The change itself and the verification against the full PHPStan, PHPCS and PHPUnit runs were reviewed and confirmed by me in a local development environment.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props tstokes8040, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

*
* @return bool Whether a tag was found before the end of the document.
*
* @phpstan-impure

@westonruter westonruter Aug 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is consistent with the same which was added in 68c0f19 on the next_tag() method:

* @return bool Whether a tag was matched.
*
* @phpstan-impure
*/
public function next_tag( $query = null ): bool {

…ure.

PHPStan infers `parse_next_tag()` as pure, so it retains the `STATE_READY` narrowing assigned to `$parser_state` before the call, even though the method reassigns that property throughout. Every subsequent state comparison in `base_class_next_token()` is then decided statically, which additionally left the remainder of that method analyzed as unreachable and two of its callees reported as unused.

This empties the `booleanAnd.alwaysTrue` and `property.unusedType` PHPStan baselines, so both files are removed along with their `includes` entries in `phpstan.neon.dist`. The baselines were regenerated with:
{{{
composer phpstan:baselines -- --identifier=booleanAnd.alwaysTrue,notIdentical.alwaysTrue,identical.alwaysFalse,deadCode.unreachable,method.unused,property.unusedType
}}}

Props tstokes8040.
See #65817.
@tstokes8040
tstokes8040 force-pushed the phpstan/tag-processor-impure branch from b84edad to 0cd03e6 Compare August 14, 2026 18:08
pento pushed a commit that referenced this pull request Aug 14, 2026
…PHPStan.

PHPStan infers the `WP_HTML_Tag_Processor::parse_next_tag()` method as pure, so the `STATE_READY` value narrowed onto `$parser_state` before the call survives across it, even though the method reassigns that property throughout its body. Every state comparison that follows in the `base_class_next_token()` method is then decided at analysis time rather than at runtime: both `&&` operands resolve to `true`, the early `return true` is treated as unconditional, and the remainder of the method is analyzed as unreachable. This in turn leaves `skip_rawtext()` and `skip_script_data()` reported as uncalled and `$skip_newline_at` as never assigned an `int`. Adding `@phpstan-impure`, the annotation PHPStan's own tip recommends, resolves ten baselined errors across six identifiers; `next_tag()` in the same class already carries it.

This empties the `booleanAnd.alwaysTrue` and `property.unusedType` baselines, so both files are removed along with their `includes` entries in `phpstan.neon.dist`.

Developed in #13057.
Follow-up to r61934, r63023.

Props tstokes8040.
See #65817.


git-svn-id: https://develop.svn.wordpress.org/trunk@63301 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Aug 14, 2026
…PHPStan.

PHPStan infers the `WP_HTML_Tag_Processor::parse_next_tag()` method as pure, so the `STATE_READY` value narrowed onto `$parser_state` before the call survives across it, even though the method reassigns that property throughout its body. Every state comparison that follows in the `base_class_next_token()` method is then decided at analysis time rather than at runtime: both `&&` operands resolve to `true`, the early `return true` is treated as unconditional, and the remainder of the method is analyzed as unreachable. This in turn leaves `skip_rawtext()` and `skip_script_data()` reported as uncalled and `$skip_newline_at` as never assigned an `int`. Adding `@phpstan-impure`, the annotation PHPStan's own tip recommends, resolves ten baselined errors across six identifiers; `next_tag()` in the same class already carries it.

This empties the `booleanAnd.alwaysTrue` and `property.unusedType` baselines, so both files are removed along with their `includes` entries in `phpstan.neon.dist`.

Developed in WordPress/wordpress-develop#13057.
Follow-up to r61934, r63023.

Props tstokes8040.
See #65817.

Built from https://develop.svn.wordpress.org/trunk@63301


git-svn-id: http://core.svn.wordpress.org/trunk@62494 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants