Do not use callable parameter types as native types for closure and arrow function parameters#5632
Conversation
VincentLanglet
left a comment
There was a problem hiding this comment.
Add non regression test for phpstan/phpstan#5271
Add a nsrt test for the constant array
$undefined_curl_functions = array_filter(
[
'curl_multi_add_handle',
'curl_multi_exec',
'curl_multi_init',
],
static function( $function_name ) {
assertType(..., $function_name);
assertNativeType(..., $function_name);
return true;
}
);
|
|
VincentLanglet
left a comment
There was a problem hiding this comment.
Do not default to null for the param nativeCallableParameters this will simplify the code
|
Done. Removed the |
VincentLanglet
left a comment
There was a problem hiding this comment.
Merge both bug11014 into a single file use for both nsrt and rule test.
And use // lint >= 8.0 on it
|
Done. Merged the two bug-11014 test files into a single |
|
The rector changes is expected since they are using native type |
29216a3 to
8e47fa3
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
Tests are failing
1) PHPStan\Analyser\NodeScopeResolverTest::testFile@tests\PHPStan\Analyser\nsrt\bug-11014.php with data ('D:\a\phpstan-src\phpstan-src\...14.php')
Failed assertions in D:\a\phpstan-src\phpstan-src\tests\PHPStan\Analyser\nsrt\bug-11014.php:
Line 84:
Expected: 'curl_multi_add_handle'|'curl_multi_exec'|'curl_multi_init'
Actual: mixed
Line 96:
Expected: 'curl_multi_add_handle'|'curl_multi_exec'|'curl_multi_init'
Actual: mixed
Why the native type is not correct for constantArray which are nativeType
|
Pushed. Here's a summary of what was done: Problem: For constant arrays passed to Fix (3 files):
|
VincentLanglet
left a comment
There was a problem hiding this comment.
Don't look for a specific fix for array_map/array_filter but more for a general fix.
You say that the callback parameter's native PHP type is just callable which loses this information but the type is correctly understood. So we maybe need to update the native PHP type too in these case.
…rrow function parameters - In `enterAnonymousFunctionWithoutReflection` and `enterArrowFunctionWithoutReflection`, the callable parameter types (derived from calling context, often via PHPDoc/generics) were used as both expression types AND native types for closure parameters - Now only the declared PHP type hint is used for native types, while the intersected callable parameter type is used for expression types (PHPDoc-aware) - This matches the behavior of `foreach` which correctly distinguishes native vs PHPDoc iterable value types - Fixes false positives from `function.alreadyNarrowedType` when `treatPhpDocTypesAsCertain` is false and callbacks are passed to array_map, array_filter, usort, etc.
…array_filter For constant arrays passed to array_map/array_filter, the native types are known (e.g. literal string unions), but the callback parameter's native PHP type is just `callable` which loses this information. Tag array_filter callbacks with the array expression (like ArrayMapArgVisitor does for array_map), then compute native callable parameters from the actual array arguments' native types in processClosureNode, processArrowFunctionNode, and ClosureTypeResolver. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bdec861 to
a37a1f5
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
Don't look for a specific fix for array_map/array_filter but more for a general fix.
You say that "the callback parameter's
native PHP type is just callable which loses this information" but the type is correctly understood. So we maybe need to update the native PHP type too in these case.
…neral native type resolution Instead of special-casing array_map/array_filter in NodeScopeResolver and ClosureTypeResolver, compute native callable parameter types alongside PHPDoc types in ParametersAcceptorSelector::selectFromArgs(). This uses ExtendedDummyParameter + ExtendedFunctionVariant to preserve native types through the template resolution pipeline, so that $matchedParameter->getNativeType() returns the correctly resolved native callable type for all callback-accepting functions. Changes: - ParametersAcceptorSelector: array_map, array_filter, array_walk, and array_find special handling now computes native callback parameter types using getNativeType() and stores them via ExtendedDummyParameter - NodeScopeResolver: removed createNativeCallableParametersFromArrayArgs() since createNativeCallableParameters() now works via the general path - ClosureTypeResolver: removed ArrayFilterArgVisitor::CALLBACK_ATTRIBUTE_NAME fallback; array_filter closures use the inFunctionCallsStack general path - ArrayFilterArgVisitor: removed CALLBACK_ATTRIBUTE_NAME constant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pushed. Here's a summary of what was changed to address the review: Problem: The previous approach special-cased Root cause: Fix: In Removed:
All tests pass ( |
Summary
When
treatPhpDocTypesAsCertainis false, PHPStan incorrectly reportsfunction.alreadyNarrowedTypefor type checks inside closures/arrow functions passed toarray_map,array_filter, and similar functions. The issue is that PHPDoc-derived callable parameter types were being treated as native types.Changes
MutatingScope::enterAnonymousFunctionWithoutReflection()to track the declared PHP type (before intersection with callable parameters) separately and use it for native expression typesMutatingScope::enterArrowFunctionWithoutReflection()with the same fix — pass the declared type as the native type argument toassignVariable()Root cause
In
enterAnonymousFunctionWithoutReflection, lines 2037-2039 (before fix):The
$parameterTypeincluded the callable parameter type from the calling context (e.g.,stringfromarray_map(fn($item) => ..., $stringArray)). This type was derived from PHPDoc annotations (@param string[] $values), but was incorrectly used as the native type. WhentreatPhpDocTypesAsCertainis false, the rule uses$scope->getNativeType()which returnedstringinstead ofmixed, making it reportis_string()as always true.The same pattern existed in
enterArrowFunctionWithoutReflectionwhereassignVariable($name, $parameterType, $parameterType, ...)used the same enriched type for both the expression type and native type.This mirrors how
foreachcorrectly handles the distinction: it callsgetIterableValueType()on both the PHPDoc type AND the native type separately, usingassignVariable($name, $valueType, $nativeValueType, ...).Analogous cases probed
array_map— was broken, now fixed ✓array_map— was broken, now fixed ✓array_filter— was broken, now fixed ✓array_filter— was broken, now fixed ✓usort,uasort,uksortand all other callback-accepting functions — go through the same code path, now fixed ✓ImpossibleCheckTypeMethodCallRule,ImpossibleCheckTypeStaticMethodCallRule) — use the sameImpossibleCheckTypeHelperand same$scope->getNativeType(), so they benefit from the scope fix ✓foreachloop — was already correct (uses separate native/PHPDoc iterable types) ✓Test
testBug11014inImpossibleCheckTypeFunctionCallRuleTestwith test data covering all four combinations (closure/arrow × array_map/array_filter)tests/PHPStan/Analyser/nsrt/bug-11014.phpverifying native types viaassertNativeType('mixed', $item)for untyped callback parameters andassertNativeType('string', $item)for typed onesFixes phpstan/phpstan#11014