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.
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.
|
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 ( |
d2a8601 to
af0a3dd
Compare
…neral native type resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
af0a3dd to
d40cad9
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
I feel like createExtendedParameter and createModifiedAcceptor are unclear method name
Rename to parameterWithOverriddenType and acceptorWithReplacedParameters to better communicate what these helper methods do. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Renamed the two methods in
All tests (12056) pass and |
VincentLanglet
left a comment
There was a problem hiding this comment.
parameterWithOverriddenType and acceptorWithReplacedParameters are not yet good names.
There is already wrapAcceptor and wrapParameter ; we could build a similar name.
What about overrideParameterType and overrideAcceptorParameters ?
Also parameterWithOverriddenType could use wrapParameter and I wonder if acceptorWithReplacedParameters shouldn't use wrapAcceptor...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…apParameter - Rename parameterWithOverriddenType → overrideParameterType (consistent with wrapParameter naming) - Rename acceptorWithReplacedParameters → overrideAcceptorParameters (consistent with wrapAcceptor naming) - Refactor overrideParameterType to use wrapParameter internally instead of duplicating instanceof ExtendedParameterReflection checks - Fix use statement ordering in ClosureTypeResolver.php Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pushed. Here's what was done:
|
VincentLanglet
left a comment
There was a problem hiding this comment.
Add non regression for phpstan/phpstan#5271
Closes phpstan/phpstan#5271 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pushed. Added a non-regression test for issue #5271 in two places:
|
VincentLanglet
left a comment
There was a problem hiding this comment.
instead of a nsrt test bug-5271 should have a rule test checking that https://phpstan.org/r/37cebac0-0a30-4164-b79d-31d4a7798bdb
/**
* @param BannerPatternLayer[] $patterns
*
* @phpstan-param list<BannerPatternLayer> $patterns
* @return $this
*/
public function setPatterns(array $patterns) : self{
$checked = array_filter($patterns, fn($v) => $v instanceof BannerPatternLayer);
if(count($checked) !== count($patterns)){
throw new \TypeError("Deque must only contain " . BannerPatternLayer::class . " objects");
}
$this->patterns = $checked;
return $this;
}
Does not report
Instanceof between BannerPatternLayer and BannerPatternLayer will always evaluate to true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Updated the bug-5271 test to use the actual playground reproducer from the issue — the |
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
Fixes phpstan/phpstan#5271