Skip to content

Docs: Add PHPStan types for optional callback parameters - #13890

Open
swissspidy wants to merge 8 commits into
WordPress:trunkfrom
swissspidy:fix/phpstan-callbacks
Open

swissspidy wants to merge 8 commits into
WordPress:trunkfrom
swissspidy:fix/phpstan-callbacks

Conversation

@swissspidy

@swissspidy swissspidy commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Split out of #13530, as proposed in this comment (subset 3).

  • The 13 add_*_page() functions, and the Custom_Background and Custom_Image_Header constructors and properties, default their callback to '', which a plain callable does not allow. Their @phpstan-param and @phpstan-var now accept '' and null alongside a callable(): mixed, since the page and header hooks call the callback without arguments.
  • unregister_setting() narrows its deprecated third parameter, documented as callable with a default of '', to ''. This is deliberate, like the deprecated parameters in Docs: Narrow deprecated parameters to their default values in PHPStan types #13889: passing a callable still works at runtime but triggers _deprecated_argument(), so static analysis now flags it. The only core caller, remove_option_update_handler(), is itself deprecated and excluded from analysis.
  • register_activation_hook(), register_deactivation_hook(), register_uninstall_hook() and add_feed() document the signature core calls their callback with.

This empties parameter.defaultValue.neon. The baseline file is deleted, and so is its line in phpstan.neon.dist.

Changes from #13530

These follow the review analysis:

  • ''|callable becomes ''|callable|null. Plugins commonly pass null to add_menu_page() and the related functions, and the Customizer and _custom_header_background_just_in_time() pass null to the two constructors.
  • The callback signatures return mixed rather than void, so a callback that returns a value is still accepted.
  • The standalone @phpstan-return void tags on the activation and deactivation hooks are dropped.

Changes after review

  • Custom_Image_Header::__construct()'s $admin_header_callback now accepts '' and null as well. Before, only $admin_image_div_callback did, although core passes both values for both parameters.
  • The add_*_page() callbacks are typed ''|(callable(): mixed)|null, matching the Custom_* classes, instead of a bare callable.

Effect on the analysis

composer run phpstan passes, and the parameter.defaultValue baseline (17 entries) is removed. At level 10, compared against trunk, 4 errors are fixed and 4 are introduced. The 4 introduced errors are the same 4 calls, reworded: mixed is passed to the new constructor type. (These counts were measured before the changes after review.)

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 and from review on this PR, checking each subset with composer run phpstan (also at level 10, compared against trunk) and phpcs, and drafting the commit messages 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

The `add_*_page()` functions, and the `Custom_Background` and `Custom_Image_Header` constructors, default their callback to an empty string, which a plain `callable` does not allow. Their `@phpstan-param` now allows `''` and `null`, the latter because plugins and the Customizer commonly pass it. `unregister_setting()` narrows its deprecated third parameter to `''` in the same way.

The activation, deactivation, uninstall and feed callbacks gain the signature core calls them with.

This empties the `parameter.defaultValue` baseline, which is removed.

See #65817.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc
Copilot AI balanced review requested due to automatic review settings October 1, 2026 09:01
@github-actions

github-actions Bot commented Oct 1, 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.

Unlinked Accounts

The 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:

Props swissspidy, westonruter, marian1.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Two annotations currently reject callback values accepted and used by Core.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds precise PHPStan callback types and removes the resolved default-value baseline.

Changes:

  • Types optional callbacks and hook signatures.
  • Narrows unregister_setting()’s deprecated parameter.
  • Removes the emptied PHPStan baseline.
File Description
src/​wp-admin/​includes/​class-custom-background.php Types callback properties and parameters.
src/​wp-admin/​includes/​class-custom-image-header.php Types callback properties and constructor parameter.
src/​wp-admin/​includes/​plugin.php Types admin-page callbacks.
src/​wp-includes/​option.php Narrows the deprecated callback parameter.
src/​wp-includes/​plugin.php Types plugin lifecycle callbacks.
src/​wp-includes/​rewrite.php Types feed callbacks.
phpstan.neon.dist Removes the obsolete baseline include.
tests/​phpstan/​baselines/​parameter.defaultValue.neon Deletes the resolved baseline.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/wp-admin/includes/class-custom-image-header.php Outdated
Comment thread src/wp-includes/option.php
@github-actions

github-actions Bot commented Oct 1, 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.

