Skip to content

Code Quality: Fix the booleanAnd.alwaysFalse PHPStan error and remove the baseline - #15478

Closed
bgturner wants to merge 7 commits into
WordPress:trunkfrom
bgturner:bt/ticket/65817/booleanAndAlwaysFalse
Closed

bgturner wants to merge 7 commits into
WordPress:trunkfrom
bgturner:bt/ticket/65817/booleanAndAlwaysFalse

Conversation

@bgturner

@bgturner bgturner commented Nov 4, 2026 •

Copy link
Copy Markdown

✅ Committed in r65282 (e5cb560).


Fixes the only booleanAnd.alwaysFalse PHPStan error, in src/wp-admin/themes.php, by declaring the admin globals that the file reads, rather than by changing the condition. The baseline file for the identifier is removed.

// 0 = name, 1 = capability, 2 = file.
if ( 0 === strcmp( $self, $item[2] ) && empty( $parent_file )
	|| $parent_file && $item[2] === $parent_file
) {
	$class = ' current';
}

PHPStan saw $parent_file only as the literal 'themes.php' assigned earlier in the file, so it reported empty( $parent_file ) as never true. That is a false positive: wp-admin/menu-header.php, which wp-admin/admin-header.php loads, passes $parent_file through the parent_file filter, so a plugin can make it empty and reach the first clause. Likewise $self is set in menu-header.php, and $submenu in wp-admin/menu.php, neither of which PHPStan follows across the require chain.

The condition is therefore left exactly as it is in trunk. After the admin-header.php require, the file now declares the globals with a file-scope global statement:

/**
 * @global string $self        The current admin page, set in wp-admin/menu-header.php.
 * @global string $parent_file The parent menu file, filtered in wp-admin/menu-header.php.
 * @global array<string, array<int, array{ 0: string, 1: string, 2: string, 3?: string, 4?: string }>> $submenu The admin submenu items keyed by parent slug, set in wp-admin/menu.php.
 */
global $self, $parent_file, $submenu;

An inline @var is not enough here: with treatPhpDocTypesAsCertain: false, the always-true/false rules use the native type, which only the global statement resets. The @global tags are then applied by tests/phpstan/GlobalDocBlockVisitor.php.

Baseline entries removed for themes.php:

  • booleanAnd.alwaysFalse (the whole baseline file, and its includes entry in phpstan.neon.dist)
  • booleanAnd.leftAlwaysTrue
  • empty.variable for $parent_file
  • variable.undefined for $self and $submenu

With the $submenu shape, the level 10 mixed errors in the Appearance submenu loop are also gone. themes.php no longer has any baseline entries.

Original description

Fixes the only booleanAnd.alwaysFalse error and removes tests/phpstan/baselines/booleanAnd.alwaysFalse.neon.

The error

src/wp-admin/themes.php:376
Result of && is always false.
// 0 = name, 1 = capability, 2 = file.
if ( 0 === strcmp( $self, $item[2] ) && empty( $parent_file )
	|| $parent_file && $item[2] === $parent_file
) {
	$class = ' current';
}

$parent_file is assigned once, earlier in this file, to the literal string 'themes.php', and never reassigned before this line. So empty( $parent_file ) can never be true, and the first half of the || -- 0 === strcmp( $self, $item[2] ) && empty( $parent_file ) -- can never be true either, regardless of what $self is.

The fix

Drop the always-false clause. The condition reduces to the half that was actually reachable: $parent_file && $item[2] === $parent_file.

This removes the file's only reference to $self, which also retires a variable.undefined baseline entry for it. To be precise about why: $self is not dead or unassigned code -- it's a legitimate WordPress admin global, set in wp-admin/menu-header.php (required transitively via wp-admin/admin-header.php, itself required earlier in this same file). PHPStan's single-file analysis can't see across that require chain, the same way it can't see $submenu's definition in wp-admin/menu.php. Removing the reference is safe not because $self was unused, but because the clause it was part of is unreachable regardless of $self's value (see verification below) -- its removal from the variable.undefined baseline is an incidental consequence, not a fix to a real bug.

Verification

Since this is top-level script code (not a function) gated behind capability checks and wp_die(), it isn't practically unit-testable without fully bootstrapping an admin page render. Instead, since $parent_file is a fixed constant here, the whole expression reduces to a function of just $self and $item[2]. I exhaustively compared the old and new expressions across representative values of both, including $self's real runtime value ('themes.php') and the values Intelephense/PHPStan would assume if genuinely unset -- all agree:

self=NULL            item2='themes.php'    old=true  new=true  match
self=NULL            item2='nav-menus.php' old=false new=false match
self=NULL            item2=''              old=false new=false match
self=NULL            item2='0'             old=false new=false match
self='themes.php'    item2='themes.php'    old=true  new=true  match
... (16/16 combinations match)

Also ran the full admin test group (1910 tests) with no regressions.

Trac ticket: https://core.trac.wordpress.org/ticket/65817

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): The original commits (2a0ba8b and 25c18e3) do not record a model; Claude Opus 5.5 (medium and high effort) for the follow-up commits.
Used for: The original commits were authored with Claude Code. The follow-up commits, which restore the original condition and declare the globals, were written with Claude Code, along with a code review of the branch and this description; the changes were reviewed and directed by a human contributor.


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.

@github-actions

github-actions Bot commented Nov 4, 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.

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

Props bgturner, westonruter.

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


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

This comment has been minimized.

@github-actions

github-actions Bot commented Nov 4, 2026

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.

…ve the baseline.

`$parent_file` is hard-coded to the string `'themes.php'` earlier in this
file and never reassigned, so `empty( $parent_file )` can never be true.
Drop the always-false clause; the condition reduces to the half that was
actually reachable, regardless of $self's value.

