From 25bacb3e671d045dafb2eddcbb208578aa4f9e4c Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 8 Jul 2026 18:53:45 +0900 Subject: [PATCH] Add named argument tests for @pure-unless-callable-is-impure --- .../Rules/Pure/PureFunctionRuleTest.php | 51 +++++++ ...re-unless-callable-is-impure-named-arg.php | 138 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 tests/PHPStan/Rules/Pure/data/pure-unless-callable-is-impure-named-arg.php diff --git a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php index 173ab215ba..1a4d3c3fbb 100644 --- a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php @@ -296,6 +296,57 @@ public function testPureUnlessCallableIsImpure(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testPureUnlessCallableIsImpureNamedArgs(): void + { + $this->analyse([__DIR__ . '/data/pure-unless-callable-is-impure-named-arg.php'], [ + [ + 'Impure call to function PureUnlessCallableIsImpureNamedArg\myMap() in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithImpureCallbackByName().', + 61, + ], + [ + 'Impure echo in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithImpureCallbackByName().', + 62, + ], + [ + 'Possibly impure call to a callable in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithOpaqueCallbackByName().', + 75, + ], + [ + 'Possibly impure call to function PureUnlessCallableIsImpureNamedArg\myMap() in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithOpaqueCallbackByName().', + 75, + ], + [ + 'Impure call to function array_map() in pure function PureUnlessCallableIsImpureNamedArg\pureWithImpureCallbackByName().', + 85, + ], + [ + 'Impure echo in pure function PureUnlessCallableIsImpureNamedArg\pureWithImpureCallbackByName().', + 86, + ], + [ + 'Possibly impure call to a callable in pure function PureUnlessCallableIsImpureNamedArg\pureWithOpaqueCallbackByName().', + 99, + ], + [ + 'Possibly impure call to function array_map() in pure function PureUnlessCallableIsImpureNamedArg\pureWithOpaqueCallbackByName().', + 99, + ], + [ + 'Impure call to function PureUnlessCallableIsImpureNamedArg\myMap() in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithImpureCallbackShuffled().', + 121, + ], + [ + 'Impure echo in pure function PureUnlessCallableIsImpureNamedArg\pureCallingUserlandWithImpureCallbackShuffled().', + 122, + ], + [ + 'Impure call to method PureUnlessCallableIsImpureNamedArg\Mapper::map() in pure function PureUnlessCallableIsImpureNamedArg\pureCallingMethodWithImpureCallbackByName().', + 134, + ], + ]); + } + #[RequiresPhp('>= 8.4.0')] public function testPureUnlessCallableIsImpurePhp84(): void { diff --git a/tests/PHPStan/Rules/Pure/data/pure-unless-callable-is-impure-named-arg.php b/tests/PHPStan/Rules/Pure/data/pure-unless-callable-is-impure-named-arg.php new file mode 100644 index 0000000000..20ebcd5dea --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/pure-unless-callable-is-impure-named-arg.php @@ -0,0 +1,138 @@ += 8.0 + +declare(strict_types = 1); + +namespace PureUnlessCallableIsImpureNamedArg; + +/** + * @param callable(int): int $f + * @param array $arr + * @return array + * @pure-unless-callable-is-impure $f + */ +function myMap(callable $f, array $arr): array +{ + $result = []; + foreach ($arr as $i => $v) { + $result[$i] = $f($v); + } + + return $result; +} + +class Mapper +{ + + /** + * @param callable(int): int $f + * @param array $arr + * @return array + * @pure-unless-callable-is-impure $f + */ + public function map(callable $f, array $arr): array + { + $result = []; + foreach ($arr as $i => $v) { + $result[$i] = $f($v); + } + + return $result; + } + +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureCallingUserlandWithPureCallbackByName(array $arr): array +{ + return myMap(f: static fn (int $x): int => $x * 2, arr: $arr); +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureCallingUserlandWithImpureCallbackByName(array $arr): array +{ + return myMap(f: static function (int $x): int { + echo $x; + return $x * 2; + }, arr: $arr); +} + +/** + * @param array $arr + * @param callable(int): int $cb + * @return array + * @phpstan-pure + */ +function pureCallingUserlandWithOpaqueCallbackByName(array $arr, callable $cb): array +{ + return myMap(f: $cb, arr: $arr); +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureWithImpureCallbackByName(array $arr): array +{ + return array_map(callback: static function (int $x): int { + echo $x; + return $x * 2; + }, array: $arr); +} + +/** + * @param array $arr + * @param callable(int): int $cb + * @return array + * @phpstan-pure + */ +function pureWithOpaqueCallbackByName(array $arr, callable $cb): array +{ + return array_map(callback: $cb, array: $arr); +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureCallingUserlandWithPureCallbackShuffled(array $arr): array +{ + // Arguments are passed in shuffled order using names, so name-based matching + // (not positional matching) determines which argument is judged for purity. + return myMap(arr: $arr, f: static fn (int $x): int => $x * 2); +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureCallingUserlandWithImpureCallbackShuffled(array $arr): array +{ + return myMap(arr: $arr, f: static function (int $x): int { + echo $x; + return $x * 2; + }); +} + +/** + * @param array $arr + * @return array + * @phpstan-pure + */ +function pureCallingMethodWithImpureCallbackByName(Mapper $mapper, array $arr): array +{ + return $mapper->map(f: static function (int $x): int { + echo $x; + return $x * 2; + }, arr: $arr); +}