Copy link
Copy Markdown
Member Author

🤖 Comment by Claude Opus 5.5

Props for this subset, gathered from the full history of functionMap.php in php-stubs/wordpress-stubs. A change counts only where it introduced or changed the specific tag this PR carries, not just any change to the function's map entry. Purely mechanical changes are left out: tag key renames (php-stubs/wordpress-stubs#420), parameter renames (php-stubs/wordpress-stubs#115), coding standards (php-stubs/wordpress-stubs#119) and the duplicate-code refactor (php-stubs/wordpress-stubs#85).

Stubs change Author Entries
php-stubs/wordpress-stubs#161 @lipemat add_submenu_page()
php-stubs/wordpress-stubs#289 @IanDelMar add_comments_page(), add_dashboard_page(), add_links_page(), add_management_page(), add_media_page(), add_menu_page(), add_options_page(), add_pages_page(), add_plugins_page(), add_posts_page(), add_theme_page(), add_users_page()
php-stubs/wordpress-stubs#396 @IanDelMar add_feed()
php-stubs/wordpress-stubs#401 @IanDelMar register_activation_hook(), register_deactivation_hook(), register_uninstall_hook()
php-stubs/wordpress-stubs#413 @IanDelMar Custom_Background::$admin_header_callback, Custom_Background::$admin_image_div_callback, Custom_Background::__construct(), Custom_Image_Header::$admin_header_callback, Custom_Image_Header::$admin_image_div_callback, Custom_Image_Header::__construct()
php-stubs/wordpress-stubs#436 @IanDelMar unregister_setting()

Props for this PR (swissspidy for the split, westonruter for the review that shaped it):

swissspidy, westonruter, marian1, mat-lipe

Generated by Claude Code

claude and others added 3 commits October 5, 2026 15:46
Allow null and an empty string for the $admin_header_callback parameter of Custom_Image_Header::__construct(), since the Customizer and the theme-support defaults pass them. Also type the admin page callbacks as callable(): mixed, matching Custom_Image_Header and Custom_Background, since the page hook is fired without arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BuoHZQvJMvPEYp4Sfjgvd

@westonruter westonruter left a comment

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.

🤖 Comment by Claude Opus 5.5

All of the findings below are PHPDoc-only; none changes runtime behavior, and none is considered blocking. The new callback signatures were traced against how core invokes each callback (activation/deactivation pass a bool, the feed callback passes bool and string, the rest pass nothing), and they all match. The deleted baseline is not referenced anywhere else.

One finding concerns a file outside the diff, so it is noted here rather than inline:

get_theme_support() is the root cause of new mixed errors. The newly typed Custom_Image_Header and Custom_Background constructors now receive mixed from $args[0]['admin-head-callback'] / $args[0]['admin-preview-callback'] in _custom_header_background_just_in_time() (src/wp-includes/theme.php lines 2977 and 2990), and similarly in the Customizer (WP_Customize_Header_Image_Setting). These surface as new level-10 errors. Typing the custom-header/custom-background support args once at the source (e.g. a typed local in _custom_header_background_just_in_time(), or a return shape for those branches of get_theme_support()) would clear them all, rather than leaving every typed consumer to deal with mixed.

Comment thread src/wp-includes/plugin.php Outdated
* @param callable $callback The callback to run when the hook is called. Must be
* a static method or function.
*
* @phpstan-param callable(): mixed $callback

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.

🤖 Comment by Claude Opus 5.5

callable(): mixed is looser than what this function actually accepts. It admits closures, invokable objects, and [$object, 'method'] arrays, but only a function name or a static [ClassName, 'method'] pair works:

  • register_uninstall_hook( __FILE__, function () { … } ) is accepted by PHPStan, but the Closure is passed to update_option( 'uninstall_plugins', … ), where serialization throws "Serialization of 'Closure' is not allowed".
  • register_uninstall_hook( __FILE__, array( $this, 'uninstall' ) ) is accepted by PHPStan, but hits _doing_it_wrong() and is silently not registered.

Something along the lines of (callable-string|array{class-string, non-empty-string})&(callable(): mixed) would match the docblock's "Must be a static method or function" and catch both cases statically.

* @param string $option_name The name of the option to unregister.
* @param callable $deprecated Optional. Deprecated.
*
* @phpstan-param '' $deprecated

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.

🤖 Comment by Claude Opus 5.5

