Mark array_filter(), array_map(), array_reduce() and sibling non-mutating callback functions as having no side effects#5912
Conversation
…n-mutating callback functions as having no side effects - Add `hasSideEffects => false` entries for `array_filter`, `array_map` and `array_reduce` to `bin/functionMetadata_original.php` and the generated `resources/functionMetadata.php`. These functions do not mutate their arguments and have no side effects of their own; any side effects come from the supplied callback, whose impure points already propagate to the caller because callable parameters of functions are treated as immediately invoked. - Sweep the rest of the non-mutating array callback family that had the same problem: `array_find`, `array_find_key`, `array_any`, `array_all`. - Also mark the analogous `preg_replace_callback` and `preg_replace_callback_array` as side-effect-free. - An impure callback (e.g. one that echoes) still suppresses the "no effect" report because its impure points are merged into the statement; only genuinely pure callbacks make the whole call a no-op. - Update `tests/.../data/file-without-errors.php` and `tests/.../data/discussion-7124.php` so they use the result of the now-pure calls instead of relying on the previous (buggy) behavior where these calls were never reported. - Considered but deliberately excluded `call_user_func`/`call_user_func_array`: they are the general-purpose invocation primitive rather than part of the array-callback family; the propagation machinery would make them safe, but the behavior change is broader than this issue.
staabm
left a comment
There was a problem hiding this comment.
needs a test for https://phpstan.org/r/75ed18fc-365f-4cbd-9089-0395dff17166
staabm
left a comment
There was a problem hiding this comment.
also add a test for phpstan/phpstan#11100
|
@VincentLanglet I think your suggestion in #3482 (comment) stays correct. I think for immediate-invoked-callables we can support the purity for these builtin functions without the need for |
…lback Closes phpstan/phpstan#11100 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng no effect Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Both review requests are addressed and pushed. SummaryThe reviewer (staabm) requested tests for two scenarios. I fetched both playground links via the PHPStan playground API to get the exact reproducers, then added validated regression tests for each. 1. Issue #11100 — a
2. Current review playground (
Verification:
Committed as two separate logical commits and pushed. Commit #1 includes |
0789c96 to
cf7ee51
Compare
|
Done. Here's a summary of how I addressed staabm's review. Both review requests are now coveredI fetched both playground samples via the PHPStan playground API (
I wrote regression tests for both and verified each fails before the metadata fix (showing exactly the issue's error / missing error) and passes after:
Note on what actually landedWhile I was working, the PR author/bot pushed equivalent commits for both scenarios (
The branch now contains both regression tests (from the upstream commits) with no duplication. |
I agree, and had the #5580 PR for this. But I thought ondrej had different opinion on it see https://phpstan.slack.com/archives/C0AEG6PGEL8/p1777717259302239 |
Summary
array_filter(),array_map()andarray_reduce()used as standalone statements were not reported by the "Call to function … on a separate line has no effect." rule, even when the supplied callback is pure. These functions do not mutate their arguments and have no side effects of their own — the only side effects come from the callback. Because PHPStan already propagates the callback's impure points to the caller (callable parameters of functions are treated as immediately invoked), the functions themselves can be marked side-effect-free without losing detection of impure callbacks.Changes
bin/functionMetadata_original.phpandresources/functionMetadata.php(the generated file consumed at runtime): addedhasSideEffects => falsefor:array_filter,array_map,array_reduce(reported in the issue)array_find,array_find_key,array_any,array_all(the rest of the non-mutating array callback family — PHP 8.4)preg_replace_callback,preg_replace_callback_array(analogous string callback functions)tests/PHPStan/Rules/Functions/data/bug-11101.php+bug-11101-php84.phpand new test methods inCallToFunctionStatementWithoutSideEffectsRuleTestcovering both pure callbacks (reported) and impure / unknown callbacks (not reported).tests/PHPStan/Command/data/file-without-errors.php: changedarray_filter([0, 1, 2]);toecho count(array_filter([0, 1, 2]));so the "no errors" fixture no longer contains what is now correctly a no-op call.tests/PHPStan/Analyser/data/discussion-7124.php: the localfilter()wrapper is now transitively pure (it only callsarray_filter), so its standalone statement calls were correctly flagged as having no effect. Wrapped the calls inecho count(...)so the test keeps focusing on its conditional-callable type errors.Probed and intentionally not changed
array_walk,usort,uasort,uksort: mutate their array argument by reference, so they correctly keep their side effects.array_udiff,array_uintersect,array_diff_uassoc, … : alreadyhasSideEffects => false.call_user_func/call_user_func_array: genuinely pure-modulo-callback too, but they are the general-purpose invocation primitive rather than part of the array-callback family; excluded to keep the behavior change scoped to the issue.Root cause
Purity of built-in functions is driven by
resources/functionMetadata.php. When a function is absent there,hasSideEffects()isMaybe, soSimpleImpurePoint::createFromVariant()attaches afunctionCallimpure point to every call. That impure point preventsNodeScopeResolverfrom emitting aNoopExpressionNode, so the no-effect rule never fires. The fix records the true side-effect status of these higher-order array functions. The callback's own side effects are unaffected: they are gathered independently when arguments are processed (immediately-invoked callable parameters), so an impure callback still produces impure points and the call is not flagged.The same metadata gap affected the whole family of non-mutating array callback functions and the
preg_replace_callbackpair, which are fixed together here.Test
CallToFunctionStatementWithoutSideEffectsRuleTest::testBug11101asserts thatarray_filter(),array_map(),array_reduce(),array_filter()(no callback),preg_replace_callback()andpreg_replace_callback_array()statements with pure callbacks are reported, while impure and unknown-purity callbacks are not.testBug11101Php84(gated>= 8.4.0) coversarray_any()/array_all(). (array_find/array_find_keyare verified via CLI; they cannot be asserted inRuleTestCasebecause existingnsrt/array-find*.phppolyfill fixtures shadow the built-ins during static reflection. In production they resolve through the native signature map and honor the metadata.)make testsandmake phpstanare green.Fixes phpstan/phpstan#11101
Fixes phpstan/phpstan#11100