From 789601165da41f885441496f990473f3407b6340 Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 10 Jun 2024 10:16:31 +0200 Subject: [PATCH] Check array functions which require stringish values --- conf/bleedingEdge.neon | 1 + conf/config.level5.neon | 11 +- conf/config.neon | 1 + conf/parametersSchema.neon | 1 + phpstan-baseline.neon | 16 + src/Rules/Functions/ImplodeFunctionRule.php | 6 + .../ParameterCastableToStringFunctionRule.php | 170 ++++++++ .../Functions/ImplodeFunctionRuleTest.php | 2 +- ...ameterCastableToStringFunctionRuleTest.php | 373 ++++++++++++++++++ .../Rules/Functions/data/bug-11111.php | 26 ++ .../Rules/Functions/data/bug-11141.php | 22 ++ .../PHPStan/Rules/Functions/data/bug-3946.php | 8 + .../PHPStan/Rules/Functions/data/bug-5848.php | 12 + ...aram-castable-to-string-functions-enum.php | 32 ++ ...astable-to-string-functions-named-args.php | 41 ++ .../param-castable-to-string-functions.php | 83 ++++ 16 files changed, 803 insertions(+), 2 deletions(-) create mode 100644 src/Rules/Functions/ParameterCastableToStringFunctionRule.php create mode 100644 tests/PHPStan/Rules/Functions/ParameterCastableToStringFunctionRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11111.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11141.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3946.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5848.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-enum.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-named-args.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index f6cc07dab3..788cec4791 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -53,5 +53,6 @@ parameters: magicConstantOutOfContext: true paramOutType: true pure: true + checkParameterCastableToStringFunctions: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 5a7516931d..d4cdc74875 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -12,10 +12,11 @@ conditionalTags: phpstan.rules.rule: %featureToggles.arrayValues% PHPStan\Rules\Functions\CallUserFuncRule: phpstan.rules.rule: %featureToggles.callUserFunc% + PHPStan\Rules\Functions\ParameterCastableToStringFunctionRule: + phpstan.rules.rule: %featureToggles.checkParameterCastableToStringFunctions% rules: - PHPStan\Rules\DateTimeInstantiationRule - - PHPStan\Rules\Functions\ImplodeFunctionRule services: - @@ -37,3 +38,11 @@ services: - class: PHPStan\Rules\Functions\CallUserFuncRule + - + class: PHPStan\Rules\Functions\ImplodeFunctionRule + arguments: + disabled: %featureToggles.checkParameterCastableToStringFunctions% + tags: + - phpstan.rules.rule + - + class: PHPStan\Rules\Functions\ParameterCastableToStringFunctionRule diff --git a/conf/config.neon b/conf/config.neon index b2e0b4c394..b53e8c4902 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -88,6 +88,7 @@ parameters: magicConstantOutOfContext: false paramOutType: false pure: false + checkParameterCastableToStringFunctions: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 1c621ce153..e4bf73db35 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -83,6 +83,7 @@ parametersSchema: magicConstantOutOfContext: bool() paramOutType: bool() pure: bool() + checkParameterCastableToStringFunctions: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 58be161dd3..bc9976e16b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1825,6 +1825,22 @@ parameters: count: 1 path: tests/PHPStan/Rules/DeadCode/NoopRuleTest.php + - + message: """ + #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRule\\: + Replaced by PHPStan\\\\Rules\\\\Functions\\\\ParameterCastableToStringFunctionRule$# + """ + count: 1 + path: tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php + + - + message: """ + #^Return type of method PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRule\\: + Replaced by PHPStan\\\\Rules\\\\Functions\\\\ParameterCastableToStringFunctionRule$# + """ + count: 1 + path: tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php + - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" count: 1 diff --git a/src/Rules/Functions/ImplodeFunctionRule.php b/src/Rules/Functions/ImplodeFunctionRule.php index a9615bedeb..ef94a288b2 100644 --- a/src/Rules/Functions/ImplodeFunctionRule.php +++ b/src/Rules/Functions/ImplodeFunctionRule.php @@ -17,6 +17,7 @@ use function sprintf; /** + * @deprecated Replaced by PHPStan\Rules\Functions\ParameterCastableToStringFunctionRule * @implements Rule */ class ImplodeFunctionRule implements Rule @@ -25,6 +26,7 @@ class ImplodeFunctionRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, private RuleLevelHelper $ruleLevelHelper, + private bool $disabled, ) { } @@ -36,6 +38,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($this->disabled) { + return []; + } + if (!($node->name instanceof Node\Name)) { return []; } diff --git a/src/Rules/Functions/ParameterCastableToStringFunctionRule.php b/src/Rules/Functions/ParameterCastableToStringFunctionRule.php new file mode 100644 index 0000000000..8717633425 --- /dev/null +++ b/src/Rules/Functions/ParameterCastableToStringFunctionRule.php @@ -0,0 +1,170 @@ + + */ +class ParameterCastableToStringFunctionRule implements Rule +{ + + public function __construct( + private ReflectionProvider $reflectionProvider, + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Node\Name)) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + $functionName = $functionReflection->getName(); + $implodeFunctions = ['implode', 'join']; + $checkAllArgsFunctions = ['array_intersect', 'array_intersect_assoc', 'array_diff', 'array_diff_assoc']; + $checkFirstArgFunctions = [ + 'array_unique', + 'array_combine', + 'sort', + 'rsort', + 'asort', + 'arsort', + 'natcasesort', + 'natsort', + 'array_count_values', + 'array_fill_keys', + ]; + + if ( + !in_array($functionName, $checkAllArgsFunctions, true) + && !in_array($functionName, $checkFirstArgFunctions, true) + && !in_array($functionName, $implodeFunctions, true) + ) { + return []; + } + + $origArgs = $node->getArgs(); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $origArgs, + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $errorMessage = 'Parameter %s of function %s expects an array of values castable to string, %s given.'; + $getNormalizedArgs = static function () use ($parametersAcceptor, $node): array { + $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + + if ($normalizedFuncCall === null) { + return []; + } + + return $normalizedFuncCall->getArgs(); + }; + if (in_array($functionName, $implodeFunctions, true)) { + $normalizedArgs = $getNormalizedArgs(); + $errorMessage = 'Parameter %s of function %s expects array, %s given.'; + if (count($normalizedArgs) === 1) { + $argsToCheck = [0 => $normalizedArgs[0]]; + } elseif (count($normalizedArgs) === 2) { + $argsToCheck = [1 => $normalizedArgs[1]]; + } else { + return []; + } + } elseif (in_array($functionName, $checkAllArgsFunctions, true)) { + $argsToCheck = $origArgs; + } elseif (in_array($functionName, $checkFirstArgFunctions, true)) { + $normalizedArgs = $getNormalizedArgs(); + if ($normalizedArgs === []) { + return []; + } + $argsToCheck = [0 => $normalizedArgs[0]]; + } else { + return []; + } + + $origNamedArgs = []; + foreach ($origArgs as $arg) { + if ($arg->unpack || $arg->name === null) { + continue; + } + + $origNamedArgs[$arg->name->toString()] = $arg; + } + + $errors = []; + $functionParameters = $parametersAcceptor->getParameters(); + + foreach ($argsToCheck as $argIdx => $arg) { + if ($arg->unpack) { + continue; + } + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $arg->value, + '', + static fn (Type $type): bool => !$type->getIterableValueType()->toString() instanceof ErrorType, + ); + + if ($typeResult->getType() instanceof ErrorType + || !$typeResult->getType()->getIterableValueType()->toString() instanceof ErrorType) { + continue; + } + + if (in_array($functionName, $implodeFunctions, true)) { + // implode has weird variants, so $array has to be fixed. It's especially weird with named arguments. + if (array_key_exists('array', $origNamedArgs)) { + $argName = '$array'; + } elseif (array_key_exists('separator', $origNamedArgs) && count($origArgs) === 1) { + $argName = '$separator'; + } else { + $argName = sprintf('#%d $array', $argIdx + 1); + } + } elseif (array_key_exists($argIdx, $functionParameters)) { + $paramName = $functionParameters[$argIdx]->getName(); + $argName = array_key_exists($paramName, $origNamedArgs) + ? sprintf('$%s', $paramName) + : sprintf('#%d $%s', $argIdx + 1, $paramName); + } else { + $argName = sprintf('#%d', $argIdx + 1); + } + + $errors[] = RuleErrorBuilder::message( + sprintf($errorMessage, $argName, $functionName, $typeResult->getType()->describe(VerbosityLevel::typeOnly())), + )->identifier('argument.type')->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php index 547f66ebc6..44755df63d 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php @@ -15,7 +15,7 @@ class ImplodeFunctionRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ImplodeFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false, false, true, false)); + return new ImplodeFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false, false, true, false), false); } public function testFile(): void diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToStringFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToStringFunctionRuleTest.php new file mode 100644 index 0000000000..8574786b3e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToStringFunctionRuleTest.php @@ -0,0 +1,373 @@ + + */ +class ParameterCastableToStringFunctionRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new ParameterCastableToStringFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false, false, true, false)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/param-castable-to-string-functions.php'], $this->hackParameterNames([ + [ + 'Parameter #1 $array of function array_intersect expects an array of values castable to string, array given.', + 16, + ], + [ + 'Parameter #2 $arrays of function array_intersect expects an array of values castable to string, array given.', + 17, + ], + [ + 'Parameter #3 of function array_intersect expects an array of values castable to string, array given.', + 18, + ], + [ + 'Parameter #2 $arrays of function array_diff expects an array of values castable to string, array given.', + 19, + ], + [ + 'Parameter #2 $arrays of function array_diff_assoc expects an array of values castable to string, array given.', + 20, + ], + [ + 'Parameter #1 $array of function array_unique expects an array of values castable to string, array> given.', + 22, + ], + [ + 'Parameter #1 $keys of function array_combine expects an array of values castable to string, array> given.', + 23, + ], + [ + 'Parameter #1 $array of function sort expects an array of values castable to string, array> given.', + 26, + ], + [ + 'Parameter #1 $array of function sort expects an array of values castable to string, array given.', + 27, + ], + [ + 'Parameter #1 $array of function rsort expects an array of values castable to string, array> given.', + 28, + ], + [ + 'Parameter #1 $array of function asort expects an array of values castable to string, array> given.', + 29, + ], + [ + 'Parameter #1 $array of function arsort expects an array of values castable to string, array> given.', + 30, + ], + [ + 'Parameter #1 $array of function natsort expects an array of values castable to string, array> given.', + 31, + ], + [ + 'Parameter #1 $array of function natcasesort expects an array of values castable to string, array> given.', + 32, + ], + [ + 'Parameter #1 $array of function array_count_values expects an array of values castable to string, array> given.', + 33, + ], + [ + 'Parameter #1 $keys of function array_fill_keys expects an array of values castable to string, array> given.', + 34, + ], + ])); + } + + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/param-castable-to-string-functions-named-args.php'], [ + [ + 'Parameter $array of function array_unique expects an array of values castable to string, array> given.', + 16, + ], + [ + 'Parameter $keys of function array_combine expects an array of values castable to string, array> given.', + 17, + ], + [ + 'Parameter $array of function sort expects an array of values castable to string, array> given.', + 19, + ], + [ + 'Parameter $array of function rsort expects an array of values castable to string, array> given.', + 20, + ], + [ + 'Parameter $array of function asort expects an array of values castable to string, array> given.', + 21, + ], + [ + 'Parameter $array of function arsort expects an array of values castable to string, array> given.', + 22, + ], + [ + 'Parameter $keys of function array_fill_keys expects an array of values castable to string, array> given.', + 23, + ], + [ + 'Parameter $array of function implode expects array, array> given.', + 25, + ], + [ + 'Parameter $separator of function implode expects array, array> given.', + 26, + ], + [ + 'Parameter $array of function implode expects array, array> given.', + 27, + ], + [ + 'Parameter $array of function implode expects array, array> given.', + 28, + ], + ]); + } + + public function testEnum(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/param-castable-to-string-functions-enum.php'], [ + [ + 'Parameter #1 $array of function array_intersect expects an array of values castable to string, array given.', + 12, + ], + [ + 'Parameter #2 $arrays of function array_intersect expects an array of values castable to string, array given.', + 13, + ], + [ + 'Parameter #3 of function array_intersect expects an array of values castable to string, array given.', + 14, + ], + [ + 'Parameter #2 $arrays of function array_diff expects an array of values castable to string, array given.', + 15, + ], + [ + 'Parameter #2 $arrays of function array_diff_assoc expects an array of values castable to string, array given.', + 16, + ], + [ + 'Parameter #1 $array of function array_unique expects an array of values castable to string, array given.', + 18, + ], + [ + 'Parameter #1 $keys of function array_combine expects an array of values castable to string, array given.', + 19, + ], + [ + 'Parameter #1 $array of function sort expects an array of values castable to string, array given.', + 21, + ], + [ + 'Parameter #1 $array of function rsort expects an array of values castable to string, array given.', + 22, + ], + [ + 'Parameter #1 $array of function asort expects an array of values castable to string, array given.', + 23, + ], + [ + 'Parameter #1 $array of function arsort expects an array of values castable to string, array given.', + 24, + ], + [ + 'Parameter #1 $array of function natsort expects an array of values castable to string, array given.', + 25, + ], + [ + 'Parameter #1 $array of function natcasesort expects an array of values castable to string, array given.', + 26, + ], + [ + 'Parameter #1 $array of function array_count_values expects an array of values castable to string, array given.', + 27, + ], + [ + 'Parameter #1 $keys of function array_fill_keys expects an array of values castable to string, array given.', + 28, + ], + [ + 'Parameter #2 $array of function implode expects array, array given.', + 31, + ], + ]); + } + + public function testImplode(): void + { + $this->analyse([__DIR__ . '/data/implode.php'], $this->hackParameterNames([ + [ + 'Parameter #2 $array of function implode expects array, array|string> given.', + 9, + ], + [ + 'Parameter #1 $array of function implode expects array, array> given.', + 11, + ], + [ + 'Parameter #1 $array of function implode expects array, array> given.', + 12, + ], + [ + 'Parameter #1 $array of function implode expects array, array> given.', + 13, + ], + [ + 'Parameter #2 $array of function implode expects array, array> given.', + 15, + ], + [ + 'Parameter #2 $array of function join expects array, array> given.', + 16, + ], + ])); + } + + public function testBug6000(): void + { + $this->analyse([__DIR__ . '/../Arrays/data/bug-6000.php'], []); + } + + public function testBug8467a(): void + { + $this->analyse([__DIR__ . '/../Arrays/data/bug-8467a.php'], []); + } + + public function testBug5848(): void + { + $this->analyse([__DIR__ . '/data/bug-5848.php'], $this->hackParameterNames([ + [ + 'Parameter #1 $array of function array_diff expects an array of values castable to string, array given.', + 8, + ], + [ + 'Parameter #2 $arrays of function array_diff expects an array of values castable to string, array given.', + 8, + ], + ])); + } + + public function testBug3946(): void + { + $this->analyse([__DIR__ . '/data/bug-3946.php'], [ + [ + 'Parameter #1 $keys of function array_combine expects an array of values castable to string, array|Bug3946\stdClass|float|int|string> given.', + 8, + ], + ]); + } + + public function testBug11111(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-11111.php'], [ + [ + 'Parameter #1 $keys of function array_fill_keys expects an array of values castable to string, array given.', + 23, + ], + [ + 'Parameter #1 $keys of function array_fill_keys expects an array of values castable to string, array given.', + 26, + ], + ]); + } + + public function testBug11141(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-11141.php'], [ + [ + 'Parameter #1 $array of function array_diff expects an array of values castable to string, array given.', + 22, + ], + [ + 'Parameter #2 $arrays of function array_diff expects an array of values castable to string, array given.', + 22, + ], + ]); + } + + /** + * @param list $errors + * @return list + */ + private function hackParameterNames(array $errors): array + { + if (PHP_VERSION_ID >= 80000) { + return $errors; + } + + return array_map(static function (array $error): array { + $error[0] = str_replace( + [ + '$array of function array_diff', + '$array of function array_diff_assoc', + '$array of function array_intersect', + '$arrays of function array_intersect', + '$arrays of function array_diff', + '$arrays of function array_diff_assoc', + '$array of function sort', + '$array of function rsort', + '$array of function asort', + '$array of function arsort', + '$array of function natsort', + '$array of function natcasesort', + '$array of function array_count_values', + '#3 of function array_intersect', + ], + [ + '$arr1 of function array_diff', + '$arr1 of function array_diff_assoc', + '$arr1 of function array_intersect', + '$arr2 of function array_intersect', + '$arr2 of function array_diff', + '$arr2 of function array_diff_assoc', + '$array_arg of function sort', + '$array_arg of function rsort', + '$array_arg of function asort', + '$array_arg of function arsort', + '$array_arg of function natsort', + '$array_arg of function natcasesort', + '$input of function array_count_values', + '#3 $args of function array_intersect', + ], + $error[0], + ); + + return $error; + }, $errors); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/bug-11111.php b/tests/PHPStan/Rules/Functions/data/bug-11111.php new file mode 100644 index 0000000000..c36a4ae778 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11111.php @@ -0,0 +1,26 @@ += 8.1 + +namespace Bug11111; + +enum Language: string +{ + case ENG = 'eng'; + case FRE = 'fre'; + case GER = 'ger'; + case ITA = 'ita'; + case SPA = 'spa'; + case DUT = 'dut'; + case DAN = 'dan'; +} + +/** @var Language[] $langs */ +$langs = [ + Language::ENG, + Language::GER, + Language::DAN, +]; + +$array = array_fill_keys($langs, null); +unset($array[Language::GER]); + +var_dump(array_fill_keys([Language::ITA, Language::DUT], null)); diff --git a/tests/PHPStan/Rules/Functions/data/bug-11141.php b/tests/PHPStan/Rules/Functions/data/bug-11141.php new file mode 100644 index 0000000000..f9eaddf4fb --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11141.php @@ -0,0 +1,22 @@ += 8.1 + +namespace Bug11141; + +enum Language: string +{ + case ENG = 'eng'; + case FRE = 'fre'; + case GER = 'ger'; + case ITA = 'ita'; + case SPA = 'spa'; + case DUT = 'dut'; + case DAN = 'dan'; +} + +$langs = [ + Language::ENG, + Language::GER, + Language::DAN, +]; + +$result = array_diff($langs, [Language::DAN]); diff --git a/tests/PHPStan/Rules/Functions/data/bug-3946.php b/tests/PHPStan/Rules/Functions/data/bug-3946.php new file mode 100644 index 0000000000..bdb12cccb0 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3946.php @@ -0,0 +1,8 @@ +test(); diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-enum.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-enum.php new file mode 100644 index 0000000000..ee0cca82ea --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-enum.php @@ -0,0 +1,32 @@ += 8.1 + +namespace ParamCastableToStringFunctionsEnum; + +enum FooEnum +{ + case A; +} + +function invalidUsages() +{ + array_intersect([FooEnum::A], ['a']); + array_intersect(['a'], [FooEnum::A]); + array_intersect(['a'], [], [FooEnum::A]); + array_diff(['a'], [FooEnum::A]); + array_diff_assoc(['a'], [FooEnum::A]); + + array_unique(['a', FooEnum::A]); + array_combine([FooEnum::A], [['b']]); + $arr1 = [FooEnum::A]; + sort($arr1); + rsort($arr1); + asort($arr1); + arsort($arr1); + natsort($arr1); + natcasesort($arr1); + array_count_values($arr1); + array_fill_keys($arr1, 5); + + + implode(',', [FooEnum::A]); +} diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-named-args.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-named-args.php new file mode 100644 index 0000000000..d36d76d194 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions-named-args.php @@ -0,0 +1,41 @@ += 8.0 + +namespace ParamCastableToStringFunctionsNamedArgs; + +class ClassWithoutToString {} +class ClassWithToString +{ + public function __toString(): string + { + return 'foo'; + } +} + +function invalidUsages() +{ + array_unique(flags: SORT_STRING, array: [['a'], ['b']]); + array_combine(values: [['b']], keys: [['a']]); + $arr1 = [['a']]; + sort(flags: SORT_REGULAR, array: $arr1); + rsort(flags: SORT_REGULAR, array: $arr1); + asort(flags: SORT_REGULAR, array: $arr1); + arsort(flags: SORT_REGULAR, array: $arr1); + array_fill_keys(value: 5, keys: $arr1); + // implode weirdness + implode(array: [['a']], separator: ','); + implode(separator: [['a']]); + implode(',', array: [['a']]); + implode(separator: ',', array: [['']]); +} + +function validUsages() +{ + array_unique(flags: SORT_STRING, array: ['a', 'b']); + array_combine(values: [['b']], keys: ['a']); + $arr1 = ['a']; + sort(flags: SORT_REGULAR, array: $arr1); + rsort(flags: SORT_REGULAR, array: $arr1); + asort(flags: SORT_REGULAR, array: $arr1); + arsort(flags: SORT_REGULAR, array: $arr1); + array_fill_keys(value: 5, keys: $arr1); +} diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions.php new file mode 100644 index 0000000000..aef61a2f53 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-string-functions.php @@ -0,0 +1,83 @@ +