Narrowing $deprecated to '' contradicts the function body, which still handles a non-empty value: if ( '' !== $deprecated ) → _deprecated_argument() → remove_filter( "sanitize_option_{$option_name}", $deprecated ). Under this type that whole branch is dead code, and $deprecated is narrowed to never inside it.

PHPStan stays quiet today only because treatPhpDocTypesAsCertain: false is set in tests/phpstan/base.neon. Meanwhile, a third-party caller still passing a sanitize callback (which continues to work at runtime, with a deprecation notice) would now get an argument.type error. Perhaps ''|callable would better reflect the actual contract, with the deprecation conveyed by the description.

* @since 3.0.0
* @var callable
*
* @phpstan-var ''|(callable(): mixed)|null

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.

🤖 Comment by Claude Opus 5.5

The @phpstan-var / @phpstan-param tags now allow ''|(callable(): mixed)|null, but the plain @var callable / @param callable tags alongside them still say callable only. So the documented type remains wrong for the default '' and for the null that core passes. IDEs and the developer reference read the non-prefixed tags. Updating them to callable|string|null (here and on $admin_image_div_callback and the constructor params) would keep both views consistent.

* @param callable $admin_image_div_callback Optional. Custom image div output callback.
* Default empty string.
*
* @phpstan-param ''|(callable(): mixed)|null $admin_header_callback

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.

🤖 Comment by Claude Opus 5.5

Same mismatch as in Custom_Background: $admin_header_callback now accepts '' and null via @phpstan-param, but the plain @param callable $admin_header_callback still reads "Administration header callback." with no indication that an empty string or null is allowed — even though core passes '' (the custom-header theme-support defaults) and null (WP_Customize_Header_Image_Setting::update()). The @var on the property has the same issue.

Comment thread src/wp-admin/includes/plugin.php Outdated
* @param int|float $position Optional. The position in the menu order this item should appear.
* @return string The resulting page's hook_suffix.
*
* @phpstan-param ''|(callable(): mixed)|null $callback

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.

🤖 Comment by Claude Opus 5.5

Nit: this same ''|(callable(): mixed)|null union is repeated across 13 add_*_page() docblocks, plus four more tags in the two Custom_* classes. If the accepted shape ever changes (e.g. dropping '' or tightening the return to void), all 17 sites must be edited in lockstep, and missing one silently yields inconsistent typing between add_submenu_page() and its wrappers. A shared @phpstan-type alias could avoid that, though it may not be worth the indirection for docblocks that are otherwise self-contained.

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 could be a global alias added in typeAliases located in base.neon.

Comment thread src/wp-includes/rewrite.php Outdated
swissspidy and others added 2 commits October 7, 2026 08:47
Co-authored-by: Weston Ruter <westonruter@gmail.com>
- Add an `Optional_Callback` type alias to `base.neon` and use it for the
  `add_*_page()` callbacks and the `Custom_Background` and
  `Custom_Image_Header` callbacks, instead of repeating the union.
- Document those `Custom_*` callbacks as `callable|string|null` in the plain
  `@var` and `@param` tags too, so IDEs and the developer reference see the
  empty string and `null` that core passes.
- Type `unregister_setting()`'s deprecated `$deprecated` parameter as
  `''|callable`, since the function still handles a callable.
- Restrict `register_uninstall_hook()`'s callback to a function name or a
  static `[ClassName, 'method']` pair, as the docblock requires. Closures
  cannot be serialized into the `uninstall_plugins` option, and instance
  methods are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BL6dYS5ydouQDBQmjem93A
@swissspidy
swissspidy requested a review from westonruter October 7, 2026 13:12
* Default empty string.
* @param callable $admin_image_div_callback Optional. Custom image div output callback.
* Default empty string.
* @param callable|string|null $admin_header_callback Optional. Administration header callback.

@westonruter westonruter Oct 7, 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.

✅ I understand that string is needed here to account for the empty string case. Otherwise, callable|null would be sufficient since that includes callable-string.

Comment thread src/wp-includes/option.php Outdated
* @param string $option_name The name of the option to unregister.
* @param callable $deprecated Optional. Deprecated.
*
* @phpstan-param ''|callable $deprecated

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.

Why not just:

Suggested change
* @phpstan-param ''|callable $deprecated
* @phpstan-param '' $deprecated

Otherwise, a caller supplying anything but '' won't get flagged by PHPStan.

See also trackback_url():

