Repository navigation
Mark array_filter(), array_map(), array_reduce() and PHP 8.4 array_find/array_any/array_all as pure in function metadata
#5580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
phpstan-bot
wants to merge
7
commits into
phpstan:2.1.x
from
phpstan-bot:create-pull-request/patch-pgvhhg1
Closed
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a09ab3a
Mark `array_filter()`, `array_map()`, `array_reduce()` and PHP 8.4 `a…
VincentLanglet bc9200f
Implement @phpstan-pure-unless-callable-is-impure tag and mark callba…
phpstan-bot e562794
Do not treat @phpstan-pure-unless-callable-is-impure as unconditional…
phpstan-bot 0953cfc
Store pureUnlessCallableIsImpure as array of parameter names instead …
phpstan-bot ce47eba
Remove pureUnlessCallableIsImpure from function metadata
phpstan-bot 0d86fb8
Remove pureUnlessCallableIsImpure implementation, rely on existing @p…
phpstan-bot eba6f20
Remove getFunctionPurityFromMetadata, use lint directives in array-fi…
phpstan-bot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| <?php | ||
|
|
||
| array_filter([0, 1, 2]); | ||
| $a = array_filter([0, 1, 2]); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
tests/PHPStan/Rules/Functions/data/bug-11101-custom-pure.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| <?php | ||
|
|
||
| namespace Bug11101CustomPure; | ||
|
|
||
| /** | ||
| * @phpstan-pure | ||
| * @param array<int> $array | ||
| * @param callable(int): bool $callback | ||
| * @param-immediately-invoked-callable $callback | ||
| * @return array<int> | ||
| */ | ||
| function pureWithImmediateCallback(array $array, callable $callback): array | ||
| { | ||
| return array_filter($array, $callback); | ||
| } | ||
|
|
||
| /** | ||
| * @phpstan-pure | ||
| * @param array<int> $array | ||
| * @param callable(int): bool $callback | ||
| * @param-later-invoked-callable $callback | ||
| * @return array<int> | ||
| */ | ||
| function pureWithLaterCallback(array $array, callable $callback): array | ||
| { | ||
| return $array; | ||
| } | ||
|
|
||
| class Foo | ||
| { | ||
|
|
||
| /** | ||
| * @param array<int> $array | ||
| */ | ||
| public function testImmediateCallback(array $array, callable $callback): void | ||
| { | ||
| // Pure callback - should be reported | ||
| pureWithImmediateCallback($array, fn ($v) => $v > 5); | ||
|
|
||
| // Impure callback - should NOT be reported | ||
| pureWithImmediateCallback($array, function ($v) { | ||
| echo $v; | ||
| return $v > 0; | ||
| }); | ||
|
|
||
| // Unknown callback - should NOT be reported | ||
| pureWithImmediateCallback($array, $callback); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<int> $array | ||
| */ | ||
| public function testLaterCallback(array $array, callable $callback): void | ||
| { | ||
| // Pure callback - should be reported | ||
| pureWithLaterCallback($array, fn ($v) => $v > 5); | ||
|
|
||
| // Impure callback - should be reported (later-invoked, callback impurity doesn't matter at call site) | ||
| pureWithLaterCallback($array, function ($v) { | ||
| echo $v; | ||
| return $v > 0; | ||
| }); | ||
|
|
||
| // Unknown callback - should be reported (later-invoked, callback impurity doesn't matter at call site) | ||
| pureWithLaterCallback($array, $callback); | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php // lint >= 8.4 | ||
|
|
||
| namespace Bug11101Php84; | ||
|
|
||
| class Foo | ||
| { | ||
|
|
||
| /** | ||
| * @param array<int> $array | ||
| */ | ||
| public function doFoo(array $array): void | ||
| { | ||
| array_find($array, fn ($v) => $v > 5); | ||
| array_find_key($array, fn ($v) => $v > 5); | ||
| array_any($array, fn ($v) => $v > 5); | ||
| array_all($array, fn ($v) => $v > 5); | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| namespace Bug11101; | ||
|
|
||
| class Foo | ||
| { | ||
|
|
||
| /** | ||
| * @param array<int> $array | ||
| */ | ||
| public function doFoo(array $array): void | ||
| { | ||
| array_filter($array, 'is_string'); | ||
| array_map('is_string', $array); | ||
| array_reduce($array, function ($carry, $item) { | ||
| return $carry + $item; | ||
| }, 0); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<int> $array | ||
| */ | ||
| public function doBar(array $array, callable $callback): void | ||
| { | ||
| // These should NOT be reported because the callback might be impure | ||
| array_filter($array, $callback); | ||
| array_map($callback, $array); | ||
| array_reduce($array, $callback, 0); | ||
|
|
||
| // Impure closure should not be reported | ||
| array_filter($array, function ($v) { | ||
| echo $v; | ||
| return $v > 0; | ||
| }); | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be needed, function metadata are already correclty handled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All 11980 tests pass (79552 assertions) and
make phpstanreports no errors. There are no code changes to commit since the files are back to their original state.The reviewer's suggestion to remove the
getFunctionPurityFromMetadatacall is incorrect. Here's the explanation:The metadata lookup in
getCustomFunction()IS needed because some functions with metadata entries can be resolved throughgetCustomFunction()instead offindFunctionReflection(). Specifically,array_findandarray_find_keyhave polyfill-style definitions in test data files (tests/PHPStan/Analyser/nsrt/array-find.phpandarray-find-key.php). When BetterReflection resolves these functions, it finds them in those files rather than as internal PHP functions.NativeFunctionReflectionProvider::findFunctionReflection()then returns null at line 79 (file is not internal and path doesn't contain "polyfill"), so the function falls through togetCustomFunction()where the metadata lookup is the only way to determine purity.The same scenario occurs in real-world codebases when users have polyfill definitions for newer PHP functions — BetterReflection resolves the function from the polyfill file, bypassing the normal
findFunctionReflection()metadata check.I verified this by temporarily removing the code and seeing that the
testBug11101Php84test fails:array_findandarray_find_keyare no longer reported as pure, whilearray_anyandarray_all(which don't have polyfill files) continue to work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those polyfill in test should be removed and the test should be updated (with a
// lintcomment ?) to run on the appropriate php versions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Here's what was changed to address VincentLanglet's review:
The reviewer was right — the
getFunctionPurityFromMetadatafallback inBetterReflectionProvider::getCustomFunction()was only needed because test files (array-find.phpandarray-find-key.php) contained polyfill definitions that causedarray_find/array_find_keyto be resolved throughgetCustomFunction()instead offindFunctionReflection().Fix:
tests/PHPStan/Analyser/nsrt/array-find.phpandarray-find-key.php, added// lint >= 8.4so these tests only run on PHP 8.4+ where the native functions existgetFunctionPurityFromMetadata()method fromNativeFunctionReflectionProviderBetterReflectionProvider::getCustomFunction()All 11980 tests pass (79552 assertions) and
make phpstanreports no errors.