Fix phpstan/phpstan#12063: Call to function is_callable() with array{...} will always evaluate to true#5409
Conversation
…thod names - When ConstantArrayType::findTypeAndMethodNames() skips non-existent methods, isCallable() now accounts for those skipped entries - Added doFindTypeAndMethodNames() private helper with out parameter tracking whether any method names were skipped due to not existing - New regression test in tests/PHPStan/Rules/Comparison/data/bug-12063.php
| /** @return ConstantArrayTypeAndMethod[] */ | ||
| public function findTypeAndMethodNames(): array | ||
| { | ||
| $hasNonExistentMethod = false; |
There was a problem hiding this comment.
Do we have to instantiate this variable since we don't use it ?
There was a problem hiding this comment.
Done. Made the $hasNonExistentMethod parameter optional with a default value of false in doFindTypeAndMethodNames(), so findTypeAndMethodNames() no longer needs to instantiate a variable it never uses. All tests and static analysis pass.
Make the by-reference parameter optional with a default value so findTypeAndMethodNames() doesn't need to instantiate a variable it never reads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VincentLanglet
left a comment
There was a problem hiding this comment.
Add non regression for phpstan/phpstan#4608, for this, ensure the three last line of
$c = new class {
public function abc(): void {}
};
$s = rand(0, 1) ? 'abc' : 'not_abc';
$c->{$s}();
call_user_func([$c, $s]);
[$c, $s]();
report an error
- Call to an undefined method class@anonymous/tmp.php:3::not_abc().
- Parameter #1 $callback of function call_user_func expects callable(): mixed, array{class@anonymous/tmp.php:3, 'abc'|'not_abc'} given.
- Trying to invoke array{class@anonymous/tmp.php:3, 'abc'|'not_abc'} but it might not be a callable.
Ensure that union method names ('abc'|'not_abc') on anonymous classes
correctly report errors for dynamic method calls, call_user_func, and
array callable invocations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The background |
VincentLanglet
left a comment
There was a problem hiding this comment.
You get a failure on PHP 7.4
1) PHPStan\Rules\Functions\CallToFunctionParametersRuleTest::testBug4608
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'11: Parameter #1 $callback of function call_user_func expects callable(): mixed, array{class@anonymous/tests/PHPStan/Rules/Functions/data/bug-4608-call-user-func.php:5, 'abc'|'not_abc'} given.
+'11: Parameter #1 $function of function call_user_func expects callable(): mixed, array{class@anonymous/tests/PHPStan/Rules/Functions/data/bug-4608-call-user-func.php:5, 'abc'|'not_abc'} given.
'
The name of the param is depend on the PHP VERSION ID
The first parameter of call_user_func is named $function in PHP 7.x but was renamed to $callback in PHP 8.0+. Use PHP_VERSION_ID to select the correct name in the test assertion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Fixed. The |
VincentLanglet
left a comment
There was a problem hiding this comment.
Fix
--------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
--------------------------------------------------------------------------------
2825 | ERROR | Variable "$paramName" not allowed in double quoted string; use
| | sprintf() instead (Squiz.Strings.DoubleQuoteUsage.ContainsVar)
--------------------------------------------------------------------------------
…ed string Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
…...} will always evaluate to true (phpstan#5409)
Summary
When
is_callable()is called with an array like[$object, $methodName]where$methodNameis a union type containing both existing and non-existing method names (e.g.,'existingMethod'|'nonExistingMethod'), PHPStan incorrectly reported that the call will always evaluate totrue. The fix makesisCallable()returnmaybewhen some method names in the union don't exist on the target class.Changes
ConstantArrayType::isCallable()insrc/Type/Constant/ConstantArrayType.phpto detect whenfindTypeAndMethodNames()skipped non-existent methodsfindTypeAndMethodNames()into a privatedoFindTypeAndMethodNames(bool &$hasNonExistentMethod)helper that tracks whether any method names were skippedisCallable()ANDs the result withmaybeto prevent falseyesresultsRoot cause
ConstantArrayType::findTypeAndMethodNames()skips method names that don't exist on the target class (hasMethod()->no()→continue). This means when checkingis_callable([$obj, 'existingMethod'|'nonExistingMethod']), onlyexistingMethodwas returned, makingisCallable()conclude the array is always callable. The fix preserves the skip behavior (needed forgetCallableParametersAcceptors) but letsisCallable()know that some methods were filtered out.Test
Added
tests/PHPStan/Rules/Comparison/data/bug-12063.phpwith the exact reproducing code from the issue — a class withexistingMethodand a loop over a mapping that includes bothexistingMethodandnonExistingMethod. The test expects no errors (the false positivefunction.alreadyNarrowedTypeshould not be reported).Fixes phpstan/phpstan#12063
Fixes phpstan/phpstan#4608