Repository navigation
Docs: Add PHPStan purity annotations - #14095
swissspidy wants to merge 4 commits into
Conversation
Mark functions whose result depends only on their arguments as `@phpstan-pure`: `add_cssclass()`, `backslashit()`, `trailingslashit()`, `untrailingslashit()`, `wp_slash()`, `bool_from_yn()`, `wp_is_numeric_array()`, `rest_sanitize_boolean()` and `get_shortcode_atts_regex()`. PHPStan then verifies that these functions have no side effects, and reports calls whose result is unused. It also remembers their return values when `rememberPossiblyImpureFunctionValues` is disabled. See #65817. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH
|
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. |
Add `@phpstan-pure` to 55 more functions whose result depends only on their arguments and which core calls directly, such as `absint()`, `wp_basename()`, `_wp_array_get()`, `wp_array_slice_assoc()`, `_n_noop()` and `wp_validate_boolean()`. The candidates were found by marking every documented function as pure and removing the tag wherever PHPStan reported a side effect, until none remained. The result was then filtered further to drop any function that reads globals or object state, calls methods or hooks, depends on time or randomness, or performs I/O, directly or through another WordPress function. Callback-only functions, pluggable and drop-in replaceable functions, deprecated functions, polyfills and generated block files are left out. See #65817. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH
|
@swissspidy Can you ask Claude to check, whether these are included? Pure functions
Pure functions excluded on purpose
Pure functions already included
|
I collected impure points and PHPStan said |
Add `@phpstan-pure` to `wp_parse_list()`, `wp_parse_id_list()`, `is_php_version_compatible()`, `_get_wptexturize_shortcode_regex()`, `remove_serialized_parent_block()`, `extract_serialized_parent_block()`, `get_query_pagination_arrow()` and `get_comments_pagination_arrow()`. These were missed before because the filter used to pick candidates also rejected property reads on parameters, `array_map()` and `array_filter()` with pure callbacks, and `version_compare()`. See #65817. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH
|
Most of these I left out on purpose:
|
|
Concerning |
|
Technically, I updated the list. Added new entries, removed entries already handled by this PR and removed entries that were not included in this PR on purpose. The removed functions are now listed under "Pure functions excluded on purpose" and "Pure functions already included" |
Add `@phpstan-pure` to `make_before_block_visitor()`, `make_after_block_visitor()` and `wp_caption_input_textarea()`. Creating and returning a closure has no side effects; those only happen when the closure is called, and PHPStan already treats calling the returned `callable` as possibly impure. See #65817. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH
|
Another purity annotation is landing in #14037. I left #14037 (comment) there as well:
|
Disagree. Without annotations PHPStan cannot check purity against your expectations. As the PR description notes, functions may silently become impure. See PHPStan annotations also provide information to anyone reading the source. Automatically inferred information is not visible to those developers. So, choosing not to add the annotations is the equivalent of deciding that this information is not relevant to developers reading the source. Building an index or map solely from PHPStan's own analysis is redundant if its purpose is to feed that information back into PHPStan: it already knows it. See |
@swissspidy As of PHPStan 2.3 (I haven’t checked which patch version introduced this), PHPStan treats Can Edit: |
Split out of #13896, which in turn was split out of #13530 (subset 9).
@phpstan-pureis added to 75 functions whose result depends only on their arguments.From #13896:
add_cssclass(),backslashit(),trailingslashit(),untrailingslashit(),wp_slash(),bool_from_yn(),wp_is_numeric_array(),rest_sanitize_boolean()andget_shortcode_atts_regex().New in this PR (66 functions that core calls directly):
absint(),wp_basename(),_wp_array_get(),wp_array_slice_assoc(),wp_parse_list(),wp_parse_id_list(),zeroise(),wp_validate_boolean(),is_php_version_compatible(),_n_noop(),_nx_noop(),before_last_bar(),add_magic_quotes(),url_shorten(),wp_fuzzy_number_match(),plugin_dir_path(),translate_level_to_role(),calendar_week_mod(),default_topic_count_scale()_split_str_by_whitespace(),_wp_emoji_list(),_get_wptexturize_shortcode_regex(),unescape_invalid_shortcodes(),wp_kses_array_lc(),valid_unicode(),wp_kses_version(),wp_js_dataset_name(),wp_html_custom_data_attribute_name()rest_default_additional_properties_to_false(),rest_is_boolean(),rest_get_allowed_schema_keywords(),_rest_array_intersect_key_recursive(),_wp_prepare_json_schema_for_client_with_allowed_keywords()_get_component_from_parsed_url_array(),_wp_translate_php_url_constant_to_key()image_hwstring(),_wp_get_image_size_from_meta(),wp_sizes_attribute_includes_valid_auto(),wp_is_heic_image_mime_type(),wp_exif_frac2dec(),_image_get_preview_ratio(),wp_caption_input_textarea()wp_get_first_block(),wp_normalize_remote_block_pattern(),remove_serialized_parent_block(),extract_serialized_parent_block(),make_before_block_visitor(),make_after_block_visitor(),get_query_pagination_arrow(),get_comments_pagination_arrow(),wp_get_layout_definitions(),wp_get_layout_child_values(),wp_get_layout_container_values(),wp_get_root_state_style(),wp_get_state_declarations_with_fallback_border_styles(),wp_get_state_declarations_with_background_resets(),wp_get_state_style_with_fallback_dimension_styles(),wp_is_explicit_aspect_ratio_value(),wp_should_add_elements_class_name(),wp_get_entity_view_config_hook_name()_wp_get_abilities_match_meta(),_wp_connectors_mask_api_key(),wp_connectors_parse_application_password_credentials(),_wp_reset_invalid_menu_item_parent(),_wp_privacy_action_request_types(),wp_make_theme_file_tree()make_before_block_visitor()andmake_after_block_visitor()return closures that have side effects, but creating a closure has none. PHPStan already treats calling the returnedcallableas possibly impure.Why
@phpstan-pureis still usefulBy default (
rememberPossiblyImpureFunctionValues: true), PHPStan already remembers the return values of functions not marked impure, so@phpstan-puredoesn't change that (see Remembering and forgetting returned values). This is why it was dropped from #13896. The tag still has two effects at the level 5 that core runs:impure.*), so these functions can't silently become impure later.function.resultUnused), because such a call has no effect.For anyone analyzing with
rememberPossiblyImpureFunctionValues: false, it also lets PHPStan keep remembering these functions' return values.How the functions were chosen
wp-includesandwp-admin/includeswas marked@phpstan-pure. PHPStan was then run repeatedly, removing the tag from every function it reported as impure or as called for its side effects, until nothing changed.get_the_ID()survived even though it callsget_post(), which PHPStan does report as possibly impure whenget_the_ID()alone is tagged. So the remaining functions were filtered further, dropping any function that, directly or through another WordPress function, does any of the following:composer run phpstanon their own, without the mass tagging, so each function here is accepted by PHPStan's normal analysis.array_filter()callbacks, such as__return_true(),_wp_post_thumbnail_class_filter()and_is_valid_nav_menu_item(), where the tag only guards the body.pluggable.php, the deprecated files and the compat polyfills, since they can be overridden.cache.phpfunctions, since object-cache drop-ins replace them with versions that have side effects.blocks/andbuild/files, and bundled libraries.preg_match(),preg_replace()or similar, such assanitize_hex_color(),normalize_whitespace()andwp_kses_no_null(). PHPStan treats these PCRE functions as possibly impure, since they updatepreg_last_error(), so it rejects the tag._deep_replace()is rejected for the same reason, since it usesstr_replace()'s by-reference$count.block_version(), which callshas_blocks()and so can fall back toget_post()for non-string input. Making it string-only would be a code change, better done separately.wp_clone(), sincecloneruns__clone().@phpstan-impureis not part of this PR. As discussed in #13896, which functions to mark impure needs agreement first.Effect on the analysis
composer run phpstanpasses with no baseline changes, and no call site in core is reported asfunction.resultUnused.Trac ticket: https://core.trac.wordpress.org/ticket/65817
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Used for: Splitting the
@phpstan-pureannotations out of #13896, finding further pure functions with PHPStan and a stricter body filter, comparing the result with the lists from review, checking the result withcomposer run phpstan, 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_01T3XYNUvB9sQNDXA14uJQeH