Fix phpstan/phpstan#11619: Parameter #2 $callback of function uasort expects callable(Foo, Foo): int, 'strnatcasecmp' given.#5171
Conversation
befcc3e to
b5e3d6e
Compare
| return $this->function->getAsserts(); | ||
| } | ||
|
|
||
| public function isBuiltin(): bool |
There was a problem hiding this comment.
this method should return TrinaryLogic instead of bool
There was a problem hiding this comment.
Done. Changed FunctionCallableVariant::isBuiltin() to return TrinaryLogic instead of bool, as requested by staabm. The method now returns the TrinaryLogic directly when the underlying function provides one, or wraps the bool via TrinaryLogic::createFromBoolean(). The call site in CallableType.php was updated to use ->yes() on the result. Both PHPStan and tests pass.
…mode - Propagated $strictTypes from CallableType::accepts() through to CallableTypeHelper - In non-strict mode, Stringable objects are now accepted where string is expected in callable parameters - This fixes false positives for patterns like uasort($stringableArray, 'strnatcasecmp') - In strict_types mode, the stricter behavior is preserved (e.g. closures with explicit string params) - New regression test in tests/PHPStan/Rules/Functions/data/bug-11619.php Closes phpstan/phpstan#11619
Adds a second test file with declare(strict_types=1) to verify that in strict mode, passing strnatcasecmp to uasort/usort with Stringable objects is correctly reported as an error. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Built-in PHP functions always perform implicit type coercion regardless of declare(strict_types=1), so Stringable objects should be accepted as string parameters even in strict mode. Instead of propagating the calling scope's $strictTypes, detect whether the callable is a built-in function via FunctionCallableVariant::isBuiltin() and use non-strict checking for its parameters. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
b7187f1 to
e1817a5
Compare
| return $this->function->getAsserts(); | ||
| } | ||
|
|
||
| public function isBuiltin(): TrinaryLogic |
There was a problem hiding this comment.
implement this method on CallableParametersAcceptor instead
There was a problem hiding this comment.
Done. The changes:
- Added
isBuiltin(): TrinaryLogicto theCallableParametersAcceptorinterface - Implemented it in all 7 implementors:
ClosureType,CallableType,TrivialParametersAcceptor,InaccessibleMethod(all returnTrinaryLogic::createNo()),ExtendedCallableFunctionVariant,ResolvedFunctionVariantWithCallable(propagate via constructor parameter), andFunctionCallableVariant(existing implementation) - Updated creation sites in
GenericParametersAcceptorResolverandParametersAcceptorSelectorto propagateisBuiltinthrough - Simplified
CallableType::isSuperTypeOfInternal()to use$variant->isBuiltin()->yes()instead of$variant instanceof FunctionCallableVariant && $variant->isBuiltin()->yes()
Instead of checking `instanceof FunctionCallableVariant` in CallableType, the isBuiltin() method is now part of the CallableParametersAcceptor interface and implemented by all classes. The value is propagated through ExtendedCallableFunctionVariant and ResolvedFunctionVariantWithCallable. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
I have added more tests, but I am getting to a point in which I don't know when/why php-src accepts a it clearly depends on strict-types, but there is some other factor playing in. |
Looking at
I wonder if the initial commit is not good enough for the fix
And then we could open an issue on php-src to know
NB: it's more related to uasort than to the fact strnatcasecmp is buildin because |
yeah lets get in contact with php-src to get an idea how it is supposed to work.
I think we can decide better about that after we got some more information from php-src. |
|
ok - so to sum up the result of the upstream report: when a built-in function is directly invoked from a built-in function it is always treated as strict-types=0. |
218d339 to
ef20977
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
I simplified the test, but didn't udpate the error expected.
In the call uasort($options, 'strnatcasecmp'); we don't have error. It's not because strnatcasecmp is build-in but because uasort is buildin. Please ensure the implementation is checking the right function, then update the tests.
The non-error on `uasort($options, 'strnatcasecmp')` in strict mode is because uasort (the caller) is builtin, not because strnatcasecmp (the callback) is builtin. Per PHP internals, when a builtin function calls a callback, it always uses strict_types=0. - Remove isBuiltin() from CallableParametersAcceptor interface and all implementations (was checking the wrong function) - Forward $strictTypes through CallableType/ClosureType accepts() to CallableTypeHelper - In FunctionCallParametersCheck, set strictTypes=false for callable parameters of builtin functions - Update tests: customUsort (user-defined) in strict mode now correctly reports an error, while uasort/usort (builtin) do not Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Both background tasks completed successfully as well. All done — the commit has been pushed to the PR. |
| $callableStrictTypes = $scope->isDeclareStrictTypes(); | ||
| if ($isBuiltin && $parameterType->isCallable()->yes()) { | ||
| $callableStrictTypes = false; | ||
| } |
There was a problem hiding this comment.
This could be a one liner
There was a problem hiding this comment.
The background PHPStan task also completed successfully (exit code 0). Everything is green and pushed.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
| Foo::fromString('a'), | ||
| ]; | ||
|
|
||
| uasort($options, 'strnatcasecmp'); |
There was a problem hiding this comment.
needs another test like
function userlandComparator(string $a, string $b): int {
return strnatcasecmp($a, $b);
}
$options = [
Foo::fromString('c'),
Foo::fromString('b'),
Foo::fromString('a'),
];
uasort($options, 'userlandComparator');
var_dump($options);
for both, strict-types 0 or 1. there should be no error for userland comparators, see https://3v4l.org/gEURZ#veol
There was a problem hiding this comment.
The background make tests task also completed successfully. All 11666 tests passed. The changes have already been committed and pushed.
When a builtin function like uasort calls a userland callback, it's always treated as strict_types=0, so Stringable objects are accepted for string parameters. Added test cases for both strict and non-strict mode to verify no false positives. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Summary
PHPStan incorrectly reported an error when passing a string-comparison function like
strnatcasecmptouasort()for arrays ofStringableobjects. For example,uasort($options, 'strnatcasecmp')where$optionsisFoo[]andFooimplementsStringablewould produce: "Parameter #2 $callback of function uasort expects callable(Foo, Foo): int, 'strnatcasecmp' given."This is a false positive because PHP's internal functions like
strnatcasecmpperform implicit type coercion fromStringabletostring.Changes
src/Type/CallableType.php: Propagated the$strictTypesparameter fromaccepts()throughisSuperTypeOfInternal()toCallableTypeHelper::isParametersAcceptorSuperTypeOf()src/Type/CallableTypeHelper.php: Added optional$strictTypesparameter (defaulttrue) and used it in the callable parameter acceptance check instead of the hardcodedtruetests/PHPStan/Rules/Functions/data/bug-11619.php: New regression test withStringableclass anduasort/usortwithstrnatcasecmptests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php: AddedtestBug11619test methodRoot cause
CallableType::accepts()received a$strictTypesparameter but ignored it, always passingtreatMixedAsAny=trueto its internal method without forwarding$strictTypes. TheCallableTypeHelper::isParametersAcceptorSuperTypeOf()hardcodedstrictTypes=truewhen calling$theirParameter->getType()->accepts(), which causedStringType::accepts()to rejectStringableobjects without checking for__toString().The fix propagates the scope's
$strictTypesvalue through the chain. In non-strict mode (nodeclare(strict_types=1)),StringType::accepts()now checks for__toString()and acceptsStringableobjects. In strict mode, the existing behavior is preserved, which correctly handles cases like bug-12317 where a user-defined closure with explicitstringparameter types should still be flagged.Test
Added
tests/PHPStan/Rules/Functions/data/bug-11619.phpwith aStringableclassFooand calls touasort($options, 'strnatcasecmp')andusort($options, 'strnatcasecmp'). The test expects no errors.Fixes phpstan/phpstan#11619