From 98b96fa03f71a3ddbc444cf402ef7998801b2a8f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 07:48:58 +0100 Subject: [PATCH 01/21] StrictFunctionCallsRule is auto-fixable --- .../StrictCalls/StrictFunctionCallsRule.php | 119 ++++++++++++++++-- .../StrictFunctionCallsRuleTest.php | 20 +++ .../data/strict-function-calls-fixable.php | 31 +++++ .../strict-function-calls-fixable.php.fixed | 31 +++++ .../strict-function-calls-named-arguments.php | 30 +++++ ...t-function-calls-named-arguments.php.fixed | 30 +++++ 6 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-fixable.php create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 9af4389..442f198 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -3,14 +3,19 @@ namespace PHPStan\Rules\StrictCalls; use PhpParser\Node; +use PhpParser\Node\Arg; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\Type; use function array_key_exists; use function sprintf; use function strtolower; @@ -47,6 +52,8 @@ public function processNode(Node $node, Scope $scope): array return []; } + $originalNode = $node; + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { return []; } @@ -69,26 +76,122 @@ public function processNode(Node $node, Scope $scope): array $argumentPosition = $this->functionArguments[$functionName]; if (!array_key_exists($argumentPosition, $node->getArgs())) { return [ - RuleErrorBuilder::message(sprintf( - 'Call to function %s() requires parameter #%d to be set.', + $this->buildError( + sprintf( + 'Call to function %s() requires parameter #%d to be set.', + $functionName, + $argumentPosition + 1, + ), $functionName, - $argumentPosition + 1, - ))->identifier('function.strict')->build(), + $originalNode, + $node, + $scope, + ), ]; } $argumentType = $scope->getType($node->getArgs()[$argumentPosition]->value); if (!$argumentType->isTrue()->yes()) { return [ - RuleErrorBuilder::message(sprintf( - 'Call to function %s() requires parameter #%d to be true.', + $this->buildError( + sprintf( + 'Call to function %s() requires parameter #%d to be true.', + $functionName, + $argumentPosition + 1, + ), $functionName, - $argumentPosition + 1, - ))->identifier('function.strict')->build(), + $originalNode, + $node, + $scope, + ), ]; } return []; } + private function buildError( + string $message, + string $functionName, + FuncCall $originalNode, + FuncCall $normalizedNode, + Scope $scope + ): IdentifierRuleError + { + $errorBuilder = RuleErrorBuilder::message($message)->identifier('function.strict'); + if ( + ($functionName === 'in_array' || $functionName === 'array_search' || $functionName === 'array_keys') + && $this->hasSafeStrictComparisonTypes($functionName, $normalizedNode, $scope) + ) { + $errorBuilder->fixNode($originalNode, static function (FuncCall $call): FuncCall { + $strictArgument = null; + $positionalArgument = 0; + $hasNamedArguments = false; + foreach ($call->getArgs() as $arg) { + if ($arg->unpack) { + // unpack is not fixable + return $call; + } + + if ($arg->name !== null) { + $hasNamedArguments = true; + if ($arg->name->toString() === 'strict') { + $strictArgument = $arg; + } + continue; + } + + if ($positionalArgument === 2) { + $strictArgument = $arg; + } + $positionalArgument++; + } + + $true = new ConstFetch(new Name('true')); + if ($strictArgument !== null) { + $strictArgument->value = $true; + return $call; + } + + $call->args[] = new Arg( + $true, + false, + false, + [], + $hasNamedArguments ? new Identifier('strict') : null, + ); + + return $call; + }); + } + + return $errorBuilder->build(); + } + + private function hasSafeStrictComparisonTypes(string $functionName, FuncCall $normalizedNode, Scope $scope): bool + { + $args = $normalizedNode->getArgs(); + if (!isset($args[0], $args[1])) { + return false; + } + + $needleArgument = $functionName === 'array_keys' ? $args[1] : $args[0]; + $haystackArgument = $functionName === 'array_keys' ? $args[0] : $args[1]; + $needleType = $scope->getNativeType($needleArgument->value); + $haystackValueType = $scope->getNativeType($haystackArgument->value)->getIterableValueType(); + + return $this->areSameStrictScalarTypes($needleType, $haystackValueType); + } + + private function areSameStrictScalarTypes(Type $needleType, Type $haystackValueType): bool + { + return ( + $needleType->isBoolean()->yes() + && $haystackValueType->isBoolean()->yes() + ) || ( + $needleType->isInteger()->yes() + && $haystackValueType->isInteger()->yes() + ); + } + } diff --git a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php index 00e9009..11970f8 100644 --- a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php +++ b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php @@ -75,6 +75,26 @@ public function testRule(): void ]); } + public function testFix(): void + { + $this->fix( + __DIR__ . '/data/strict-function-calls-fixable.php', + __DIR__ . '/data/strict-function-calls-fixable.php.fixed', + ); + } + + public function testFixNamedArguments(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + + $this->fix( + __DIR__ . '/data/strict-function-calls-named-arguments.php', + __DIR__ . '/data/strict-function-calls-named-arguments.php.fixed', + ); + } + public function testBug231(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php new file mode 100644 index 0000000..711f102 --- /dev/null +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -0,0 +1,31 @@ += 8.0 + +namespace StrictCallsNamedArguments; + +/** + * @param int<5, 10> $phpdocIntRange + */ +function doFoo(int $i, int $i2, $phpdocIntRange): void { + in_array(needle: $i, haystack: [1, 2]); + in_array(needle: $i, haystack: [1, 2], strict: false); + in_array(strict: false, needle: $i, haystack: [1, 2]); + in_array($i, haystack: [1, 2]); + + if ($i > 5 && $i < 10) { + in_array($i, haystack: [1, 2]); + } + if ($i > 0) { + in_array($i, haystack: [1, 2]); + } + if ($i2 > 0) { + in_array($i, haystack: [1, $i2]); + } + + in_array($i, haystack: [1, $phpdocIntRange]); + in_array($phpdocIntRange, haystack: [1, 2]); +} + +function doBar(int|string $union): void { + in_array(needle: $union, haystack: [1, 2]); +} diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed new file mode 100644 index 0000000..305214e --- /dev/null +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed @@ -0,0 +1,30 @@ += 8.0 + +namespace StrictCallsNamedArguments; + +/** + * @param int<5, 10> $phpdocIntRange + */ +function doFoo(int $i, int $i2, $phpdocIntRange): void { + in_array(needle: $i, haystack: [1, 2], strict: true); + in_array(needle: $i, haystack: [1, 2], strict: true); + in_array(strict: true, needle: $i, haystack: [1, 2]); + in_array($i, haystack: [1, 2], strict: true); + + if ($i > 5 && $i < 10) { + in_array($i, haystack: [1, 2], strict: true); + } + if ($i > 0) { + in_array($i, haystack: [1, 2], strict: true); + } + if ($i2 > 0) { + in_array($i, haystack: [1, $i2], strict: true); + } + + in_array($i, haystack: [1, $phpdocIntRange]); + in_array($phpdocIntRange, haystack: [1, 2]); +} + +function doBar(int|string $union): void { + in_array(needle: $union, haystack: [1, 2]); +} From b014b77c4465dfd3124b7d4b87d261318228611d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 07:57:35 +0100 Subject: [PATCH 02/21] add string comparison edge case --- .../StrictCalls/data/strict-function-calls-fixable.php | 9 ++++++++- .../data/strict-function-calls-fixable.php.fixed | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index 711f102..b39cc00 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -2,9 +2,12 @@ namespace StrictCalls; +function searchBool(bool $bool) { + array_search(false, [$bool]); +} + in_array(1, [1, 2]); in_array(1, [1, 2], false); -array_search(false, [true, false]); array_keys([1, 2], 1); $strict = rand(0, 1) === 1; array_keys([1, 2], 1, $strict); @@ -12,6 +15,7 @@ in_array(1, [1.0], false); in_array(1, [1, 1.0]); in_array('1', ['01']); + $float = 1.0; in_array($float, [1.0]); in_array(null, [null]); @@ -29,3 +33,6 @@ function doFoo(int $i): bool array_keys([1, 2]); base64_decode('abcd'); + +$numericStringNeedle = '01'; // edge case: numeric string compared against int-string +in_array($numericStringNeedle, ['1', '2']); diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index 7778f82..6f24ac0 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -2,9 +2,12 @@ namespace StrictCalls; +function searchBool(bool $bool) { + array_search(false, [$bool], true); +} + in_array(1, [1, 2], true); in_array(1, [1, 2], true); -array_search(false, [true, false], true); array_keys([1, 2], 1, true); $strict = rand(0, 1) === 1; array_keys([1, 2], 1, true); @@ -12,6 +15,7 @@ in_array(1, [1.0]); in_array(1, [1.0], false); in_array(1, [1, 1.0]); in_array('1', ['01']); + $float = 1.0; in_array($float, [1.0]); in_array(null, [null]); @@ -29,3 +33,6 @@ function doFoo(int $i): bool array_keys([1, 2]); base64_decode('abcd'); + +$numericStringNeedle = '01'; // edge case: numeric string compared against int-string +in_array($numericStringNeedle, ['1', '2']); From f4ed7c29b6124096b4c862da55c3b2364ebd4858 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 07:58:30 +0100 Subject: [PATCH 03/21] test class-constants --- .../StrictCalls/data/strict-function-calls-fixable.php | 8 ++++++++ .../data/strict-function-calls-fixable.php.fixed | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index b39cc00..8ab5b53 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -36,3 +36,11 @@ function doFoo(int $i): bool $numericStringNeedle = '01'; // edge case: numeric string compared against int-string in_array($numericStringNeedle, ['1', '2']); + +class X { + private const TYPES = [128, 256]; + + function doFoo(int $i):bool { + return in_array($i, self::TYPES); + } +} diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index 6f24ac0..c8a8db4 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -36,3 +36,11 @@ base64_decode('abcd'); $numericStringNeedle = '01'; // edge case: numeric string compared against int-string in_array($numericStringNeedle, ['1', '2']); + +class X { + private const TYPES = [128, 256]; + + function doFoo(int $i):bool { + return in_array($i, self::TYPES, true); + } +} From 9a7e6d4c9ea4fa73fce7641e9fe5d97f33f1d754 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 08:00:42 +0100 Subject: [PATCH 04/21] link edge case --- tests/Rules/StrictCalls/data/strict-function-calls-fixable.php | 2 +- .../StrictCalls/data/strict-function-calls-fixable.php.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index 8ab5b53..ccd5aa3 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -34,7 +34,7 @@ function doFoo(int $i): bool array_keys([1, 2]); base64_decode('abcd'); -$numericStringNeedle = '01'; // edge case: numeric string compared against int-string +$numericStringNeedle = '01'; // edge case: numeric string compared against int-string: https://3v4l.org/hmgdd#v in_array($numericStringNeedle, ['1', '2']); class X { diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index c8a8db4..debeda1 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -34,7 +34,7 @@ function doFoo(int $i): bool array_keys([1, 2]); base64_decode('abcd'); -$numericStringNeedle = '01'; // edge case: numeric string compared against int-string +$numericStringNeedle = '01'; // edge case: numeric string compared against int-string: https://3v4l.org/hmgdd#v in_array($numericStringNeedle, ['1', '2']); class X { From 8f203b3b57b9bb2bac98ba6f899760d0296ef706 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 08:15:18 +0100 Subject: [PATCH 05/21] make some strings autofixable --- .../StrictCalls/StrictFunctionCallsRule.php | 4 +++ .../data/strict-function-calls-fixable.php | 31 +++++++++++++++++-- .../strict-function-calls-fixable.php.fixed | 31 +++++++++++++++++-- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 442f198..4aca434 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -191,6 +191,10 @@ private function areSameStrictScalarTypes(Type $needleType, Type $haystackValueT ) || ( $needleType->isInteger()->yes() && $haystackValueType->isInteger()->yes() + ) || ( + $needleType->isString()->yes() + && $haystackValueType->isString()->yes() + && $haystackValueType->isNumericString()->no() ); } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index ccd5aa3..c80455e 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -37,10 +37,37 @@ function doFoo(int $i): bool $numericStringNeedle = '01'; // edge case: numeric string compared against int-string: https://3v4l.org/hmgdd#v in_array($numericStringNeedle, ['1', '2']); +$stringNeedle = 'foo'; +$strings = ['foo', 'bar']; +in_array($stringNeedle, $strings); +in_array($stringNeedle, $strings, false); +array_search($stringNeedle, $strings); +array_keys($strings, $stringNeedle); + +/** @return string[] */ +function getStringArray(): array +{ + return ['foo', 'bar']; +} + +function doStringFoo(string $needle): bool +{ + return in_array($needle, getStringArray()); +} + +function doLiteralStringFoo(string $needle): bool +{ + return in_array($needle, ['foo', 'bar']); +} + class X { private const TYPES = [128, 256]; + private const TYPE_STRINGS = ['ABC', 'DEF']; + private const TYPES_NUMERIC_STRING = ['ABC', 'DEF', '1']; - function doFoo(int $i):bool { - return in_array($i, self::TYPES); + function doFoo(int $i, string $s) { + in_array($i, self::TYPES); + in_array($s, self::TYPE_STRINGS); + in_array($s, self::TYPES_NUMERIC_STRING); } } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index debeda1..bb306a7 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -37,10 +37,37 @@ base64_decode('abcd'); $numericStringNeedle = '01'; // edge case: numeric string compared against int-string: https://3v4l.org/hmgdd#v in_array($numericStringNeedle, ['1', '2']); +$stringNeedle = 'foo'; +$strings = ['foo', 'bar']; +in_array($stringNeedle, $strings, true); +in_array($stringNeedle, $strings, true); +array_search($stringNeedle, $strings, true); +array_keys($strings, $stringNeedle, true); + +/** @return string[] */ +function getStringArray(): array +{ + return ['foo', 'bar']; +} + +function doStringFoo(string $needle): bool +{ + return in_array($needle, getStringArray()); +} + +function doLiteralStringFoo(string $needle): bool +{ + return in_array($needle, ['foo', 'bar'], true); +} + class X { private const TYPES = [128, 256]; + private const TYPE_STRINGS = ['ABC', 'DEF']; + private const TYPES_NUMERIC_STRING = ['ABC', 'DEF', '1']; - function doFoo(int $i):bool { - return in_array($i, self::TYPES, true); + function doFoo(int $i, string $s) { + in_array($i, self::TYPES, true); + in_array($s, self::TYPE_STRINGS, true); + in_array($s, self::TYPES_NUMERIC_STRING); } } From 66859087c2ea6aaba33c2e943f8922f3c79ddbc6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 09:18:16 +0100 Subject: [PATCH 06/21] added failling test --- .../data/strict-function-calls-named-arguments.php | 12 +++++++++++- .../strict-function-calls-named-arguments.php.fixed | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php index 8ae8850..bc61a57 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php @@ -1,7 +1,10 @@ -= 8.0 += 8.2 namespace StrictCallsNamedArguments; +use function file_put_contents; +use function in_array; + /** * @param int<5, 10> $phpdocIntRange */ @@ -19,6 +22,7 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { } if ($i2 > 0) { in_array($i, haystack: [1, $i2]); + in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); } in_array($i, haystack: [1, $phpdocIntRange]); @@ -28,3 +32,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { function doBar(int|string $union): void { in_array(needle: $union, haystack: [1, 2]); } + +function returnsFalseWithSideEffect(): false { + // some side-effect + file_put_contents('somefile.txt', 'some content'); + return false; +} diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed index 305214e..44ff88a 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed @@ -1,7 +1,10 @@ -= 8.0 += 8.2 namespace StrictCallsNamedArguments; +use function file_put_contents; +use function in_array; + /** * @param int<5, 10> $phpdocIntRange */ @@ -19,6 +22,7 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { } if ($i2 > 0) { in_array($i, haystack: [1, $i2], strict: true); + in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); } in_array($i, haystack: [1, $phpdocIntRange]); @@ -28,3 +32,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { function doBar(int|string $union): void { in_array(needle: $union, haystack: [1, 2]); } + +function returnsFalseWithSideEffect(): false { + // some side-effect + file_put_contents('somefile.txt', 'some content'); + return false; +} From d36fdcc04cc910e009b41ce4c324278fd82a324d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 09:21:21 +0100 Subject: [PATCH 07/21] Update StrictFunctionCallsRule.php --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 4aca434..18f25fb 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -129,8 +129,6 @@ private function buildError( $hasNamedArguments = false; foreach ($call->getArgs() as $arg) { if ($arg->unpack) { - // unpack is not fixable - return $call; } if ($arg->name !== null) { From 11d068efd946c26eb82d206b010221ef6093e632 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 09:40:16 +0100 Subject: [PATCH 08/21] Update StrictFunctionCallsRule.php --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 18f25fb..4aca434 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -129,6 +129,8 @@ private function buildError( $hasNamedArguments = false; foreach ($call->getArgs() as $arg) { if ($arg->unpack) { + // unpack is not fixable + return $call; } if ($arg->name !== null) { From 7771be11dffc503710d2174683b09f99ad7d0f27 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:16:03 +0100 Subject: [PATCH 09/21] fix --- .../StrictCalls/StrictFunctionCallsRule.php | 38 ++++++++++++++++--- .../strict-function-calls-named-arguments.php | 3 ++ ...t-function-calls-named-arguments.php.fixed | 3 ++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 4aca434..273b247 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -6,8 +6,10 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PhpParser\Node\Scalar; use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -17,6 +19,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Type; use function array_key_exists; +use function is_string; use function sprintf; use function strtolower; @@ -123,10 +126,13 @@ private function buildError( ($functionName === 'in_array' || $functionName === 'array_search' || $functionName === 'array_keys') && $this->hasSafeStrictComparisonTypes($functionName, $normalizedNode, $scope) ) { - $errorBuilder->fixNode($originalNode, static function (FuncCall $call): FuncCall { + $errorBuilder->fixNode($originalNode, static function (FuncCall $call) use ($functionName): FuncCall { $strictArgument = null; - $positionalArgument = 0; $hasNamedArguments = false; + $usedPositions = []; + $namedArgumentPositions = $functionName === 'array_keys' + ? ['array' => 0, 'filter_value' => 1, 'strict' => 2] + : ['needle' => 0, 'haystack' => 1, 'strict' => 2]; foreach ($call->getArgs() as $arg) { if ($arg->unpack) { // unpack is not fixable @@ -135,20 +141,42 @@ private function buildError( if ($arg->name !== null) { $hasNamedArguments = true; - if ($arg->name->toString() === 'strict') { + $argumentName = $arg->name->toString(); + if (!array_key_exists($argumentName, $namedArgumentPositions)) { + return $call; + } + + $argumentPosition = $namedArgumentPositions[$argumentName]; + $usedPositions[$argumentPosition] = true; + if ($argumentPosition === 2) { $strictArgument = $arg; } continue; } - if ($positionalArgument === 2) { + $argumentPosition = 0; + while (array_key_exists($argumentPosition, $usedPositions)) { + $argumentPosition++; + } + $usedPositions[$argumentPosition] = true; + if ($argumentPosition === 2) { $strictArgument = $arg; } - $positionalArgument++; } $true = new ConstFetch(new Name('true')); if ($strictArgument !== null) { + if ( + !$strictArgument->value instanceof ConstFetch + && !$strictArgument->value instanceof Scalar + && !( + $strictArgument->value instanceof Variable + && is_string($strictArgument->value->name) + ) + ) { + return $call; + } + $strictArgument->value = $true; return $call; } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php index bc61a57..6965bed 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php @@ -25,6 +25,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); } + // named argument followed by positional ones + in_array(needle: $i, haystack: [1, 2], false); + in_array($i, haystack: [1, $phpdocIntRange]); in_array($phpdocIntRange, haystack: [1, 2]); } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed index 44ff88a..265bd21 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed @@ -25,6 +25,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); } + // named argument followed by positional ones + in_array(needle: $i, haystack: [1, 2], true); + in_array($i, haystack: [1, $phpdocIntRange]); in_array($phpdocIntRange, haystack: [1, 2]); } From ee4074011a75ea7a1b52849c431586e769c6474e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:16:20 +0100 Subject: [PATCH 10/21] Update StrictFunctionCallsRule.php --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 273b247..ce14aeb 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -159,9 +159,11 @@ private function buildError( $argumentPosition++; } $usedPositions[$argumentPosition] = true; - if ($argumentPosition === 2) { - $strictArgument = $arg; + if ($argumentPosition !== 2) { + continue; } + + $strictArgument = $arg; } $true = new ConstFetch(new Name('true')); From 4cf7c02fca82bf482057ede5ceb102afe3882e1d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:19:41 +0100 Subject: [PATCH 11/21] fix lint --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 02c6d35..e03a15d 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ tests: .PHONY: lint lint: php vendor/bin/parallel-lint --colors -e php,php3,php4,php5,phtml,phpt,fixed \ + --exclude tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php \ + --exclude tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed \ src tests .PHONY: cs-install From 9fe0f410dcf68bf215c419142b1ad4d3719f7442 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:37:53 +0100 Subject: [PATCH 12/21] test it fixes strcit=1/0 to const bool --- tests/Rules/StrictCalls/data/strict-function-calls-fixable.php | 3 +++ .../StrictCalls/data/strict-function-calls-fixable.php.fixed | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index c80455e..5d2f808 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -16,6 +16,9 @@ function searchBool(bool $bool) { in_array(1, [1, 1.0]); in_array('1', ['01']); +in_array(1, [1, 2], 1); +in_array(1, [1, 2], 0); + $float = 1.0; in_array($float, [1.0]); in_array(null, [null]); diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index bb306a7..0e72dbb 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -16,6 +16,9 @@ in_array(1, [1.0], false); in_array(1, [1, 1.0]); in_array('1', ['01']); +in_array(1, [1, 2], true); +in_array(1, [1, 2], true); + $float = 1.0; in_array($float, [1.0]); in_array(null, [null]); From 6ee6afdea81c9f31e9d42840487cc29bd672fe8e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:47:58 +0100 Subject: [PATCH 13/21] fix properties with side-effects --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 12 ++++++++++-- .../data/strict-function-calls-fixable.php | 2 ++ .../data/strict-function-calls-fixable.php.fixed | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index ce14aeb..9b7682a 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -19,6 +19,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Type; use function array_key_exists; +use function in_array; use function is_string; use function sprintf; use function strtolower; @@ -169,8 +170,15 @@ private function buildError( $true = new ConstFetch(new Name('true')); if ($strictArgument !== null) { if ( - !$strictArgument->value instanceof ConstFetch - && !$strictArgument->value instanceof Scalar + $strictArgument->value instanceof ConstFetch + ) { + if (!in_array($strictArgument->value->name->toLowerString(), ['true', 'false', 'null'], true)) { + return $call; + } + } elseif ( + !$strictArgument->value instanceof Scalar\Int_ + && !$strictArgument->value instanceof Scalar\Float_ + && !$strictArgument->value instanceof Scalar\String_ && !( $strictArgument->value instanceof Variable && is_string($strictArgument->value->name) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index 5d2f808..7839999 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -18,6 +18,8 @@ function searchBool(bool $bool) { in_array(1, [1, 2], 1); in_array(1, [1, 2], 0); +in_array(1, [1, 2], "1"); +in_array(1, [1, 2], "0"); $float = 1.0; in_array($float, [1.0]); diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index 0e72dbb..02ac02b 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -16,6 +16,8 @@ in_array(1, [1.0], false); in_array(1, [1, 1.0]); in_array('1', ['01']); +in_array(1, [1, 2], true); +in_array(1, [1, 2], true); in_array(1, [1, 2], true); in_array(1, [1, 2], true); From f04d1d6340e88c144bb519f1a8d6061db080d4d4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:57:38 +0100 Subject: [PATCH 14/21] Update StrictFunctionCallsRule.php --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 9b7682a..a04df24 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -177,7 +177,6 @@ private function buildError( } } elseif ( !$strictArgument->value instanceof Scalar\Int_ - && !$strictArgument->value instanceof Scalar\Float_ && !$strictArgument->value instanceof Scalar\String_ && !( $strictArgument->value instanceof Variable From abfdd0c00003ddf272cbd1271c447602294762a1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 10:58:20 +0100 Subject: [PATCH 15/21] test properties with side-effects --- .../data/strict-function-calls-fixable.php | 19 +++++++++++++++++++ .../strict-function-calls-fixable.php.fixed | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php index 7839999..70bf747 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php @@ -76,3 +76,22 @@ function doFoo(int $i, string $s) { in_array($s, self::TYPES_NUMERIC_STRING); } } + +class SideEffectProperty { + + public function __get(string $name): bool + { + // some side-effect + echo 'side effect'; + + return false; + } + +} + +$sideEffectProperty = new SideEffectProperty(); +// the "strict" argument is an interpolated string whose embedded expression +// triggers a magic __get() with a side-effect; it must not be overwritten +// with a plain "true" because that would silently drop the side-effect. +in_array(1, [1], "{$sideEffectProperty->strict}"); + diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index 02ac02b..511f521 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -76,3 +76,22 @@ class X { in_array($s, self::TYPES_NUMERIC_STRING); } } + +class SideEffectProperty { + + public function __get(string $name): bool + { + // some side-effect + echo 'side effect'; + + return false; + } + +} + +$sideEffectProperty = new SideEffectProperty(); +// the "strict" argument is an interpolated string whose embedded expression +// triggers a magic __get() with a side-effect; it must not be overwritten +// with a plain "true" because that would silently drop the side-effect. +in_array(1, [1], "{$sideEffectProperty->strict}"); + From 00e6af89bda513610ad0a616f5a674d1d73861cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 11:03:38 +0100 Subject: [PATCH 16/21] remove dirty hack --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 4 ---- .../StrictCalls/data/strict-function-calls-fixable.php.fixed | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index a04df24..419c731 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -178,10 +178,6 @@ private function buildError( } elseif ( !$strictArgument->value instanceof Scalar\Int_ && !$strictArgument->value instanceof Scalar\String_ - && !( - $strictArgument->value instanceof Variable - && is_string($strictArgument->value->name) - ) ) { return $call; } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed index 511f521..0202c5e 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-fixable.php.fixed @@ -10,7 +10,7 @@ in_array(1, [1, 2], true); in_array(1, [1, 2], true); array_keys([1, 2], 1, true); $strict = rand(0, 1) === 1; -array_keys([1, 2], 1, true); +array_keys([1, 2], 1, $strict); in_array(1, [1.0]); in_array(1, [1.0], false); in_array(1, [1, 1.0]); From 3332d3e69bdf3fb5557efb38da58a364c4ac1ba0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 11:03:48 +0100 Subject: [PATCH 17/21] Update StrictFunctionCallsRule.php --- src/Rules/StrictCalls/StrictFunctionCallsRule.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 419c731..1e624f8 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -6,7 +6,6 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Scalar; @@ -20,7 +19,6 @@ use PHPStan\Type\Type; use function array_key_exists; use function in_array; -use function is_string; use function sprintf; use function strtolower; From b972f1a6b5482c337e78385cf6906b845dc8fde1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 11:09:25 +0100 Subject: [PATCH 18/21] simplify --- .../StrictCalls/StrictFunctionCallsRule.php | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/src/Rules/StrictCalls/StrictFunctionCallsRule.php index 1e624f8..c8b0fbc 100644 --- a/src/Rules/StrictCalls/StrictFunctionCallsRule.php +++ b/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -11,6 +11,7 @@ use PhpParser\Node\Scalar; use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; @@ -18,6 +19,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Type; use function array_key_exists; +use function array_search; use function in_array; use function sprintf; use function strtolower; @@ -88,6 +90,8 @@ public function processNode(Node $node, Scope $scope): array $originalNode, $node, $scope, + $parametersAcceptor, + $argumentPosition, ), ]; } @@ -105,6 +109,8 @@ public function processNode(Node $node, Scope $scope): array $originalNode, $node, $scope, + $parametersAcceptor, + $argumentPosition, ), ]; } @@ -117,7 +123,9 @@ private function buildError( string $functionName, FuncCall $originalNode, FuncCall $normalizedNode, - Scope $scope + Scope $scope, + ParametersAcceptor $parametersAcceptor, + int $strictArgumentPosition ): IdentifierRuleError { $errorBuilder = RuleErrorBuilder::message($message)->identifier('function.strict'); @@ -125,13 +133,15 @@ private function buildError( ($functionName === 'in_array' || $functionName === 'array_search' || $functionName === 'array_keys') && $this->hasSafeStrictComparisonTypes($functionName, $normalizedNode, $scope) ) { - $errorBuilder->fixNode($originalNode, static function (FuncCall $call) use ($functionName): FuncCall { + $namedArgumentPositions = []; + foreach ($parametersAcceptor->getParameters() as $position => $parameter) { + $namedArgumentPositions[$parameter->getName()] = $position; + } + + $errorBuilder->fixNode($originalNode, static function (FuncCall $call) use ($namedArgumentPositions, $strictArgumentPosition): FuncCall { $strictArgument = null; $hasNamedArguments = false; $usedPositions = []; - $namedArgumentPositions = $functionName === 'array_keys' - ? ['array' => 0, 'filter_value' => 1, 'strict' => 2] - : ['needle' => 0, 'haystack' => 1, 'strict' => 2]; foreach ($call->getArgs() as $arg) { if ($arg->unpack) { // unpack is not fixable @@ -147,7 +157,7 @@ private function buildError( $argumentPosition = $namedArgumentPositions[$argumentName]; $usedPositions[$argumentPosition] = true; - if ($argumentPosition === 2) { + if ($argumentPosition === $strictArgumentPosition) { $strictArgument = $arg; } continue; @@ -158,7 +168,7 @@ private function buildError( $argumentPosition++; } $usedPositions[$argumentPosition] = true; - if ($argumentPosition !== 2) { + if ($argumentPosition !== $strictArgumentPosition) { continue; } @@ -184,12 +194,14 @@ private function buildError( return $call; } + $targetArgumentName = array_search($strictArgumentPosition, $namedArgumentPositions, true); + $call->args[] = new Arg( $true, false, false, [], - $hasNamedArguments ? new Identifier('strict') : null, + $hasNamedArguments && $targetArgumentName !== false ? new Identifier($targetArgumentName) : null, ); return $call; From 421227de43350f1d8dc9440663db6ed2d0ed9075 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Nov 2026 11:56:24 +0100 Subject: [PATCH 19/21] extract non-lint example into separate test --- Makefile | 4 ++-- .../Rules/StrictCalls/StrictFunctionCallsRuleTest.php | 9 +++++++++ .../data/strict-function-calls-named-arguments.php | 5 +---- .../strict-function-calls-named-arguments.php.fixed | 5 +---- .../strict-function-calls-positional-before-named.php | 11 +++++++++++ ...t-function-calls-positional-before-named.php.fixed | 11 +++++++++++ 6 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php create mode 100644 tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed diff --git a/Makefile b/Makefile index e03a15d..3941124 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ tests: .PHONY: lint lint: php vendor/bin/parallel-lint --colors -e php,php3,php4,php5,phtml,phpt,fixed \ - --exclude tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php \ - --exclude tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed \ + --exclude tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php \ + --exclude tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed \ src tests .PHONY: cs-install diff --git a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php index 11970f8..410bd1d 100644 --- a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php +++ b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php @@ -95,6 +95,15 @@ public function testFixNamedArguments(): void ); } + public function testFixPositionalBeforeNamed(): void + { + $this->fix( + __DIR__ . '/data/strict-function-calls-positional-before-named.php', + __DIR__ . '/data/strict-function-calls-positional-before-named.php.fixed', + ); + } + + public function testBug231(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php index 6965bed..e6938f7 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php @@ -22,12 +22,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { } if ($i2 > 0) { in_array($i, haystack: [1, $i2]); - in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); + in_array($i, [1, $i2], returnsFalseWithSideEffect()); } - // named argument followed by positional ones - in_array(needle: $i, haystack: [1, 2], false); - in_array($i, haystack: [1, $phpdocIntRange]); in_array($phpdocIntRange, haystack: [1, 2]); } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed index 265bd21..54a0cad 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-named-arguments.php.fixed @@ -22,12 +22,9 @@ function doFoo(int $i, int $i2, $phpdocIntRange): void { } if ($i2 > 0) { in_array($i, haystack: [1, $i2], strict: true); - in_array($i, haystack: [1, $i2], returnsFalseWithSideEffect()); + in_array($i, [1, $i2], returnsFalseWithSideEffect()); } - // named argument followed by positional ones - in_array(needle: $i, haystack: [1, 2], true); - in_array($i, haystack: [1, $phpdocIntRange]); in_array($phpdocIntRange, haystack: [1, 2]); } diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php new file mode 100644 index 0000000..0c9704f --- /dev/null +++ b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php @@ -0,0 +1,11 @@ += 8.2 + +namespace StrictCallsNamedArguments; + +use function in_array; + +function doFoo(int $i): void +{ + // named argument followed by positional ones + in_array(needle: $i, haystack: [1, 2], false); +} diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed new file mode 100644 index 0000000..d2f968f --- /dev/null +++ b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed @@ -0,0 +1,11 @@ += 8.2 + +namespace StrictCallsNamedArguments; + +use function in_array; + +function doFoo(int $i): void +{ + // named argument followed by positional ones + in_array(needle: $i, haystack: [1, 2], true); +} From 974d0e405cfb99b16780f278024985f0f9330b11 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 5 Nov 2026 08:11:44 +0100 Subject: [PATCH 20/21] cs --- tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php index 410bd1d..a2af661 100644 --- a/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php +++ b/tests/Rules/StrictCalls/StrictFunctionCallsRuleTest.php @@ -103,7 +103,6 @@ public function testFixPositionalBeforeNamed(): void ); } - public function testBug231(): void { if (PHP_VERSION_ID < 80100) { From fa5f83ce3a264b0e8e2686cc82bdfaf017f7f145 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 5 Nov 2026 08:12:25 +0100 Subject: [PATCH 21/21] fix name collision --- .../data/strict-function-calls-positional-before-named.php | 2 +- .../strict-function-calls-positional-before-named.php.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php index 0c9704f..fc704c4 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php +++ b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php @@ -1,6 +1,6 @@ = 8.2 -namespace StrictCallsNamedArguments; +namespace StrictCallsNamedArgumentsFollowedByPositional; use function in_array; diff --git a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed index d2f968f..69fd467 100644 --- a/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed +++ b/tests/Rules/StrictCalls/data/strict-function-calls-positional-before-named.php.fixed @@ -1,6 +1,6 @@ = 8.2 -namespace StrictCallsNamedArguments; +namespace StrictCallsNamedArgumentsFollowedByPositional; use function in_array;