Skip to content

Commit f52584a

Browse files
dylanpulverclaude
andcommitted
Report only the function's own flags for a parameter without an allowed constant list
The fallback branch rejected every builtin constant passed to a parameter whose type matched the type of some mapped parameter of the same function. For str_pad() that made $length, an unmapped int, reject any int constant, so str_pad('bla', PHP_INT_SIZE, 'bla') was reported even though it is a valid length. The branch now reports a constant only when some other parameter of the function allows it, which is the misplaced-flag case it exists for. Closes phpstan/phpstan#15308 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M5uTyCU4WcNTsPvGrErDo
1 parent 7002950 commit f52584a

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

‎src/Rules/FunctionCallParametersCheck.php‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,14 @@ public function check(
110110
$functionParametersMinCount = 0;
111111
$functionParametersMaxCount = 0;
112112
$allowedConstantsTypes = [];
113+
$parametersWithAllowedConstants = [];
113114
foreach ($parametersAcceptor->getParameters() as $parameter) {
114115
if (
115116
$parameter instanceof ExtendedParameterReflection
116117
&& $parameter->getAllowedConstants() !== null
117118
) {
118119
$allowedConstantsTypes[] = $parameter->getType();
120+
$parametersWithAllowedConstants[] = $parameter;
119121
}
120122
if (!$parameter->isOptional()) {
121123
$functionParametersMinCount++;
@@ -491,6 +493,13 @@ public function check(
491493
if ($constantReflection->isBuiltin()->no()) {
492494
continue;
493495
}
496+
// This parameter has no constant list of its own, so the only mistake
497+
// to report is one of the function's own flags in the wrong position.
498+
// Any other builtin constant is a plain value there: PHP_INT_SIZE is
499+
// a valid str_pad() length.
500+
if (!$this->isAllowedByAnotherParameter($constantReflection, $parametersWithAllowedConstants)) {
501+
continue;
502+
}
494503
$errors[] = RuleErrorBuilder::message(sprintf(
495504
$invalidConstantMessage,
496505
$constantReflection->describe(),
@@ -808,6 +817,23 @@ private function describeParameter(ParameterReflection $parameter, int|string|nu
808817
return implode(' ', $parts);
809818
}
810819

820+
/**
821+
* Whether the constant belongs to the allowed list of some other parameter of the same
822+
* function, which makes it a flag passed in the wrong position rather than a value.
823+
*
824+
* @param list<ExtendedParameterReflection> $parameters Parameters that have an allowed list
825+
*/
826+
private function isAllowedByAnotherParameter(ConstantReflection $constantReflection, array $parameters): bool
827+
{
828+
foreach ($parameters as $parameter) {
829+
if ($parameter->checkAllowedConstants([$constantReflection])->getDisallowedConstants() === []) {
830+
return true;
831+
}
832+
}
833+
834+
return false;
835+
}
836+
811837
/**
812838
* @return list<ConstantReflection>|null Null when the expression is not a constant or bitmask of constants
813839
*/

‎tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,6 +2875,17 @@ public function testBug14312b(): void
28752875
$this->analyse([__DIR__ . '/data/bug-14312b.php'], []);
28762876
}
28772877

2878+
#[RequiresPhp('>= 8.0.0')]
2879+
public function testBug15308(): void
2880+
{
2881+
$this->analyse([__DIR__ . '/data/bug-15308.php'], [
2882+
[
2883+
'Constant STR_PAD_LEFT is not allowed for parameter #2 $length of function str_pad.',
2884+
10,
2885+
],
2886+
]);
2887+
}
2888+
28782889
#[RequiresPhp('>= 8.0.0')]
28792890
public function testConstantParameterCheck(): void
28802891
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug15308;
4+
5+
// $length has no constant list of its own, so a numeric constant is a valid length.
6+
str_pad('bla', PHP_INT_SIZE, 'bla');
7+
str_pad('bla', SODIUM_CRYPTO_PWHASH_SALTBYTES, 'bla');
8+
9+
// One of str_pad()'s own flags in the $length position is still reported.
10+
str_pad('bla', STR_PAD_LEFT, 'bla');

0 commit comments

Comments
 (0)