From 2b3273d1bc48ccef6f4270f7cb36dfb0caf3b95b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 14:12:54 -0700 Subject: [PATCH 1/2] Code Quality: Remove the global PHPStan type aliases The `typeAliases` in `tests/phpstan/base.neon` were global to core's own analysis, but the docblocks in `src/` that used them are read elsewhere too. The stubs generated from core keep the references and lose the definitions, so anyone who analyzes against core, or against those stubs, gets an unknown type where each alias was used, unless they define it themselves. Inline the `Maybe_Callable` union at its uses, which are mostly functions, where a class alias cannot be imported. Declare `Block_Style_Properties` with `@phpstan-type` on `WP_Block_Styles_Registry`, which the stubs keep, and import it into `WP_REST_Block_Types_Controller`. Leave a note in place of the aliases, so that new ones are declared on classes too. Co-Authored-By: Claude Opus 5.5 --- .../class-wp-block-styles-registry.php | 16 ++++++++++ src/wp-includes/class-wp-hook.php | 4 +-- src/wp-includes/plugin.php | 10 +++---- .../class-wp-rest-block-types-controller.php | 2 ++ tests/phpstan/base.neon | 30 +++---------------- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/wp-includes/class-wp-block-styles-registry.php b/src/wp-includes/class-wp-block-styles-registry.php index 46ebc0c5a3b83..0afb410970003 100644 --- a/src/wp-includes/class-wp-block-styles-registry.php +++ b/src/wp-includes/class-wp-block-styles-registry.php @@ -10,7 +10,23 @@ /** * Class used for interacting with block styles. * + * The properties of a block style as the registry stores them are more than its register() hash + * accepts: `name` is required for a style to be registered at all, and `label` is filled in from + * the name when the caller omits it, so both are always present on the way back out. The shape + * stays open because the array is stored verbatim, so a caller's own keys survive, including + * into the REST response, which does not set `additionalProperties` to false. + * * @since 5.3.0 + * + * @phpstan-type Block_Style_Properties array{ + * name: string, + * label: string, + * inline_style?: string, + * style_handle?: string, + * is_default?: bool, + * style_data?: array, + * ... + * } */ #[AllowDynamicProperties] final class WP_Block_Styles_Registry { diff --git a/src/wp-includes/class-wp-hook.php b/src/wp-includes/class-wp-hook.php index 9a4d4a2c5eeca..e1ead15a8d213 100644 --- a/src/wp-includes/class-wp-hook.php +++ b/src/wp-includes/class-wp-hook.php @@ -201,7 +201,7 @@ private function resort_active_iterations( $new_priority = false, $priority_exis * a callback that may or may not exist. * @param int $priority The exact priority used when adding the original filter callback. * @return bool Whether the callback existed before it was removed. - * @phpstan-param Maybe_Callable $callback + * @phpstan-param callable|string|array{0: string|object, 1: string} $callback */ public function remove_filter( $hook_name, $callback, $priority ) { if ( null === $priority ) { @@ -249,7 +249,7 @@ public function remove_filter( $hook_name, $callback, $priority ) { * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. - * @phpstan-param Maybe_Callable|false $callback + * @phpstan-param callable|string|array{0: string|object, 1: string}|false $callback * @phpstan-return ( * $callback is false * ? bool diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index a3e9afacdb8dc..f09f6c264d5a1 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -284,7 +284,7 @@ function apply_filters_ref_array( $hook_name, $args ) { * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. - * @phpstan-param Maybe_Callable|false $callback + * @phpstan-param callable|string|array{0: string|object, 1: string}|false $callback * @phpstan-return ( * $callback is false * ? bool @@ -324,7 +324,7 @@ function has_filter( $hook_name, $callback = false, $priority = false ) { * @param int $priority Optional. The exact priority used when adding the original * filter callback. Default 10. * @return bool Whether the function existed before it was removed. - * @phpstan-param Maybe_Callable $callback + * @phpstan-param callable|string|array{0: string|object, 1: string} $callback */ function remove_filter( $hook_name, $callback, $priority = 10 ) { global $wp_filter; @@ -606,7 +606,7 @@ function do_action_ref_array( $hook_name, $args ) { * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. - * @phpstan-param Maybe_Callable|false $callback + * @phpstan-param callable|string|array{0: string|object, 1: string}|false $callback * @phpstan-return ( * $callback is false * ? bool @@ -638,7 +638,7 @@ function has_action( $hook_name, $callback = false, $priority = false ) { * @param int $priority Optional. The exact priority used when adding the original * action callback. Default 10. * @return bool Whether the function is removed. - * @phpstan-param Maybe_Callable $callback + * @phpstan-param callable|string|array{0: string|object, 1: string} $callback */ function remove_action( $hook_name, $callback, $priority = 10 ) { return remove_filter( $hook_name, $callback, $priority ); @@ -1019,7 +1019,7 @@ function _wp_call_all_hook( $args ) { * @param int $priority Unused. The order in which the functions * associated with a particular action are executed. * @return string|null Unique function ID for usage as array key, or null if it couldn't be determined. - * @phpstan-param Maybe_Callable $callback + * @phpstan-param callable|string|array{0: string|object, 1: string} $callback * @phpstan-return non-decimal-int-string|null */ function _wp_filter_build_unique_id( $hook_name, $callback, $priority ): ?string { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php index fa2dfd10d3602..a6b3192f52d42 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php @@ -13,6 +13,8 @@ * @since 5.5.0 * * @see WP_REST_Controller + * + * @phpstan-import-type Block_Style_Properties from WP_Block_Styles_Registry */ class WP_REST_Block_Types_Controller extends WP_REST_Controller { diff --git a/tests/phpstan/base.neon b/tests/phpstan/base.neon index 04e13eb2fb6cb..9871319cf14c0 100644 --- a/tests/phpstan/base.neon +++ b/tests/phpstan/base.neon @@ -351,30 +351,8 @@ parameters: - ../../src/wp-includes/pomo - ../../src/wp-includes/rss.php - ../../src/wp-includes/sodium_compat - typeAliases: - # A callable that may not be defined in the current scope. - Maybe_Callable: ''' - callable|string|array{ - 0: string|object, - 1: string, - } - ''' - # The properties of a block style as WP_Block_Styles_Registry stores them, which is - # more than its register() hash accepts: `name` is required for a style to be - # registered at all, and `label` is filled in from the name when the caller omits - # it, so both are always present on the way back out. The shape stays open because - # the array is stored verbatim, so a caller's own keys survive — including into the - # REST response, which does not set `additionalProperties` to false. Read from - # procedural files as well as classes, which is why it is declared here. - Block_Style_Properties: ''' - array{ - name: string, - label: string, - inline_style?: string, - style_handle?: string, - is_default?: bool, - style_data?: array, - ... - } - ''' + # No global `typeAliases` are defined here, since docblocks in src/ that use them are not + # self-contained: the aliases are lost from the stubs generated from core, and anyone who + # analyzes against core would have to define each one. Declare an alias with `@phpstan-type` + # on a class, and import it with `@phpstan-import-type` into other classes, instead. From 711fd1394d97a41246c036aad85326fccba3807a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 14:21:02 -0700 Subject: [PATCH 2/2] Code Quality: Point to the PHPStan issue for aliases outside classes The note in place of the global type aliases says to declare an alias on a class, which leaves a type used by functions to be repeated. Say that this lasts until PHPStan supports declaring an alias with `@phpstan-type` outside a class, and link the issue that tracks it. Co-Authored-By: Claude Opus 5.5 --- tests/phpstan/base.neon | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpstan/base.neon b/tests/phpstan/base.neon index 9871319cf14c0..9bd0169c80ae1 100644 --- a/tests/phpstan/base.neon +++ b/tests/phpstan/base.neon @@ -355,4 +355,6 @@ parameters: # No global `typeAliases` are defined here, since docblocks in src/ that use them are not # self-contained: the aliases are lost from the stubs generated from core, and anyone who # analyzes against core would have to define each one. Declare an alias with `@phpstan-type` - # on a class, and import it with `@phpstan-import-type` into other classes, instead. + # on a class, and import it with `@phpstan-import-type` into other classes, instead. A type + # used by functions has to be repeated until PHPStan supports declaring an alias with + # `@phpstan-type` outside a class: https://github.com/phpstan/phpstan/issues/9164