* @phpstan-ignore conditionalType.alwaysTrue (Typed `true` to flag the deprecated argument.)
*/
function trackback_url( $deprecated_echo = true ) {
if ( true !== $deprecated_echo ) {
_deprecated_argument(

Discussed in #13371 (section)

I'm curious why PHPStan doesn't flag if ( '' !== $deprecated ) as being an error if the type is declared as such, when it did for trackback_url().

@westonruter westonruter left a comment

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.

🤖 Comment by Claude Opus 5.5

Second-round review at 8b7e501. No correctness issues were found; the changes remain PHPDoc-only, PHPCS is clean, and PHPStan reports no new errors on changed lines relative to trunk.

Status of the earlier review's findings

  • ✅ register_uninstall_hook(): addressed. A probe at level 10 confirms Closures, array( $this, … ) and nonexistent methods are now rejected, while 'Class::method', array( self::class, … ) and function names are accepted.
  • ✅ Plain tags on Custom_Background: addressed.
  • ✅ Custom_Image_Header allowing null: addressed, including the description.
  • ✅ Repeated union across 17 sites: addressed via the Optional_Callback alias.
  • ✅ Feed-name narrowing to non-empty-string: applied, and it holds. do_feed() only fires do_feed_{$feed} when that hook exists, so the value passed is the registered $feedname, which can no longer be ''.
  • ⏳ get_theme_support() root cause: still open (see below).
  • ↩️ unregister_setting(): the earlier suggestion of ''|callable is superseded; see the inline comment.

Corrections to the earlier review

  • The ''|callable suggestion for unregister_setting()'s $deprecated was the weaker option. Plain '' is preferable (inline comment below).
  • The theme.php errors were described as new level-10 errors. They are pre-existing expects callable, mixed given errors, now reworded to mention the narrower type, so they are not introduced by this PR and do not block it.

Still open (outside the diff): get_theme_support() root cause

Level 10 still reports mixed being passed to the Custom_Image_Header constructor at src/wp-includes/theme.php line 2977, to Custom_Background at line 2990, and to Custom_Image_Header at src/wp-includes/customize/class-wp-customize-header-image-setting.php line 58. A typed local for the custom-header / custom-background support args in _custom_header_background_just_in_time() would clear the theme.php ones. This could just as well be a follow-up.

Comment thread src/wp-includes/option.php Outdated
* @param string $option_name The name of the option to unregister.
* @param callable $deprecated Optional. Deprecated.
*
* @phpstan-param ''|callable $deprecated

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.

🤖 Comment by Claude Opus 5.5

The '' suggestion in #13890 (comment) is preferable to ''|callable, and it supersedes the ''|callable suggestion made in the earlier review. With ''|callable, a caller still passing the sanitize callback (deprecated since 4.7.0) is never flagged, whereas r64163 established the convention of narrowing deprecated parameters to their default even where the function body still handles other values (e.g. the_author()).

The Copilot objection does not apply: remove_option_update_handler() forwards a callable, but it lives in src/wp-admin/includes/deprecated.php, which tests/phpstan/base.neon excludes from analysis, so narrowing to '' produces no error in core.

One small correction to the premise of the question: trackback_url()'s true !== $deprecated_echo comparison was not what got flagged there; it was conditionalType.alwaysTrue on its @phpstan-return. The '' !== $deprecated comparison here stays quiet because treatPhpDocTypesAsCertain: false is set.

If narrowed to '', the plain @param callable $deprecated could become @param string $deprecated to match.

Comment thread src/wp-admin/includes/plugin.php Outdated
@@ -1494,6 +1496,8 @@ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $call
* @param callable $callback Optional. The function to be called to output the content for this page.

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.

🤖 Comment by Claude Opus 5.5

The plain @param callable $callback tags here, on add_menu_page(), and on the 11 add_*_page() wrappers were left as callable, whereas the corresponding plain tags in Custom_Background and Custom_Image_Header were widened to callable|string|null. For consistency (IDEs and the developer reference read the plain tags), these could get the same treatment, since the default is '' and Optional_Callback now admits null.

* Default empty string.
* @param callable $admin_image_div_callback Optional. Custom image div output callback.
* Default empty string.
* @param callable|string|null $admin_header_callback Optional. Administration header callback.

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.

🤖 Comment by Claude Opus 5.5

Nit: Custom_Image_Header's constructor descriptions now say "Empty string or null for none.", but these still only say "Default empty string." even though the type permits null. Aligning the wording would make the shared Optional_Callback contract read the same in both sibling classes.

Comment thread tests/phpstan/base.neon
# An optional callback that is called without arguments, such as an admin page or
# custom header callback. Core treats an empty string or null as no callback.
Optional_Callback: '''
''|(callable(): mixed)|null

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.

🤖 Comment by Claude Opus 5.5

Optional_Callback rejects false, although the consumers guard with ! empty( $callback ) (e.g. add_menu_page() and add_submenu_page()) or truthiness checks, so false works at runtime as "no callback". For example, add_menu_page( 'Title', 'Menu', 'manage_options', 'slug', false ) is now reported as argument.type, even at the CI level in phpstan.neon.dist. That may well be the desired outcome (steering callers to ''), but if not, false could be added to the alias. Either way, it seems worth a deliberate decision.

Comment thread tests/phpstan/base.neon
# An optional callback that is called without arguments, such as an admin page or
# custom header callback. Core treats an empty string or null as no callback.
Optional_Callback: '''
''|(callable(): mixed)|null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@swissspidy May I ask why Optional_Callback is being introduced? This requires every developer who scans core directly or uses a stubs file to also define Optional_Callback or include WordPress's entire base.neon file.
Developers who do not regularly work with WordPress would also need to look up what it means - and first figure out where to find its definition. Is saving ten characters worth that additional complexity?

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 was my suggestion in #13890 (comment)

Given that PHPStan doesn't yet allow top-level @phpstan-type (until phpstan/phpstan#9164) the global typeAliases allow us to start using types in more places where previously we'd have to duplicate them every time.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How often is it duplicated, and how often is it expected to need an update? How many developers will now have to define it themselves? This essentially shifts the duplication to others. Perhaps this is worth reconsidering.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Also, why not use the WP_ prefix to avoid collisions with type aliases defined elsewhere? I do not expect many people to use this particular alias, but once this approach is introduced, it may encourage aliases for all sorts of relatively simple unions.

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.

How often is it duplicated

There are 13 duplications here.

How many developers will now have to define it themselves?

Could wordpress-stubs undo the alias when reading from core, replacing Optional_Callback with whatever is in base.neon? Then there would be no difference for developers.

Otherwise, if they are looking to use the types in their own code, either they duplicate the ''|(callable(): mixed)|null type or they add add their own alias.

Or maybe phpstan-wordpress could include all of these aliases for plugins to re-use.

Also, why not use the WP_ prefix to avoid collisions with type aliases defined elsewhere?

That probably makes sense. I think the prefix was omitted because we weren't using them on the @phpstan-type aliases on classes, but naturally this is redundant because it's already namespaced by the class.

ack '@phpstan-type' src/wp-includes/ src/wp-admin/
src/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php
16: * @phpstan-type FunctionCallArrayShape array{id?: string, name?: string, args?: mixed}

src/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php
15: * @phpstan-type FunctionDeclarationArrayShape array{

src/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php
16: * @phpstan-type FunctionResponseArrayShape array{id?: string, name?: string, response: mixed}

src/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php
15: * @phpstan-type WebSearchArrayShape array{allowedDomains?: string[], disallowedDomains?: string[]}

src/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php
26: * @phpstan-type MessagePartArrayShape array{

src/wp-includes/php-ai-client/src/Messages/DTO/Message.php
19: * @phpstan-type MessageArrayShape array{

src/wp-includes/php-ai-client/src/Builders/MessageBuilder.php
23: * @phpstan-type Input string|MessagePart|MessagePartArrayShape|File|FunctionCall|FunctionResponse|null

src/wp-includes/php-ai-client/src/Builders/PromptBuilder.php
48: * @phpstan-type Prompt string|MessagePart|Message|MessageArrayShape|list<string|MessagePart|MessagePartArrayShape>|list<Message>|null

src/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php
20: * @phpstan-type ProviderModelsMetadataArrayShape array{

src/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php
20: * @phpstan-type ProviderMetadataArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php
22: * @phpstan-type ModelRequirementsArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php
16: * @phpstan-type RequiredOptionArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php
25: * @phpstan-type ModelConfigArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php
18: * @phpstan-type SupportedOptionArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php
19: * @phpstan-type ModelMetadataArrayShape array{

src/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php
33: * @phpstan-type ImageGenerationParams array{
42: * @phpstan-type ChoiceData array{
46: * @phpstan-type UsageData array{
51: * @phpstan-type ResponseData array{

src/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php
35: * @phpstan-type ToolCallData array{
43: * @phpstan-type MessageData array{
49: * @phpstan-type ChoiceData array{
53: * @phpstan-type UsageData array{
58: * @phpstan-type ResponseData array{

src/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php
13: * @phpstan-type ApiKeyRequestAuthenticationArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php
17: * @phpstan-type ResponseArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php
15: * @phpstan-type RequestOptionsArrayShape array{

src/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php
21: * @phpstan-type RequestArrayShape array{

src/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php
20: * @phpstan-type GenerativeAiOperationArrayShape array{id: string, state: string, result?: GenerativeAiResultArrayShape}

src/wp-includes/php-ai-client/src/Results/DTO/Candidate.php
20: * @phpstan-type CandidateArrayShape array{message: MessageArrayShape, finishReason: string}

src/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php
18: * @phpstan-type TokenUsageArrayShape array{

src/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php
27: * @phpstan-type GenerativeAiResultArrayShape array{

src/wp-includes/php-ai-client/src/Files/DTO/File.php
19: * @phpstan-type FileArrayShape array{

src/wp-includes/class-wp-script-modules.php
16: * @phpstan-type ScriptModule array{

src/wp-includes/class-wp-block-supports.php
17: * @phpstan-type ApplyCallback callable( WP_Block_Type, array<string, mixed> ): array<string, mixed>
18: * @phpstan-type RegisterCallback callable( WP_Block_Type ): void

src/wp-includes/class-wp-theme.php
9: * @phpstan-type Theme_Key 'Name'|'Version'|'Status'|'Title'|'Author'|'Author Name'|'Author URI'|'Description'|'Template'|'Stylesheet'|'Template Files'|'Stylesheet Files'|'Template Dir'|'Stylesheet Dir'|'Screenshot'|'Tags'|'Theme Root'|'Theme Root URI'|'Parent Theme'

src/wp-includes/class-wp-comment.php
41: * @phpstan-type Data_Array array{

src/wp-includes/class-wp-post.php
21: * @phpstan-type Data_Array array{

src/wp-includes/class-wp-hook.php
18: * @phpstan-type Hook_Callback array{

src/wp-includes/class-wp-connector-registry.php
30: * @phpstan-type Connector array{

src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
17: * @phpstan-type Image_Sub_Size array{

src/wp-includes/rest-api/class-wp-rest-server.php
23: * @phpstan-type Endpoint_Arg array{
31: * @phpstan-type Route_Handler array{

src/wp-includes/customize/class-wp-customize-header-image-setting.php
19: * @phpstan-type Header_Image_Data array{

src/wp-admin/includes/class-wp-filesystem-ssh2.php
36: * @phpstan-type Options array{

src/wp-admin/includes/class-wp-filesystem-base.php
14: * @phpstan-type FileListing array{

src/wp-admin/includes/class-wp-filesystem-ftpsockets.php
15: * @phpstan-type Options array{

src/wp-admin/includes/class-wp-filesystem-ftpext.php
15: * @phpstan-type Options array{

So we should rename Maybe_Callable to WP_Maybe_Callable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could wordpress-stubs undo the alias when reading from core, replacing Optional_Callback with whatever is in base.neon? Then there would be no difference for developers.

Yes, wordpress-stubs could expand the alias and phpstan-wordpress could include its definition. Both would require additional ongoing maintenance, though, and neither would help developers who use core directly without these packages.

Type aliases declared with @phpstan-type are local anyways. Type aliases declared in the config are global and can collide with other type aliases.

I still do not see why avoiding a few repetitions of simple types justifies shifting that burden to downstream maintainers and developers.

claude added 2 commits October 8, 2026 07:31
# Conflicts:
#	src/wp-admin/includes/plugin.php
#	src/wp-includes/rewrite.php
- Rename the `Optional_Callback` type alias to `WP_Optional_Callback` to avoid collisions with aliases defined elsewhere.
- Widen the plain `@param` tags for the `add_*_page()` callbacks to `callable|string|null`, like the `Custom_*` classes.
- Note that an empty string or null means no callback in the `Custom_Background` and `Custom_Image_Header` constructor descriptions.
- Narrow `unregister_setting()`'s deprecated parameter to `''`, and document it as a string.

See #65817.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH
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.

5 participants