This also removes the file's only reference to $self, retiring its
`variable.undefined` baseline entry. $self is not actually dead: it's
set in wp-admin/menu-header.php, required transitively via
wp-admin/admin-header.php earlier in this same file. PHPStan's
single-file analysis can't see across that require chain -- the same
reason it can't see $submenu's definition in wp-admin/menu.php. The
baseline entry's retirement is incidental to removing the unreachable
clause, not a fix to a real bug.

See #65817.
@bgturner
bgturner force-pushed the bt/ticket/65817/booleanAndAlwaysFalse branch from eb7356b to 2a0ba8b Compare November 4, 2026 21:59
bgturner and others added 6 commits November 4, 2026 15:33
Removing the always-false clause also removed the file's only
empty( $parent_file ) call, which had its own, separate baseline
entry under the empty.variable identifier. That entry is now stale
since the error it describes no longer occurs.

See #65817.
…als.

Reverts the logic change from 2a0ba8b, which removed the
`0 === strcmp( $self, $item[2] ) && empty( $parent_file )` branch on the
premise that `$parent_file` is never reassigned after being set to
'themes.php'. That premise is wrong: wp-admin/menu-header.php, loaded via
admin-header.php, passes `$parent_file` through the `parent_file` filter
(and `$self` is derived there from PHP_SELF), so the branch is reachable at
runtime.

PHPStan cannot follow globals across the require chain, so it inferred the
literal 'themes.php' and reported the condition as always false. An inline
`@var` does not help, since `treatPhpDocTypesAsCertain: false` makes those
rules use the native type. Instead, a `global $self, $parent_file;`
statement with `@global` tags after the admin-header.php require resets
their native types, which clears `booleanAnd.alwaysFalse`,
`empty.variable`, `booleanAnd.leftAlwaysTrue`, and the `$self`
`variable.undefined` error without changing behavior. The now-stale
`booleanAnd.leftAlwaysTrue` baseline entry for themes.php is removed.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Code-Session-Id: f160029d-7c28-4d44-9016-a98aa4606cdc
`$submenu` is built in wp-admin/menu.php, loaded via admin.php, so PHPStan
could not see it and treated it as possibly undefined and `mixed`. Adding
it to the file-scope `global` statement, with an `@global` tag describing
its structure (submenu items keyed by parent slug and position, each a
list of menu title, capability, and slug, plus an optional page title and
classes), clears the `mixed` errors throughout the Appearance submenu loop.
The now-stale `$submenu might not be defined` baseline entry for
themes.php is removed.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Code-Session-Id: f160029d-7c28-4d44-9016-a98aa4606cdc
On the Themes screen, `$self` and `$parent_file` are both 'themes.php',
which the loop skips via `$forbidden_paths`, so in stock core no button
ever gets the `current` class. The condition is only reachable when a
plugin changes `$parent_file` through the `parent_file` filter. Note this
in the comment, so the branch is not mistaken for dead code again.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Code-Session-Id: f160029d-7c28-4d44-9016-a98aa4606cdc
This reverts b010970. The branch now leaves the condition's code
unchanged from trunk, so the explanation is not needed.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Code-Session-Id: f160029d-7c28-4d44-9016-a98aa4606cdc
wporg-sync pushed a commit that referenced this pull request Nov 5, 2026
In `themes.php`, PHPStan only knew `$parent_file` as the `'themes.php'` literal assigned earlier in the file, so it reported the `empty( $parent_file )` clause of the Appearance submenu condition as never true. That is a false positive: `wp-admin/menu-header.php`, loaded via `admin-header.php`, passes `$parent_file` through the `parent_file` filter, and it also sets `$self`, while `$submenu` comes from `wp-admin/menu.php`. None of these are visible to PHPStan across the `require` chain. The condition is therefore left unchanged, and the globals are instead declared with a file-scope `global` statement and `@global` tags. An inline `@var` would not suffice, since with `treatPhpDocTypesAsCertain` disabled the always-true and always-false rules use the native type, which only the `global` statement resets.

This retires the `booleanAnd.alwaysFalse` baseline along with the remaining baseline entries for `themes.php`, and the `$submenu` array shape also resolves the `mixed` errors in the submenu loop at higher rule levels.

Developed in #15478.
Follow-up to r63019, r63023.

Props bgturner, westonruter.
See #65817.


git-svn-id: https://develop.svn.wordpress.org/trunk@65282 602fd350-edb4-49c9-b593-d223f7449a82
@westonruter westonruter closed this Nov 5, 2026
wporg-sync pushed a commit to WordPress/WordPress that referenced this pull request Nov 5, 2026
In `themes.php`, PHPStan only knew `$parent_file` as the `'themes.php'` literal assigned earlier in the file, so it reported the `empty( $parent_file )` clause of the Appearance submenu condition as never true. That is a false positive: `wp-admin/menu-header.php`, loaded via `admin-header.php`, passes `$parent_file` through the `parent_file` filter, and it also sets `$self`, while `$submenu` comes from `wp-admin/menu.php`. None of these are visible to PHPStan across the `require` chain. The condition is therefore left unchanged, and the globals are instead declared with a file-scope `global` statement and `@global` tags. An inline `@var` would not suffice, since with `treatPhpDocTypesAsCertain` disabled the always-true and always-false rules use the native type, which only the `global` statement resets.

This retires the `booleanAnd.alwaysFalse` baseline along with the remaining baseline entries for `themes.php`, and the `$submenu` array shape also resolves the `mixed` errors in the submenu loop at higher rule levels.

Developed in WordPress/wordpress-develop#15478.
Follow-up to r63019, r63023.

Props bgturner, westonruter.
See #65817.

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


git-svn-id: http://core.svn.wordpress.org/trunk@64431 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